// 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 'structs.dart'; import 'package:http/http.dart' as http; import 'dart:convert'; class ApiService { static String ip = ""; static String port = ""; static List threadAttachments = []; //holds attachments of the thread static String currFolder = ""; static List currThread = []; //holds the email ids of the thread static String currThreadID = ""; //picked an email it prints the threadID Future> 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 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 threadIDs = List.from(item[1]); for (var threadId in threadIDs) { await fetchThreads(threadId, allEmails); } } } currFolder = folder; return allEmails; } else { throw Exception('Failed to load threads'); } } catch (e) { print('_displayEmailsFromFolder caught error: $e'); return []; } } Future fetchThreads( //populates allEmails, which is the List that contains all the emails in a thread int threadId, List 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 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> 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 messagesJson = json.decode(response.body); List 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> fetchEmailContent( List IDsString, String emailFolder) async { String content = r""" """; List HTMLofThread = []; threadAttachments = []; int counter = 0; 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) { counter += 1; content += response.body; HTMLofThread.add(response.body); try { List 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 += """
"""; content += "

end of email

"; } } } catch (e) { print('_getEmailContent caught error: $e'); } // return content; return HTMLofThread; } Future> threadsInSerializable( String thread_id) async { //actually a xyzwtv@gmail.com // grab all of the emails in thread anyways, for the future it'll come in handy // maybe not 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 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 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 mailsInSerializable = await this.threadsInSerializable(thread_id); if (mailsInSerializable.isEmpty) { return false; } SerializableMessage firstMail = mailsInSerializable[0]; Map 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> fetchFolders() async { try { var url = Uri.http('$ip:$port', 'folders'); var response = await http.get(url); return List.from(json.decode(response.body)); } catch (e) { print('fetchFolders caught error: $e'); return []; } } Future createFolder(String folderName) async { var url = Uri.http('$ip:$port', 'create_folder'); Map 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 renameFolder(String oldFolder, String newFolder) async { var url = Uri.http('$ip:$port', 'rename_folder'); Map 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 deleteFolder(String folderName) async { var url = Uri.http('$ip:$port', 'delete_folder'); Map 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 logIn(String json) async { return false; } Future> 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 attachmentList = json.decode(result); // Map attachmentList = json.decode(result); // print("attachment list $attachmentList"); List attachments = attachmentList.map((al) => AttachmentInfo.fromJson(al)).toList(); // print("attachments $attachments"); return attachments; } } catch (e) { print(e); } return []; } Future 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 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)); } //TODO: MOVE THIS INTO WEB // Future>> 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 parsedResult = jsonDecode(result); // var positions = List>.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 []; // } }