From fb9f7135ddfbc92f472738b98f06371bd930c340 Mon Sep 17 00:00:00 2001 From: juan Date: Tue, 18 Feb 2025 16:38:22 -0500 Subject: [PATCH 01/15] structs for getAttachment --- lib/structs.dart | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/lib/structs.dart b/lib/structs.dart index 6eb1c17..b55beb9 100644 --- a/lib/structs.dart +++ b/lib/structs.dart @@ -1,5 +1,7 @@ //data structures +import 'dart:typed_data'; + class GetThreadResponse { final int id; final List messages; @@ -76,7 +78,7 @@ class SerializableMessage { required this.subject, required this.date, required this.uid, - required this.list, + required this.list, //email list??? required this.id, required this.in_reply_to, }); @@ -118,3 +120,29 @@ class AttachmentInfo { ); } } + +class AttachmentInfoList extends Iterable { + final List _attachments; + + AttachmentInfoList(this._attachments); + + factory AttachmentInfoList.fromJsonList(List> jsonList) { + return AttachmentInfoList(jsonList.map((json) => AttachmentInfo.fromJson(json)).toList()); + } + + @override + Iterator get iterator => _attachments.iterator; + + @override + String toString() => _attachments.toString(); +} + +class AttachmentResponse { + final name; + final Uint8List data; + AttachmentResponse({required this.name, required this.data}); + + factory AttachmentResponse.fromJson(Map json) { + return AttachmentResponse(name: json["name"], data: json["data"],); + } +} From 75e2505eb3da560634dcc57f4557802fdbe359aa Mon Sep 17 00:00:00 2001 From: juan Date: Sat, 22 Feb 2025 00:15:59 -0500 Subject: [PATCH 02/15] fixed typing issue for the attachmentResponse --- lib/structs.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/structs.dart b/lib/structs.dart index b55beb9..c7e9da4 100644 --- a/lib/structs.dart +++ b/lib/structs.dart @@ -143,6 +143,6 @@ class AttachmentResponse { AttachmentResponse({required this.name, required this.data}); factory AttachmentResponse.fromJson(Map json) { - return AttachmentResponse(name: json["name"], data: json["data"],); + return AttachmentResponse(name: json["name"], data: Uint8List.fromList(List.from(json["data"]))); } } From b2fb70b3d640c35d2677ebd41374c7bb583fc188 Mon Sep 17 00:00:00 2001 From: juan Date: Sat, 22 Feb 2025 01:23:32 -0500 Subject: [PATCH 03/15] attachment showing through the augment menu --- lib/api_service.dart | 340 +++++++++++++++++++++++++++++++++---------- lib/augment.dart | 90 +++++++++++- lib/email.dart | 21 ++- lib/home_page.dart | 11 +- pubspec.yaml | 1 + 5 files changed, 371 insertions(+), 92 deletions(-) diff --git a/lib/api_service.dart b/lib/api_service.dart index 6c71127..935a77e 100644 --- a/lib/api_service.dart +++ b/lib/api_service.dart @@ -1,20 +1,29 @@ // this file should handle most of the API calls // it also builds some widgets, but it will be modulated later -import 'package:crab_ui/structs.dart'; +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 threadAttachments = []; + static String currFolder = ""; + static List currThread = []; + Future> fetchEmailsFromFolder( String folder, int pagenitaion) async { - // print(ip + " " + port); try { var url = Uri.http('$ip:$port', 'sorted_threads_by_date', { 'folder': folder, @@ -51,8 +60,8 @@ class ApiService { int threadId, List allEmails) async { try { - var url = - Uri.http('$ip:$port', 'get_thread', {'id': threadId.toString()}); + var url = Uri.http('${ApiService.ip}:${ApiService.port}', 'get_thread', + {'id': threadId.toString()}); var response = await http.get(url); if (response.statusCode == 200) { @@ -97,24 +106,34 @@ class ApiService { return []; } - Future fetchEmailContent(List IDs) async { + //returns the html for the email, it gets used in emailView + Future fetchEmailContent( + List IDsString, String emailFolder) async { String content = r""" """; + threadAttachments = []; try { //attaches email after email from a thread - for (var id in IDs) { + 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 { - getAttachmentsInfo("INBOX", id); + 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 += "
"; } } @@ -200,18 +219,21 @@ class ApiService { Future> getAttachmentsInfo( String folder, String email_id) async { try { - var url = Uri.http('127.0.0.1:3001', 'get_attachments_info', - {'folder': folder, 'email_id': email_id}); - print(url); + 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"); + // print("response $response"); if (response.statusCode == 200) { var result = response.body; + // print(result); List attachmentList = json.decode(result); - print("attachment list $attachmentList"); + // Map attachmentList = json.decode(result); + + // print("attachment list $attachmentList"); List attachments = attachmentList.map((al) => AttachmentInfo.fromJson(al)).toList(); - print("attachments $attachments"); + // print("attachments $attachments"); return attachments; } @@ -220,6 +242,93 @@ class ApiService { } 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; + // print(result); + 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)); + } + + Future>> getMarkerPosition() async { + print("maerker 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 { @@ -246,10 +355,18 @@ class EmailView extends StatefulWidget { } class _EmailViewState extends State { + //html css rendering thing late Key iframeKey; late String currentContent; late String viewTypeId; + 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}, + ]; + ApiService _apiService = ApiService(); @override void initState() { @@ -257,6 +374,7 @@ class _EmailViewState extends State { String currentContent = widget.emailContent; viewTypeId = "iframe-${DateTime.now().millisecondsSinceEpoch}"; _registerViewFactory(currentContent); + _markerPositionsFuture = ApiService().getMarkerPosition(); } void _registerViewFactory(String currentContent) { @@ -276,7 +394,7 @@ class _EmailViewState extends State { AugmentClasses.handleJump(spanId); } - // TODO: void _invisibility(String ) + // TODO: void _invisibility(String ) //to make purple numbers not visible @override Widget build(BuildContext context) { @@ -285,75 +403,141 @@ class _EmailViewState extends State { appBar: AppBar( title: Text(widget.name), ), - body: Column( + body: Stack( 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 + Column( children: [ - Text( - widget.subject, - style: TextStyle(fontSize: 30), + 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: HtmlElementView( + key: UniqueKey(), + viewType: viewTypeId, + ), ), ], ), - 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>>( + 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), + // ), + // ), + // ), + // ), + // ), ], )); } diff --git a/lib/augment.dart b/lib/augment.dart index 7b2e566..96922f0 100644 --- a/lib/augment.dart +++ b/lib/augment.dart @@ -1,3 +1,5 @@ +import 'package:crab_ui/api_service.dart'; +import 'package:crab_ui/structs.dart'; import 'package:flutter/material.dart'; import 'dart:html' as html; import 'dart:js' as js; @@ -64,8 +66,8 @@ class _DynamicClassesAugment extends State { child: Text('Reload'), ), ElevatedButton( - onPressed: AugmentClasses.handleImages, - child: Text('Images'), + onPressed: () => AugmentClasses.handleImages(context), + child: Text('Attachments'), ), SizedBox(width: 8), ElevatedButton( @@ -206,6 +208,8 @@ class _DynamicClassesAugment extends State { } class AugmentClasses { + ApiService _apiService = ApiService(); + static OverlayEntry? _overlayEntry; static void handleHome(BuildContext context) { Navigator.of(context).popUntil((route) => route.isFirst); } @@ -214,8 +218,85 @@ class AugmentClasses { print("reload"); } - static void handleImages() { + static void handleImages(BuildContext context) { print("Images button pressed"); + final overlay = Overlay.of(context); + final renderBox = context.findRenderObject() as RenderBox; + final offset = renderBox.localToGlobal(Offset.zero); + + _overlayEntry = OverlayEntry( + builder: (context) => Stack( + children: [ + // Dimmed background + GestureDetector( + onTap: () => _overlayEntry?.remove(), + child: Container( + color: Colors.black54, + width: MediaQuery.of(context).size.width, + height: MediaQuery.of(context).size.height, + ), + ), + // Focused content window + Center( + child: Positioned( + left: offset.dx + 500, + top: offset.dy + renderBox.size.height + 100, + child: Material( + elevation: 8, + borderRadius: BorderRadius.circular(12), + child: ConstrainedBox( + constraints: const BoxConstraints( + maxWidth: 400, + maxHeight: 500, + ), + child: Column( + children: [ + _buildHeader(), + const Divider(height: 1), + Expanded( + child: ListView( + children: _buildMenuItem(), + ), + ), + ], + ), + ), + ), + ), + ) + ], + ), + ); + if (_overlayEntry != null) { + overlay.insert(_overlayEntry!); + } + } + + // Add missing widget builder methods + static Widget _buildHeader() { + return const Padding( + padding: EdgeInsets.all(16.0), + child: Text( + 'Thread Attachments', + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + ), + ), + ); + } + + static List _buildMenuItem() { + List listOfFiles = []; + for (AttachmentResponse file in ApiService.threadAttachments) { + listOfFiles.add(ListTile( + leading: Icon(Icons.file_present), + title: Text(file.name.toString()), + onTap: () { + _overlayEntry?.remove(); + })); + } + return listOfFiles; } static void handleOpen() { @@ -485,7 +566,7 @@ class AugmentClasses { void handleFilter() {} static Future FilterButton(context) async { //this is literally ctrl+F :skull: - //idea is to search in file, extract the

tags that contain these + //idea is to search in file, extract the

tags that contain these //words and highlight, then when zoom, you just jump to that paragraph AugmentClasses.disableIframePointerEvents(); @@ -517,6 +598,7 @@ class AugmentClasses { } static void disableIframePointerEvents() { + //pretty sure these dont work final iframes = html.document.getElementsByTagName('iframe'); for (var iframe in iframes) { if (iframe is html.Element) { diff --git a/lib/email.dart b/lib/email.dart index 8c58daf..e289526 100644 --- a/lib/email.dart +++ b/lib/email.dart @@ -4,10 +4,12 @@ import 'structs.dart'; class EmailListScreen extends StatelessWidget { final List emails; - final Future Function(List) getEmailContent; + final Future Function(List, String) getEmailContent; + final String folder; - EmailListScreen({required this.emails, required this.getEmailContent}); + EmailListScreen({required this.emails, required this.getEmailContent, required this.folder}); +//fix the email list @override Widget build(BuildContext context) { return Scaffold( @@ -24,7 +26,7 @@ class EmailListScreen extends StatelessWidget { ), trailing: Text(email.date.toString()), onTap: () async { - String emailContent = await getEmailContent(email.messages); + String emailContent = await getEmailContent(email.messages, folder); Navigator.push( context, MaterialPageRoute( @@ -51,7 +53,7 @@ class EmailListScreen extends StatelessWidget { // ignore: must_be_immutable class EmailPage extends StatefulWidget { EmailPage({Key? key}) : super(key: key); - String selectedFolder = "INBOX"; + String selectedFolder = "INBOX"; //starter int offset = 0; int page = 1; @@ -62,11 +64,14 @@ class EmailPage extends StatefulWidget { class EmailPageState extends State { final ApiService apiService = ApiService(); List emails = []; + int page = 1; + bool isBackDisabled = false; @override void initState() { super.initState(); - widget.page = widget.page; + widget.page = page; + isBackDisabled = true; } void updateSelectedFolder(String folder) { @@ -86,11 +91,16 @@ class EmailPageState extends State { setState(() { widget.offset += 50; widget.page += 1; + isBackDisabled = false; }); } else if (option == "back") { setState(() { widget.offset -= 50; widget.page -= 1; + if (widget.page == 1) { + isBackDisabled = true; + print("back dis"); + } }); } // print(currentPage); @@ -119,6 +129,7 @@ class EmailPageState extends State { body: EmailListScreen( emails: emails, getEmailContent: apiService.fetchEmailContent, + folder: widget.selectedFolder,//try to grab from it directly ), ); } diff --git a/lib/home_page.dart b/lib/home_page.dart index f9c9408..5badb9f 100644 --- a/lib/home_page.dart +++ b/lib/home_page.dart @@ -1,9 +1,10 @@ -import 'package:crab_ui/folder_drawer.dart'; -import 'package:crab_ui/structs.dart'; +import 'folder_drawer.dart'; +import 'structs.dart'; import 'package:flutter/widgets.dart'; import 'api_service.dart'; import 'package:flutter/material.dart'; import 'email.dart'; +// import 'package:shared_preferences/shared_preferences.dart'; // import 'serialize.dart'; class HomeScreen extends StatefulWidget { @@ -119,7 +120,7 @@ class _HomeScreenState extends State with TickerProviderStateMixin { body: ListView.separated( itemCount: result.length, itemBuilder: (context, index) { - final email = result[index]; + final SerializableMessage email = result[index]; return ListTile( title: Text(email.from, style: TextStyle(fontWeight: FontWeight.bold)), @@ -131,7 +132,7 @@ class _HomeScreenState extends State with TickerProviderStateMixin { onTap: () async { // print('tapped'); String emailContent = - await apiService.fetchEmailContent([email.id]); + await apiService.fetchEmailContent([email.id], email.list); // print('content below'); // print(emailContent); Navigator.push( @@ -341,7 +342,7 @@ class _HomeScreenState extends State with TickerProviderStateMixin { children: [ ElevatedButton( onPressed: () { - _emailPageKey.currentState + _emailPageKey.currentState!.isBackDisabled ? null: _emailPageKey.currentState ?.updatePagenation('back'); }, child: Icon(Icons.navigate_before), diff --git a/pubspec.yaml b/pubspec.yaml index de4abb5..7c597a7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -18,6 +18,7 @@ dependencies: encrypt: ^5.0.0 pointycastle: ^3.4.0 mime: ^1.0.3 + pointer_interceptor: ^0.10.1+2 english_words: ^4.0.0 provider: ^6.0.0 From 5baf1d501e1c25681701daecf888fee308d288f4 Mon Sep 17 00:00:00 2001 From: juan Date: Sat, 22 Feb 2025 01:24:38 -0500 Subject: [PATCH 04/15] changed the parameters of these functions --- lib/login.dart | 12 +++++++++--- lib/serialize.dart | 2 +- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/lib/login.dart b/lib/login.dart index c1d8a08..ca2c3f9 100644 --- a/lib/login.dart +++ b/lib/login.dart @@ -1,6 +1,7 @@ import 'dart:convert'; -import 'package:crab_ui/api_service.dart'; -import 'package:crab_ui/home_page.dart'; +// import 'package:crab_ui/api_service.dart'; +import 'api_service.dart'; +import 'home_page.dart'; import 'package:flutter/material.dart'; // import 'package:flutter_widget_from_html/flutter_widget_from_html.dart'; import 'package:http/http.dart' as http; @@ -11,7 +12,9 @@ class AuthService { Future isUserLoggedIn() async { try { final response = - await http.get(Uri.parse('http://localhost:6823/read-config')); + await http.get(Uri.http('localhost:6823', 'read-config')); + // await http.get(Uri.parse('http://localhost:6823/read-config')); + print(response.statusCode); print(response.body); if (response.statusCode == 200) { @@ -27,7 +30,9 @@ class AuthService { Map json = jsonDecode(jsonOuter); // print(json["is_logged_in"]); ApiService.ip = data['ip']; + print("setting ip " + ApiService.ip); ApiService.port = data['port']; + print("setting port " + data['port']); return json["is_logged_in"]; } } catch (er) { @@ -35,6 +40,7 @@ class AuthService { } } } catch (e) { + print('caughtther'); print(e); } return false; diff --git a/lib/serialize.dart b/lib/serialize.dart index 90719a8..4ba21f1 100644 --- a/lib/serialize.dart +++ b/lib/serialize.dart @@ -61,7 +61,7 @@ class _SerializableMessageListScreenState extends State Date: Tue, 4 Mar 2025 22:15:43 -0500 Subject: [PATCH 05/15] fixed Focusing problem, now the attachment box is clickable --- lib/augment.dart | 89 ++++++++++++++++++++++++++---------------------- 1 file changed, 48 insertions(+), 41 deletions(-) diff --git a/lib/augment.dart b/lib/augment.dart index 96922f0..fe4de59 100644 --- a/lib/augment.dart +++ b/lib/augment.dart @@ -1,8 +1,10 @@ import 'package:crab_ui/api_service.dart'; import 'package:crab_ui/structs.dart'; import 'package:flutter/material.dart'; +import 'package:pointer_interceptor/pointer_interceptor.dart'; import 'dart:html' as html; import 'dart:js' as js; +import 'package:pointer_interceptor/pointer_interceptor.dart'; class EmailToolbar extends StatefulWidget { final Function(String) onJumpToSpan; @@ -228,42 +230,37 @@ class AugmentClasses { builder: (context) => Stack( children: [ // Dimmed background - GestureDetector( - onTap: () => _overlayEntry?.remove(), - child: Container( - color: Colors.black54, - width: MediaQuery.of(context).size.width, - height: MediaQuery.of(context).size.height, - ), + Container( + color: Colors.black54, + width: MediaQuery.of(context).size.width, + height: MediaQuery.of(context).size.height, ), // Focused content window - Center( - child: Positioned( - left: offset.dx + 500, - top: offset.dy + renderBox.size.height + 100, - child: Material( - elevation: 8, - borderRadius: BorderRadius.circular(12), - child: ConstrainedBox( - constraints: const BoxConstraints( - maxWidth: 400, - maxHeight: 500, - ), - child: Column( - children: [ - _buildHeader(), - const Divider(height: 1), - Expanded( - child: ListView( - children: _buildMenuItem(), + PointerInterceptor( + child: Center( + child: Material( + elevation: 8, + borderRadius: BorderRadius.circular(12), + child: ConstrainedBox( + constraints: const BoxConstraints( + maxWidth: 400, + maxHeight: 500, + ), + child: Column( + children: [ + _buildHeader(context), + const Divider(height: 1), + Expanded( + child: ListView( + children: _buildMenuItem(), + ), ), - ), - ], + ], + ), ), ), ), ), - ) ], ), ); @@ -273,17 +270,25 @@ class AugmentClasses { } // Add missing widget builder methods - static Widget _buildHeader() { - return const Padding( - padding: EdgeInsets.all(16.0), - child: Text( - 'Thread Attachments', - style: TextStyle( - fontSize: 20, - fontWeight: FontWeight.bold, - ), - ), - ); + static Widget _buildHeader(BuildContext context) { + return Padding( + padding: EdgeInsets.all(16.0), + child: + Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ + Text( + 'Thread Attachments', + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + ), + ), + CloseButton( + onPressed: () { + _overlayEntry?.remove(); + }, + ), + ] + )); } static List _buildMenuItem() { @@ -293,7 +298,9 @@ class AugmentClasses { leading: Icon(Icons.file_present), title: Text(file.name.toString()), onTap: () { - _overlayEntry?.remove(); + print("rick rolled"); + html.window + .open("https://www.youtube.com/watch?v=xvFZjo5PgG0", "testing"); })); } return listOfFiles; From 7f77f2e01b3f002e0060aacb48f09d8171b008dd Mon Sep 17 00:00:00 2001 From: juan Date: Sun, 23 Mar 2025 14:21:39 -0400 Subject: [PATCH 06/15] comment on functionality --- lib/api_service.dart | 1093 +++++++++++++++++++++--------------------- 1 file changed, 549 insertions(+), 544 deletions(-) diff --git a/lib/api_service.dart b/lib/api_service.dart index 935a77e..1777c9c 100644 --- a/lib/api_service.dart +++ b/lib/api_service.dart @@ -1,544 +1,549 @@ -// 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 threadAttachments = []; - static String currFolder = ""; - static List currThread = []; - - 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); - // print(response); - 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); - } - } - } - 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""" - """; - 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 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 += "
"; - } - } - } catch (e) { - print('_getEmailContent caught error: $e'); - } - return content; - } - - // void _addMailBox async(BuildContext context){ - // //add email folder - // showDialog(context: context, builder: builder) - // } - - 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 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 { - // var url = Uri.https('') - // try{ - // String response = await http.post( - // url - // ); - // } - - 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; - // print(result); - 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)); - } - - Future>> getMarkerPosition() async { - print("maerker 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 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 { - //html css rendering thing - late Key iframeKey; - late String currentContent; - late String viewTypeId; - 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}, - ]; - 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(currentContent); - 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: 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), - // ), - // ), - // ), - // ), - // ), - ], - )); - } -} +// 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 threadAttachments = []; + static String currFolder = ""; + static List currThread = []; + + 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); + // print(response); + 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); + } + } + } + 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""" + """; + 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 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 += "
"; + } + } + } catch (e) { + print('_getEmailContent caught error: $e'); + } + return content; + } + + // void _addMailBox async(BuildContext context){ + // //add email folder + // showDialog(context: context, builder: builder) + // } + + 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 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 { + // var url = Uri.https('') + // try{ + // String response = await http.post( + // url + // ); + // } + + 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; + // print(result); + 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)); + } + + 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 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 { + //html css rendering thing + late Key iframeKey; + late String currentContent; + late String viewTypeId; + 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}, + ]; + 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(currentContent); + 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: 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), + // ), + // ), + // ), + // ), + // ), + ], + )); + } +} From 9ea012f7adf48a54de75e52dbd8d7b7bf4143cf8 Mon Sep 17 00:00:00 2001 From: juan Date: Tue, 25 Mar 2025 02:38:13 -0400 Subject: [PATCH 07/15] support for images works --- lib/attachmentWidget.dart | 47 ++++++++++++++++++++++++++ lib/augment.dart | 70 ++++++++++++++++++++++----------------- 2 files changed, 87 insertions(+), 30 deletions(-) create mode 100644 lib/attachmentWidget.dart diff --git a/lib/attachmentWidget.dart b/lib/attachmentWidget.dart new file mode 100644 index 0000000..1239f2a --- /dev/null +++ b/lib/attachmentWidget.dart @@ -0,0 +1,47 @@ +import "package:crab_ui/structs.dart"; +import "package:flutter/material.dart"; + +class AttachmentWidget extends StatelessWidget { + final AttachmentResponse attachment; + AttachmentWidget({required this.attachment}); + + @override + Widget build(BuildContext context) { + return Stack( + // appBar: AppBar(title: Text('New Tab Content')), + children: [ + Padding( + padding: EdgeInsets.fromLTRB(10, 20, 0, 0), + child: Row( + children: [ + CloseButton( + onPressed: () => { + Navigator.pop(context) + } + ), + Text( + attachment.name.toString(), //its alr a string but incase ¯\(ツ)/¯ + style: TextStyle(color: Colors.black, fontSize: 20), //TODO: personalize your fonts + ), + ], + ), + ), + Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox( + width: 1000, + height: 600, + child: Image.memory(attachment.data), + ) + ], + ), + ), + ], + ); + } +} + + + diff --git a/lib/augment.dart b/lib/augment.dart index fe4de59..514bc44 100644 --- a/lib/augment.dart +++ b/lib/augment.dart @@ -5,6 +5,7 @@ import 'package:pointer_interceptor/pointer_interceptor.dart'; import 'dart:html' as html; import 'dart:js' as js; import 'package:pointer_interceptor/pointer_interceptor.dart'; +import 'attachmentWidget.dart'; class EmailToolbar extends StatefulWidget { final Function(String) onJumpToSpan; @@ -238,29 +239,29 @@ class AugmentClasses { // Focused content window PointerInterceptor( child: Center( - child: Material( - elevation: 8, - borderRadius: BorderRadius.circular(12), - child: ConstrainedBox( - constraints: const BoxConstraints( - maxWidth: 400, - maxHeight: 500, - ), - child: Column( - children: [ - _buildHeader(context), - const Divider(height: 1), - Expanded( - child: ListView( - children: _buildMenuItem(), - ), + child: Material( + elevation: 8, + borderRadius: BorderRadius.circular(12), + child: ConstrainedBox( + constraints: const BoxConstraints( + maxWidth: 400, + maxHeight: 500, + ), + child: Column( + children: [ + _buildHeader(context), + const Divider(height: 1), + Expanded( + child: ListView( + children: _buildMenuItem(context), ), - ], - ), + ), + ], ), ), ), ), + ), ], ), ); @@ -275,32 +276,41 @@ class AugmentClasses { padding: EdgeInsets.all(16.0), child: Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text( - 'Thread Attachments', - style: TextStyle( - fontSize: 20, - fontWeight: FontWeight.bold, - ), + Text( + 'Thread Attachments', + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, ), + ), CloseButton( onPressed: () { _overlayEntry?.remove(); }, ), - ] - )); + ])); } - static List _buildMenuItem() { + static List _buildMenuItem(BuildContext context) { List listOfFiles = []; for (AttachmentResponse file in ApiService.threadAttachments) { listOfFiles.add(ListTile( leading: Icon(Icons.file_present), title: Text(file.name.toString()), onTap: () { - print("rick rolled"); - html.window - .open("https://www.youtube.com/watch?v=xvFZjo5PgG0", "testing"); + _overlayEntry?.remove(); + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => AttachmentWidget(attachment: file))); + + // openAtta + + // Image attachment = Image.memory(file.data); + + // print("rick rolled"); + // html.window + // .open("https://www.youtube.com/watch?v=xvFZjo5PgG0", "testing"); })); } return listOfFiles; From d58d9bdf838646b1bfa12dda4d7a73c9e6e72fb1 Mon Sep 17 00:00:00 2001 From: juan Date: Sun, 30 Mar 2025 14:45:52 -0400 Subject: [PATCH 08/15] removed the underlying yellow line in the attachment name --- lib/attachmentWidget.dart | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/attachmentWidget.dart b/lib/attachmentWidget.dart index 1239f2a..cb2edb8 100644 --- a/lib/attachmentWidget.dart +++ b/lib/attachmentWidget.dart @@ -15,13 +15,17 @@ class AttachmentWidget extends StatelessWidget { child: Row( children: [ CloseButton( - onPressed: () => { + onPressed: () => { Navigator.pop(context) } ), Text( attachment.name.toString(), //its alr a string but incase ¯\(ツ)/¯ - style: TextStyle(color: Colors.black, fontSize: 20), //TODO: personalize your fonts + style: TextStyle( + color: Colors.black, + fontSize: 20, + decoration: TextDecoration.none + ), //TODO: personalize your fonts ), ], ), From b200b1e443745aec1f6e67dc437464980e00ec47 Mon Sep 17 00:00:00 2001 From: juan Date: Sun, 30 Mar 2025 15:03:28 -0400 Subject: [PATCH 09/15] made it better looking, the text bar --- lib/attachmentWidget.dart | 74 ++++++++++++++++++++++----------------- 1 file changed, 41 insertions(+), 33 deletions(-) diff --git a/lib/attachmentWidget.dart b/lib/attachmentWidget.dart index cb2edb8..c3fcf57 100644 --- a/lib/attachmentWidget.dart +++ b/lib/attachmentWidget.dart @@ -7,42 +7,50 @@ class AttachmentWidget extends StatelessWidget { @override Widget build(BuildContext context) { - return Stack( - // appBar: AppBar(title: Text('New Tab Content')), - children: [ - Padding( - padding: EdgeInsets.fromLTRB(10, 20, 0, 0), - child: Row( - children: [ - CloseButton( - onPressed: () => { - Navigator.pop(context) - } + return Container( + color: Colors.black38, + child: Stack( + // appBar: AppBar(title: Text('New Tab Content')), + children: [ + + Container( + color: Colors.white, + child: Padding( + padding: EdgeInsets.fromLTRB(10, 20, 0, 10), + child: Row( + + children: [ + CloseButton( + onPressed: () => { + Navigator.pop(context) + } + ), + Text( + attachment.name.toString(), //its alr a string but incase ¯\(ツ)/¯ + style: TextStyle( + color: Colors.black, + fontSize: 20, + decoration: TextDecoration.none + ), //TODO: personalize your fonts + ), + ], ), - Text( - attachment.name.toString(), //its alr a string but incase ¯\(ツ)/¯ - style: TextStyle( - color: Colors.black, - fontSize: 20, - decoration: TextDecoration.none - ), //TODO: personalize your fonts - ), - ], + ), ), - ), - Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - SizedBox( - width: 1000, - height: 600, - child: Image.memory(attachment.data), - ) - ], + Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox( + width: 1000, + height: 600, + child: Image.memory(attachment.data), + ) + ], + ), ), - ), - ], + ], + ), ); } } From f132ae69c1f298e9d59d9c0021c45ce1bc47ca1b Mon Sep 17 00:00:00 2001 From: juan Date: Wed, 23 Apr 2025 15:54:56 -0400 Subject: [PATCH 10/15] class that downloads the attachments --- lib/attachmentDownload.dart | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 lib/attachmentDownload.dart diff --git a/lib/attachmentDownload.dart b/lib/attachmentDownload.dart new file mode 100644 index 0000000..4fc4639 --- /dev/null +++ b/lib/attachmentDownload.dart @@ -0,0 +1,14 @@ +import 'dart:html' as html; +import 'dart:io'; +import 'structs.dart'; +import 'package:file_saver/file_saver.dart'; + +class Attachmentdownload { + Future saveFile(AttachmentResponse attachment) async { + await FileSaver.instance.saveFile( + name: attachment.name.toString().substring(0, attachment.name.toString().lastIndexOf('.')), + bytes: attachment.data, + ext: attachment.name.toString().substring(attachment.name.toString().lastIndexOf('.')+1) + ); + } +} From b98b8e7bc466e4e2830fdcdf9de63725a2801bb7 Mon Sep 17 00:00:00 2001 From: juan Date: Wed, 23 Apr 2025 15:55:30 -0400 Subject: [PATCH 11/15] added download functionality --- lib/augment.dart | 15 ++++----- pubspec.yaml | 85 ++++++++++++++++++++++++------------------------ 2 files changed, 50 insertions(+), 50 deletions(-) diff --git a/lib/augment.dart b/lib/augment.dart index 514bc44..b75226f 100644 --- a/lib/augment.dart +++ b/lib/augment.dart @@ -1,4 +1,5 @@ import 'package:crab_ui/api_service.dart'; +import 'package:crab_ui/attachmentDownload.dart'; import 'package:crab_ui/structs.dart'; import 'package:flutter/material.dart'; import 'package:pointer_interceptor/pointer_interceptor.dart'; @@ -294,9 +295,14 @@ class AugmentClasses { static List _buildMenuItem(BuildContext context) { List listOfFiles = []; for (AttachmentResponse file in ApiService.threadAttachments) { - listOfFiles.add(ListTile( + listOfFiles.add( + ListTile ( leading: Icon(Icons.file_present), title: Text(file.name.toString()), + trailing: GestureDetector( + child: Icon(Icons.download), + onTap: () => Attachmentdownload().saveFile(file), + ), onTap: () { _overlayEntry?.remove(); Navigator.push( @@ -304,13 +310,6 @@ class AugmentClasses { MaterialPageRoute( builder: (context) => AttachmentWidget(attachment: file))); - // openAtta - - // Image attachment = Image.memory(file.data); - - // print("rick rolled"); - // html.window - // .open("https://www.youtube.com/watch?v=xvFZjo5PgG0", "testing"); })); } return listOfFiles; diff --git a/pubspec.yaml b/pubspec.yaml index 7c597a7..2608eeb 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,43 +1,44 @@ -name: crab_ui -description: A new Flutter project. - -publish_to: 'none' # Remove this line if you wish to publish to pub.dev - -version: 0.0.1+1 - -environment: - sdk: ^3.1.1 - -dependencies: - flutter: - sdk: flutter - http: 1.2.2 - flutter_html_all: 3.0.0-beta.2 - flutter_widget_from_html: ^0.10.0 - shared_preferences: ^2.0.6 - encrypt: ^5.0.0 - pointycastle: ^3.4.0 - mime: ^1.0.3 - pointer_interceptor: ^0.10.1+2 - - english_words: ^4.0.0 - provider: ^6.0.0 - intl: ^0.19.0 - -dev_dependencies: - flutter_test: - sdk: flutter - - flutter_lints: ^2.0.0 - -dependency_overrides: - flutter_layout_grid: 2.0.7 - flutter_math_fork: 0.7.2 - -flutter: - uses-material-design: true - assets: - - assets/back.png - - assets/communications.png - - assets/contact-book.png +name: crab_ui +description: A new Flutter project. + +publish_to: 'none' # Remove this line if you wish to publish to pub.dev + +version: 0.0.1+1 + +environment: + sdk: ^3.1.1 + +dependencies: + flutter: + sdk: flutter + http: 1.2.2 + flutter_html_all: 3.0.0-beta.2 + flutter_widget_from_html: ^0.10.0 + shared_preferences: ^2.0.6 + encrypt: ^5.0.0 + pointycastle: ^3.4.0 + mime: ^1.0.3 + pointer_interceptor: ^0.10.1+2 + file_saver: ^0.2.14 + + english_words: ^4.0.0 + provider: ^6.0.0 + intl: ^0.19.0 + +dev_dependencies: + flutter_test: + sdk: flutter + + flutter_lints: ^2.0.0 + +dependency_overrides: + flutter_layout_grid: 2.0.7 + flutter_math_fork: 0.7.2 + +flutter: + uses-material-design: true + assets: + - assets/back.png + - assets/communications.png + - assets/contact-book.png - assets/email.png \ No newline at end of file From ef9cb5b00d1c5c0f1f44af3f0ad51dd5bdd7e6e4 Mon Sep 17 00:00:00 2001 From: juan Date: Thu, 24 Apr 2025 00:52:42 -0400 Subject: [PATCH 12/15] pdf preview implemented --- lib/attachmentWidget.dart | 77 ++++++++++++++++++++------------------- lib/augment.dart | 1 - 2 files changed, 40 insertions(+), 38 deletions(-) diff --git a/lib/attachmentWidget.dart b/lib/attachmentWidget.dart index c3fcf57..041ab67 100644 --- a/lib/attachmentWidget.dart +++ b/lib/attachmentWidget.dart @@ -1,59 +1,62 @@ import "package:crab_ui/structs.dart"; import "package:flutter/material.dart"; +import 'package:pdfrx/pdfrx.dart'; class AttachmentWidget extends StatelessWidget { final AttachmentResponse attachment; AttachmentWidget({required this.attachment}); + Widget attachmentViewer(AttachmentResponse att) { + String extension = att.name + .toString() + .substring(att.name.toString().indexOf(".") + 1) + .toLowerCase(); + if (extension == "jpg") { + return Image.memory(att.data); + } else if (extension == "pdf") { + return PdfViewer.data(att.data, sourceName: att.name); + } + return Text( + "Attachment not supported for preview, you'll need to download", + style: TextStyle( + color: Colors.black, fontSize: 20, decoration: TextDecoration.none), + ); + } + @override Widget build(BuildContext context) { return Container( - color: Colors.black38, - child: Stack( - // appBar: AppBar(title: Text('New Tab Content')), - children: [ - + color: Colors.black38, + child: Stack(children: [ Container( color: Colors.white, child: Padding( padding: EdgeInsets.fromLTRB(10, 20, 0, 10), - child: Row( - + child: Column( children: [ - CloseButton( - onPressed: () => { - Navigator.pop(context) - } + Row( + children: [ + CloseButton(onPressed: () => {Navigator.pop(context)}), + Text( + attachment.name + .toString(), //its alr a string but incase ¯\(ツ)/¯ + style: TextStyle( + color: Colors.black, + fontSize: 20, + decoration: + TextDecoration.none), //TODO: personalize your fonts + ), + ], ), - Text( - attachment.name.toString(), //its alr a string but incase ¯\(ツ)/¯ - style: TextStyle( - color: Colors.black, - fontSize: 20, - decoration: TextDecoration.none - ), //TODO: personalize your fonts - ), + + Expanded( + + child: attachmentViewer(attachment), + ) ], ), ), ), - Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - SizedBox( - width: 1000, - height: 600, - child: Image.memory(attachment.data), - ) - ], - ), - ), - ], - ), - ); + ])); } } - - - diff --git a/lib/augment.dart b/lib/augment.dart index b75226f..ebee03b 100644 --- a/lib/augment.dart +++ b/lib/augment.dart @@ -309,7 +309,6 @@ class AugmentClasses { context, MaterialPageRoute( builder: (context) => AttachmentWidget(attachment: file))); - })); } return listOfFiles; From 8ae776c92f3c126324450a2c3d477e62e18e0c40 Mon Sep 17 00:00:00 2001 From: juan Date: Thu, 24 Apr 2025 01:20:46 -0400 Subject: [PATCH 13/15] solved detached ArrayBuffer error, by cloning the data every time before use --- lib/attachmentWidget.dart | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/lib/attachmentWidget.dart b/lib/attachmentWidget.dart index 041ab67..26247bc 100644 --- a/lib/attachmentWidget.dart +++ b/lib/attachmentWidget.dart @@ -1,3 +1,5 @@ +import "dart:typed_data"; + import "package:crab_ui/structs.dart"; import "package:flutter/material.dart"; import 'package:pdfrx/pdfrx.dart'; @@ -14,7 +16,16 @@ class AttachmentWidget extends StatelessWidget { if (extension == "jpg") { return Image.memory(att.data); } else if (extension == "pdf") { - return PdfViewer.data(att.data, sourceName: att.name); + PdfViewer pdf = PdfViewer.data( + Uint8List.fromList(att.data), + sourceName: att.name, + params: const PdfViewerParams( + enableTextSelection: true, + scrollByMouseWheel: 0.5, + ) + ); + + return pdf; } return Text( "Attachment not supported for preview, you'll need to download", @@ -39,19 +50,17 @@ class AttachmentWidget extends StatelessWidget { CloseButton(onPressed: () => {Navigator.pop(context)}), Text( attachment.name - .toString(), //its alr a string but incase ¯\(ツ)/¯ + .toString(), //its alr a string but incase ¯\(ツ)/¯ //update: i did that everywhere lol style: TextStyle( color: Colors.black, fontSize: 20, - decoration: - TextDecoration.none), //TODO: personalize your fonts + decoration: TextDecoration + .none), //TODO: personalize your fonts ), ], ), - Expanded( - - child: attachmentViewer(attachment), + child: attachmentViewer(attachment), ) ], ), From 6beda384e1c058d0c2b252574c529f0ebafccb78 Mon Sep 17 00:00:00 2001 From: juan Date: Thu, 24 Apr 2025 12:09:55 -0400 Subject: [PATCH 14/15] pdf, and png preview --- lib/attachmentWidget.dart | 26 +++++++++++++------------- pubspec.yaml | 3 +++ 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/lib/attachmentWidget.dart b/lib/attachmentWidget.dart index 26247bc..c5da22a 100644 --- a/lib/attachmentWidget.dart +++ b/lib/attachmentWidget.dart @@ -3,6 +3,7 @@ import "dart:typed_data"; import "package:crab_ui/structs.dart"; import "package:flutter/material.dart"; import 'package:pdfrx/pdfrx.dart'; +import 'package:photo_view/photo_view.dart'; class AttachmentWidget extends StatelessWidget { final AttachmentResponse attachment; @@ -13,25 +14,24 @@ class AttachmentWidget extends StatelessWidget { .toString() .substring(att.name.toString().indexOf(".") + 1) .toLowerCase(); - if (extension == "jpg") { + if (extension == "jpg" || extension == "png") { return Image.memory(att.data); } else if (extension == "pdf") { - PdfViewer pdf = PdfViewer.data( - Uint8List.fromList(att.data), - sourceName: att.name, - params: const PdfViewerParams( - enableTextSelection: true, - scrollByMouseWheel: 0.5, - ) - ); - - return pdf; + return PdfViewer.data(Uint8List.fromList(att.data), + sourceName: att.name, + params: PdfViewerParams( + enableTextSelection: true, + scrollByMouseWheel: 0.5, + annotationRenderingMode: + PdfAnnotationRenderingMode.annotationAndForms, + )); } - return Text( + return Center( + child: Text( "Attachment not supported for preview, you'll need to download", style: TextStyle( color: Colors.black, fontSize: 20, decoration: TextDecoration.none), - ); + )); } @override diff --git a/pubspec.yaml b/pubspec.yaml index 2608eeb..3f94c16 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -20,10 +20,13 @@ dependencies: mime: ^1.0.3 pointer_interceptor: ^0.10.1+2 file_saver: ^0.2.14 + english_words: ^4.0.0 provider: ^6.0.0 intl: ^0.19.0 + pdfrx: ^1.0.94 + photo_view: ^0.15.0 dev_dependencies: flutter_test: From c09f4e7a110f5746285bf12761dd034ea5157b74 Mon Sep 17 00:00:00 2001 From: juan Date: Thu, 24 Apr 2025 12:51:45 -0400 Subject: [PATCH 15/15] if widget is not previewable, then give the user the possibility to download it there --- lib/attachmentWidget.dart | 42 ++++++++++++++++++++++++++++++++++----- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/lib/attachmentWidget.dart b/lib/attachmentWidget.dart index c5da22a..5146d9c 100644 --- a/lib/attachmentWidget.dart +++ b/lib/attachmentWidget.dart @@ -1,5 +1,6 @@ import "dart:typed_data"; +import "package:crab_ui/attachmentDownload.dart"; import "package:crab_ui/structs.dart"; import "package:flutter/material.dart"; import 'package:pdfrx/pdfrx.dart'; @@ -27,11 +28,42 @@ class AttachmentWidget extends StatelessWidget { )); } return Center( - child: Text( - "Attachment not supported for preview, you'll need to download", - style: TextStyle( - color: Colors.black, fontSize: 20, decoration: TextDecoration.none), - )); + child: Container( + padding: EdgeInsets.all(20), + decoration: BoxDecoration( + color: Color(0xff6C63FF), + borderRadius: BorderRadius.circular(16), + boxShadow: [ + BoxShadow( + color: Colors.black26, + blurRadius: 10, + ), + ], + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + "No preview available", + style: TextStyle( + color: Colors.white, fontSize: 18, decoration: TextDecoration.none), + ), + SizedBox( + height: 5, + ), + GestureDetector( + child: ElevatedButton( + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text("Download", style: TextStyle(color: Color(0xff2c3e50)),), + Icon(Icons.download, + color: Color(0xffb0b0b0),), + ]), + onPressed: () => Attachmentdownload().saveFile(att), + )), + ]), + )); } @override