629 lines
21 KiB
Dart
629 lines
21 KiB
Dart
// this file should handle most of the API calls
|
|
// it also builds some widgets, but it will be modulated later
|
|
|
|
import 'dart:async';
|
|
import 'dart:typed_data';
|
|
|
|
import 'package:pointer_interceptor/pointer_interceptor.dart';
|
|
|
|
import 'structs.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:http/http.dart' as http;
|
|
import 'dart:convert';
|
|
import 'dart:ui_web' as ui;
|
|
import 'augment.dart';
|
|
import 'dart:html' as html;
|
|
import 'dart:js' as js;
|
|
|
|
class ApiService {
|
|
static String ip = "";
|
|
static String port = "";
|
|
static List<AttachmentResponse> threadAttachments =
|
|
[]; //holds attachments of the thread
|
|
static String currFolder = "";
|
|
static List<String> currThread = []; //holds the email ids of the thread
|
|
static String currThreadID = ""; //picked an email it prints the threadID
|
|
|
|
Future<List<GetThreadResponse>> fetchEmailsFromFolder(
|
|
String folder, int pagenitaion) async {
|
|
try {
|
|
var url = Uri.http('$ip:$port', 'sorted_threads_by_date', {
|
|
'folder': folder,
|
|
'limit': '50',
|
|
'offset': pagenitaion.toString(),
|
|
});
|
|
var response = await http.get(url);
|
|
List<GetThreadResponse> allEmails = [];
|
|
|
|
if (response.statusCode == 200) {
|
|
List json = jsonDecode(response.body);
|
|
for (var item in json) {
|
|
//each item in the json is a date
|
|
if (item.length > 1 && item[0] is String && item[1] is List) {
|
|
List<int> threadIDs = List<int>.from(item[1]);
|
|
for (var threadId in threadIDs) {
|
|
await fetchThreads(threadId, allEmails);
|
|
}
|
|
}
|
|
}
|
|
return allEmails;
|
|
} else {
|
|
throw Exception('Failed to load threads');
|
|
}
|
|
} catch (e) {
|
|
print('_displayEmailsFromFolder caught error: $e');
|
|
return [];
|
|
}
|
|
}
|
|
|
|
Future<void> fetchThreads(
|
|
//populates allEmails, which is the List that contains all the emails in a thread
|
|
int threadId,
|
|
List<GetThreadResponse> allEmails) async {
|
|
try {
|
|
var url = Uri.http('${ApiService.ip}:${ApiService.port}', 'get_thread',
|
|
{'id': threadId.toString()});
|
|
var response = await http.get(url);
|
|
|
|
if (response.statusCode == 200) {
|
|
Map<String, dynamic> messagesJson = jsonDecode(response.body);
|
|
GetThreadResponse threadResponse =
|
|
GetThreadResponse.fromJson(messagesJson);
|
|
|
|
allEmails.add(threadResponse);
|
|
} else {
|
|
throw Exception(
|
|
'Failed to fetch thread messages for thread ID: $threadId');
|
|
}
|
|
} catch (e) {
|
|
print('Error fetching thread messages: $e');
|
|
}
|
|
}
|
|
|
|
Future<List<SerializableMessage>> sonicSearch(
|
|
String list, int limit, int offset, String query) async {
|
|
try {
|
|
var url = Uri.http('$ip:$port', 'search_emails', {
|
|
'list': list,
|
|
'limit': limit.toString(),
|
|
'offset': offset.toString(),
|
|
'query': query
|
|
});
|
|
print(url);
|
|
|
|
var response = await http.get(url);
|
|
print(response);
|
|
if (response.statusCode == 200) {
|
|
List<dynamic> messagesJson = json.decode(response.body);
|
|
List<SerializableMessage> messages =
|
|
messagesJson.map((mj) => SerializableMessage.fromJson(mj)).toList();
|
|
|
|
return messages;
|
|
}
|
|
print(response.statusCode);
|
|
} catch (e) {
|
|
print("caught $e");
|
|
}
|
|
return [];
|
|
}
|
|
|
|
//returns the html for the email, it gets used in emailView
|
|
Future<String> fetchEmailContent(
|
|
List<String> IDsString, String emailFolder) async {
|
|
String content = r"""
|
|
""";
|
|
threadAttachments = [];
|
|
|
|
try {
|
|
//attaches email after email from a thread
|
|
for (var id in IDsString) {
|
|
var url = Uri.http('$ip:$port', 'email', {'id': id});
|
|
var response = await http.get(url);
|
|
currThread.add(id);
|
|
if (response.statusCode == 200) {
|
|
content += response.body;
|
|
try {
|
|
List<AttachmentInfo> attachments =
|
|
await getAttachmentsInfo(emailFolder, id);
|
|
for (var attachment in attachments) {
|
|
//TODO: for each attachment creaate at the bottom a widget for each individual one
|
|
threadAttachments
|
|
.add(await getAttachment(emailFolder, id, attachment.name));
|
|
}
|
|
} catch (innerError) {
|
|
print('_getAttachment info caught error $innerError');
|
|
}
|
|
content +=
|
|
"""<div id="JuanBedarramarker" style="width: 10px; height: 30px;"></div>""";
|
|
content += "<hr>";
|
|
}
|
|
}
|
|
} catch (e) {
|
|
print('_getEmailContent caught error: $e');
|
|
}
|
|
return content;
|
|
}
|
|
|
|
Future<List<SerializableMessage>> threadsInSerializable(
|
|
String thread_id) async {
|
|
// grab all of the emails in thread anyways, for the future it'll come in handy
|
|
var url = Uri.http('$ip:$port', 'get_thread_messages', {'id': thread_id});
|
|
try {
|
|
var response = await http.get(url);
|
|
if (response.statusCode == 200) {
|
|
List json = jsonDecode(response.body);
|
|
List<SerializableMessage> serializableMessages = [];
|
|
for (var mail in json) {
|
|
serializableMessages.add(SerializableMessage.fromJson(mail));
|
|
}
|
|
return serializableMessages;
|
|
} else {
|
|
print(
|
|
"failed get request with status code ${response.statusCode}, and body ${response.body}");
|
|
}
|
|
} catch (e) {
|
|
print("caught in threadInSerializable method error: $e");
|
|
}
|
|
|
|
return [];
|
|
}
|
|
|
|
Future<bool> moveEmail(
|
|
//only moves the first email of the thread //or perhaps should do the last
|
|
String fromFolder,
|
|
String thread_id,
|
|
String toFolder) async {
|
|
var url = Uri.http('$ip:$port', 'move_email');
|
|
|
|
List<SerializableMessage> mailsInSerializable =
|
|
await this.threadsInSerializable(thread_id);
|
|
|
|
if (mailsInSerializable.isEmpty) {
|
|
return false;
|
|
}
|
|
|
|
SerializableMessage firstMail = mailsInSerializable[0];
|
|
|
|
Map<String, String> requestBody = {
|
|
'from': fromFolder,
|
|
'uid': firstMail.uid.toString(),
|
|
'to': toFolder,
|
|
};
|
|
|
|
try {
|
|
var response = await http.post(
|
|
url,
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: jsonEncode(requestBody),
|
|
);
|
|
if (response.statusCode == 200) {
|
|
print('response body ${response.body}');
|
|
return true;
|
|
} else {
|
|
print('error ${response.statusCode} ${response.body}');
|
|
}
|
|
} catch (e) {
|
|
print("failed trying to post move_email, with error: $e");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
Future<List<String>> fetchFolders() async {
|
|
try {
|
|
var url = Uri.http('$ip:$port', 'folders');
|
|
var response = await http.get(url);
|
|
return List<String>.from(json.decode(response.body));
|
|
} catch (e) {
|
|
print('fetchFolders caught error: $e');
|
|
return [];
|
|
}
|
|
}
|
|
|
|
Future<void> createFolder(String folderName) async {
|
|
var url = Uri.http('$ip:$port', 'create_folder');
|
|
|
|
Map<String, String> requestBody = {'name': folderName};
|
|
|
|
try {
|
|
var response = await http.post(
|
|
url,
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: jsonEncode(requestBody),
|
|
);
|
|
if (response.statusCode == 200) {
|
|
print('response body: ${response.body}');
|
|
} else {
|
|
print('Error: ${response.statusCode}, response body: ${response.body}');
|
|
}
|
|
} catch (e) {
|
|
print('error making post req: $e');
|
|
}
|
|
}
|
|
|
|
Future<void> renameFolder(String oldFolder, String newFolder) async {
|
|
var url = Uri.http('$ip:$port', 'rename_folder');
|
|
Map<String, String> requestBody = {
|
|
'old_name': oldFolder,
|
|
'new_name': newFolder,
|
|
};
|
|
|
|
try {
|
|
var response = await http.post(
|
|
url,
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: jsonEncode(requestBody),
|
|
);
|
|
if (response.statusCode == 200) {
|
|
print('response body: ${response.body}');
|
|
} else {
|
|
print('Error: ${response.statusCode}, response body: ${response.body}');
|
|
}
|
|
} catch (e) {
|
|
print('error making post req: $e');
|
|
}
|
|
}
|
|
|
|
Future<void> deleteFolder(String folderName) async {
|
|
var url = Uri.http('$ip:$port', 'delete_folder');
|
|
|
|
Map<String, String> requestBody = {'name': folderName};
|
|
|
|
try {
|
|
var response = await http.post(
|
|
url,
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: jsonEncode(requestBody),
|
|
);
|
|
if (response.statusCode == 200) {
|
|
print('response body: ${response.body}');
|
|
} else {
|
|
print('Error: ${response.statusCode}, response body: ${response.body}');
|
|
}
|
|
} catch (e) {
|
|
print('error making post req: $e');
|
|
}
|
|
}
|
|
|
|
Future<bool> logIn(String json) async {
|
|
return false;
|
|
}
|
|
|
|
Future<List<AttachmentInfo>> getAttachmentsInfo(
|
|
String folder, String email_id) async {
|
|
try {
|
|
var url = Uri.http('$ip:$port', 'get_attachments_info',
|
|
{'folder': folder, 'id': email_id});
|
|
// print(url);
|
|
var response = await http.get(url);
|
|
// print("response $response");
|
|
if (response.statusCode == 200) {
|
|
var result = response.body;
|
|
// print(result);
|
|
List<dynamic> attachmentList = json.decode(result);
|
|
// Map<String, dynamic> attachmentList = json.decode(result);
|
|
|
|
// print("attachment list $attachmentList");
|
|
List<AttachmentInfo> attachments =
|
|
attachmentList.map((al) => AttachmentInfo.fromJson(al)).toList();
|
|
// print("attachments $attachments");
|
|
|
|
return attachments;
|
|
}
|
|
} catch (e) {
|
|
print(e);
|
|
}
|
|
return [];
|
|
}
|
|
|
|
Future<AttachmentResponse> getAttachment(
|
|
String folder, String email_id, String name) async {
|
|
try {
|
|
var url = Uri.http('$ip:$port', 'get_attachment',
|
|
{'folder': folder, 'id': email_id, 'name': name});
|
|
var response = await http.get(url);
|
|
if (response.statusCode == 200) {
|
|
var result = response.body;
|
|
Map<String, dynamic> attachmentData = json.decode(result);
|
|
AttachmentResponse data = AttachmentResponse.fromJson(attachmentData);
|
|
print("data $data");
|
|
return data;
|
|
}
|
|
} catch (e) {
|
|
print("getAttachment failed $e");
|
|
}
|
|
return AttachmentResponse(name: "error", data: Uint8List(0));
|
|
}
|
|
|
|
Future<List<Map<String, dynamic>>> getMarkerPosition() async {
|
|
//this is so we can put a widget right below each email, but the way how the email content is generated
|
|
//leads to problems as for a) the html is added one right after the other in one iframe, b)
|
|
// if it was multiple iframes then the scrolling to jump would not work as expected
|
|
|
|
print("marker called");
|
|
// JavaScript code embedded as a string
|
|
String jsCode = '''
|
|
(async function waitForIframeAndMarkers() {
|
|
try {
|
|
return await new Promise((resolve) => {
|
|
const interval = setInterval(() => {
|
|
console.log("⏳ Checking for iframe...");
|
|
var iframe = document.getElementsByTagName('iframe')[0];
|
|
if (iframe && iframe.contentDocument) {
|
|
console.log("✅ Iframe found!");
|
|
var iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
|
|
var markers = iframeDoc.querySelectorAll('[id^="JuanBedarramarker"]');
|
|
if (markers.length > 0) {
|
|
console.log(`✅ Found markers in the iframe.`);
|
|
var positions = [];
|
|
markers.forEach((marker) => {
|
|
var rect = marker.getBoundingClientRect();
|
|
positions.push({
|
|
id: marker.id,
|
|
x: rect.left + window.scrollX,
|
|
y: rect.top + window.scrollY,
|
|
});
|
|
});
|
|
console.log("📌 Marker positions:", positions);
|
|
clearInterval(interval);
|
|
resolve(JSON.stringify(positions)); // Ensure proper JSON string
|
|
} else {
|
|
console.log("❌ No markers found yet.");
|
|
}
|
|
} else {
|
|
console.log("❌ Iframe not found or not loaded yet.");
|
|
}
|
|
}, 200);
|
|
});
|
|
} catch (error) {
|
|
console.error("JS Error:", error);
|
|
throw error; // Propagate error to Dart
|
|
}
|
|
})();
|
|
''';
|
|
|
|
try {
|
|
// Execute the JavaScript code using eval
|
|
final result = await js.context.callMethod('eval', [jsCode]);
|
|
|
|
if (result != null && result is String) {
|
|
print("Result received: $result");
|
|
|
|
// Parse the JSON string returned by JavaScript into a Dart list of maps
|
|
final List<dynamic> parsedResult = jsonDecode(result);
|
|
var positions = List<Map<String, dynamic>>.from(parsedResult);
|
|
print("positions put on");
|
|
print(positions);
|
|
return positions;
|
|
} else {
|
|
print("result is null or not a string");
|
|
}
|
|
} catch (e, stackTrace) {
|
|
print("Error executing JavaScript: $e");
|
|
print(stackTrace);
|
|
}
|
|
|
|
return [];
|
|
}
|
|
}
|
|
|
|
class EmailView extends StatefulWidget {
|
|
final String emailContent;
|
|
final String from;
|
|
final String name;
|
|
final String to;
|
|
final String subject;
|
|
final String date;
|
|
final String id;
|
|
|
|
const EmailView({
|
|
Key? key,
|
|
required this.emailContent,
|
|
required this.from,
|
|
required this.name,
|
|
required this.to,
|
|
required this.subject,
|
|
required this.date,
|
|
required this.id,
|
|
}) : super(key: key);
|
|
@override
|
|
_EmailViewState createState() => _EmailViewState();
|
|
}
|
|
|
|
class _EmailViewState extends State<EmailView> {
|
|
//html css rendering thing
|
|
late Key iframeKey;
|
|
late String currentContent;
|
|
late String viewTypeId;
|
|
Future<List<Map<String, dynamic>>>? _markerPositionsFuture;
|
|
// TextEditingController _jumpController = TextEditingController();
|
|
final hardcodedMarkers = [
|
|
{'id': 'marker1', 'x': 50, 'y': 100},
|
|
{'id': 'marker2', 'x': 150, 'y': 200},
|
|
{'id': 'marker3', 'x': 250, 'y': 300},
|
|
];
|
|
ApiService _apiService = ApiService();
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
String currentContent = widget.emailContent;
|
|
viewTypeId = "iframe-${DateTime.now().millisecondsSinceEpoch}";
|
|
_registerViewFactory(currentContent);
|
|
_markerPositionsFuture = ApiService().getMarkerPosition();
|
|
}
|
|
|
|
void _registerViewFactory(String currentContent) {
|
|
setState(() {
|
|
viewTypeId = 'iframe-${DateTime.now().millisecondsSinceEpoch}';
|
|
ui.platformViewRegistry.registerViewFactory(
|
|
viewTypeId,
|
|
(int viewId) => html.IFrameElement()
|
|
..width = '100%'
|
|
..height = '100%'
|
|
..srcdoc = currentContent
|
|
..style.border = 'none');
|
|
});
|
|
}
|
|
|
|
void _scrollToNumber(String spanId) {
|
|
AugmentClasses.handleJump(spanId);
|
|
}
|
|
|
|
// TODO: void _invisibility(String ) //to make purple numbers not visible
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
// print("thread id ${widget.id}");
|
|
ApiService.currThreadID = widget.id;
|
|
return Scaffold(
|
|
appBar: AppBar(
|
|
title: Text(widget.name),
|
|
),
|
|
body: Stack(
|
|
children: [
|
|
Column(
|
|
children: [
|
|
EmailToolbar(
|
|
onJumpToSpan: _scrollToNumber,
|
|
onButtonPressed: () => {},
|
|
// AugmentClasses.handleJump(viewTypeId, '1');
|
|
// print("button got pressed?");
|
|
|
|
// _registerViewFactory(r"""
|
|
// <h1>Welcome to My Website</h1>
|
|
// <p>This is a simple HTML page.</p>
|
|
// <h2>What is HTML?</h2>
|
|
// <p>HTML (HyperText Markup Language) is the most basic building block of the Web. It defines the meaning and structure of web content. Other technologies besides HTML are generally used to describe a web page's appearance/presentation (CSS) or functionality/behavior (JavaScript).</p>
|
|
// <h3>Here's a simple list:</h3>
|
|
// <ul>
|
|
// <li>HTML elements are the building blocks of HTML pages</li>
|
|
// <li>HTML uses tags like <code><tag></code> to organize and format content</li>
|
|
// <li>CSS is used with HTML to style pages</li>
|
|
// </ul>
|
|
// <p>Copyright © 2023</p>
|
|
// """);
|
|
// print("change");
|
|
// widget.emailContent = r"
|
|
|
|
//
|
|
),
|
|
Row(
|
|
// title of email
|
|
children: [
|
|
Text(
|
|
widget.subject,
|
|
style: TextStyle(fontSize: 30),
|
|
),
|
|
],
|
|
),
|
|
Row(
|
|
children: [
|
|
Text(
|
|
'from ${widget.name}',
|
|
style: TextStyle(fontSize: 18),
|
|
),
|
|
Text(
|
|
'<${widget.from}>',
|
|
style: TextStyle(fontSize: 18),
|
|
),
|
|
Spacer(),
|
|
Text(
|
|
'${widget.date}',
|
|
textAlign: TextAlign.right,
|
|
)
|
|
],
|
|
),
|
|
// TODO: make a case where if one of these is the user's email it just says me :)))))
|
|
Row(
|
|
children: [
|
|
Text(
|
|
'to ${widget.to.toString()}',
|
|
style: TextStyle(fontSize: 15),
|
|
)
|
|
],
|
|
),
|
|
|
|
Expanded(
|
|
child: HtmlElementView(
|
|
key: UniqueKey(),
|
|
viewType: viewTypeId,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
|
|
// Overlay widgets dynamically based on marker positions
|
|
FutureBuilder<List<Map<String, dynamic>>>(
|
|
future: _markerPositionsFuture,
|
|
builder: (context, snapshot) {
|
|
print("FutureBuilder state: ${snapshot.connectionState}");
|
|
if (snapshot.connectionState == ConnectionState.waiting) {
|
|
return Center(child: CircularProgressIndicator());
|
|
}
|
|
if (snapshot.hasError) {
|
|
print("Error in FutureBuilder: ${snapshot.error}");
|
|
return Center(child: Text('error loading markers'));
|
|
}
|
|
if (snapshot.hasData && snapshot.data != null) {
|
|
final markers = snapshot.data!;
|
|
return Stack(
|
|
children: markers.map((marker) {
|
|
return Positioned(
|
|
left: marker['x'].toDouble(),
|
|
top: marker['y'].toDouble(),
|
|
child: GestureDetector(
|
|
onTap: () {
|
|
print('Tapped on ${marker['id']}');
|
|
},
|
|
child: Container(
|
|
width: 50,
|
|
height: 50,
|
|
color: Colors.red,
|
|
child: Center(
|
|
child: Text(
|
|
marker['id'],
|
|
style: TextStyle(color: Colors.white),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}).toList(),
|
|
);
|
|
}
|
|
|
|
return SizedBox.shrink(); // No markers found
|
|
},
|
|
),
|
|
// Red widget overlay
|
|
// Positioned(
|
|
// left: 8, // Adjust based on your desired position
|
|
// top: 100 + 44 + 5, // Adjust based on your desired position
|
|
// child: IgnorePointer(
|
|
// ignoring: true, // Ensures the iframe remains interactive
|
|
// child: Container(
|
|
// color: Colors.red,
|
|
// width: 100,
|
|
// height: 50,
|
|
// child: Center(
|
|
// child: Text(
|
|
// 'Overlay',
|
|
// style: TextStyle(color: Colors.white),
|
|
// ),
|
|
// ),
|
|
// ),
|
|
// ),
|
|
// ),
|
|
],
|
|
));
|
|
}
|
|
}
|