// 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 'collapsableEmails.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; import 'package:web/web.dart' as web; import 'dart:js_interop' as js; 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 []; // } } class EmailView extends StatefulWidget { final List emailContent; final String from; final String name; final String to; final String subject; final String date; final String id; final List messages; 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, required this.messages, }) : super(key: key); @override _EmailViewState createState() => _EmailViewState(); } class _EmailViewState extends State { //html css rendering thing late Key iframeKey; late String currentContent; late String viewTypeId; //make this a list too??? Future>>? _markerPositionsFuture; // TextEditingController _jumpController = TextEditingController(); final hardcodedMarkers = [ {'id': 'marker1', 'x': 50, 'y': 100}, {'id': 'marker2', 'x': 150, 'y': 200}, {'id': 'marker3', 'x': 250, 'y': 300}, ]; @override void initState() { super.initState(); print("thread id? ${widget.id}"); List currentContent = widget .emailContent; //html of the email/ actually entire thread, gives me little space to play in between // i wonder if the other attributes change? because if so i have to add like some zooms in and out of the emails, as in collapse // _registerViewFactory(currentContent); } // void _registerViewFactory(List currentContent) { // i think this doesnt work anymore // setState(() { //update to do item per item // // each item to have itsviewtype ID // // is this necessarey here?? // //could just move to collapsable // viewTypeId = 'iframe-${DateTime.now().millisecondsSinceEpoch}'; // final emailHTML = web.document.createElement('div') as web.HTMLDivElement // ..id = viewTypeId // ..innerHTML = currentContent[0].toJS; // temporarily index because it has to do all of them // emailHTML.style // ..width = '100%' // ..height = '100%' // ..overflow = 'auto' // ..scrollBehavior = 'smooth'; // ui.platformViewRegistry.registerViewFactory( // viewTypeId, // (int viewId) => emailHTML, // ); // }); // } 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""" //

Welcome to My Website

//

This is a simple HTML page.

//

What is HTML?

//

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).

//

Here's a simple list:

//
    //
  • HTML elements are the building blocks of HTML pages
  • //
  • HTML uses tags like <tag> to organize and format content
  • //
  • CSS is used with HTML to style pages
  • //
//

Copyright © 2023

// """); // 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: CollapsableEmails( //change here thread: widget.messages, //this wont work in serializable threadHTML: widget.emailContent, threadIDs: widget.id, ), ), // Expanded( // child: HtmlElementView( // key: UniqueKey(), // viewType: viewTypeId, // ), // ), ], ), // Overlay widgets dynamically based on marker positions // FutureBuilder>>( // 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), // ), // ), // ), // ), // ), ], )); } }