HyM attachments resolve: #2 #3

Merged
Juan merged 15 commits from login into main 2025-04-24 16:52:58 +00:00
5 changed files with 371 additions and 92 deletions
Showing only changes of commit b2fb70b3d6 - Show all commits

View File

@ -1,20 +1,29 @@
// this file should handle most of the API calls // this file should handle most of the API calls
// it also builds some widgets, but it will be modulated later // 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:flutter/material.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'dart:convert'; import 'dart:convert';
import 'dart:ui_web' as ui; import 'dart:ui_web' as ui;
import 'augment.dart'; import 'augment.dart';
import 'dart:html' as html; import 'dart:html' as html;
import 'dart:js' as js;
class ApiService { class ApiService {
static String ip = ""; static String ip = "";
static String port = ""; static String port = "";
static List<AttachmentResponse> threadAttachments = [];
static String currFolder = "";
static List<String> currThread = [];
Future<List<GetThreadResponse>> fetchEmailsFromFolder( Future<List<GetThreadResponse>> fetchEmailsFromFolder(
String folder, int pagenitaion) async { String folder, int pagenitaion) async {
// print(ip + " " + port);
try { try {
var url = Uri.http('$ip:$port', 'sorted_threads_by_date', { var url = Uri.http('$ip:$port', 'sorted_threads_by_date', {
'folder': folder, 'folder': folder,
@ -51,8 +60,8 @@ class ApiService {
int threadId, int threadId,
List<GetThreadResponse> allEmails) async { List<GetThreadResponse> allEmails) async {
try { try {
var url = var url = Uri.http('${ApiService.ip}:${ApiService.port}', 'get_thread',
Uri.http('$ip:$port', 'get_thread', {'id': threadId.toString()}); {'id': threadId.toString()});
var response = await http.get(url); var response = await http.get(url);
if (response.statusCode == 200) { if (response.statusCode == 200) {
@ -97,24 +106,34 @@ class ApiService {
return []; return [];
} }
Future<String> fetchEmailContent(List<String> IDs) async { //returns the html for the email, it gets used in emailView
Future<String> fetchEmailContent(
List<String> IDsString, String emailFolder) async {
String content = r""" String content = r"""
"""; """;
threadAttachments = [];
try { try {
//attaches email after email from a thread //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 url = Uri.http('$ip:$port', 'email', {'id': id});
var response = await http.get(url); var response = await http.get(url);
currThread.add(id);
if (response.statusCode == 200) { if (response.statusCode == 200) {
content += response.body; content += response.body;
try { try {
getAttachmentsInfo("INBOX", id); List<AttachmentInfo> attachments = await getAttachmentsInfo(
emailFolder, id);
for (var attachment in attachments) {
//TODO: for each attachment creaate at the bottom a widget for each individual one
threadAttachments
.add(await getAttachment(emailFolder, id, attachment.name));
}
} catch (innerError) { } catch (innerError) {
print('_getAttachment info caught error $innerError'); print('_getAttachment info caught error $innerError');
} }
content +=
"""<div id="JuanBedarramarker" style="width: 10px; height: 30px;"></div>""";
content += "<hr>"; content += "<hr>";
} }
} }
@ -200,18 +219,21 @@ class ApiService {
Future<List<AttachmentInfo>> getAttachmentsInfo( Future<List<AttachmentInfo>> getAttachmentsInfo(
String folder, String email_id) async { String folder, String email_id) async {
try { try {
var url = Uri.http('127.0.0.1:3001', 'get_attachments_info', var url = Uri.http('$ip:$port', 'get_attachments_info',
{'folder': folder, 'email_id': email_id}); {'folder': folder, 'id': email_id});
print(url); // print(url);
var response = await http.get(url); var response = await http.get(url);
print("response $response"); // print("response $response");
if (response.statusCode == 200) { if (response.statusCode == 200) {
var result = response.body; var result = response.body;
// print(result);
List<dynamic> attachmentList = json.decode(result); List<dynamic> attachmentList = json.decode(result);
print("attachment list $attachmentList"); // Map<String, dynamic> attachmentList = json.decode(result);
// print("attachment list $attachmentList");
List<AttachmentInfo> attachments = List<AttachmentInfo> attachments =
attachmentList.map((al) => AttachmentInfo.fromJson(al)).toList(); attachmentList.map((al) => AttachmentInfo.fromJson(al)).toList();
print("attachments $attachments"); // print("attachments $attachments");
return attachments; return attachments;
} }
@ -220,6 +242,93 @@ class ApiService {
} }
return []; return [];
} }
Future<AttachmentResponse> getAttachment(
String folder, String email_id, String name) async {
try {
var url = Uri.http('$ip:$port', 'get_attachment',
{'folder': folder, 'id': email_id, 'name': name});
var response = await http.get(url);
if (response.statusCode == 200) {
var result = response.body;
// print(result);
Map<String, dynamic> attachmentData = json.decode(result);
AttachmentResponse data = AttachmentResponse.fromJson(attachmentData);
print("data $data");
return data;
}
} catch (e) {
print("getAttachment failed $e");
}
return AttachmentResponse(name: "error", data: Uint8List(0));
}
Future<List<Map<String, dynamic>>> getMarkerPosition() async {
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<dynamic> parsedResult = jsonDecode(result);
var positions = List<Map<String, dynamic>>.from(parsedResult);
print("positions put on");
print(positions);
return positions;
} else {
print("result is null or not a string");
}
} catch (e, stackTrace) {
print("Error executing JavaScript: $e");
print(stackTrace);
}
return [];
}
} }
class EmailView extends StatefulWidget { class EmailView extends StatefulWidget {
@ -246,10 +355,18 @@ class EmailView extends StatefulWidget {
} }
class _EmailViewState extends State<EmailView> { class _EmailViewState extends State<EmailView> {
//html css rendering thing
late Key iframeKey; late Key iframeKey;
late String currentContent; late String currentContent;
late String viewTypeId; late String viewTypeId;
Future<List<Map<String, dynamic>>>? _markerPositionsFuture;
// TextEditingController _jumpController = TextEditingController(); // 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 @override
void initState() { void initState() {
@ -257,6 +374,7 @@ class _EmailViewState extends State<EmailView> {
String currentContent = widget.emailContent; String currentContent = widget.emailContent;
viewTypeId = "iframe-${DateTime.now().millisecondsSinceEpoch}"; viewTypeId = "iframe-${DateTime.now().millisecondsSinceEpoch}";
_registerViewFactory(currentContent); _registerViewFactory(currentContent);
_markerPositionsFuture = ApiService().getMarkerPosition();
} }
void _registerViewFactory(String currentContent) { void _registerViewFactory(String currentContent) {
@ -276,7 +394,7 @@ class _EmailViewState extends State<EmailView> {
AugmentClasses.handleJump(spanId); AugmentClasses.handleJump(spanId);
} }
// TODO: void _invisibility(String ) // TODO: void _invisibility(String ) //to make purple numbers not visible
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -285,75 +403,141 @@ class _EmailViewState extends State<EmailView> {
appBar: AppBar( appBar: AppBar(
title: Text(widget.name), title: Text(widget.name),
), ),
body: Column( body: Stack(
children: [ children: [
EmailToolbar( Column(
onJumpToSpan: _scrollToNumber,
onButtonPressed: () => {},
// AugmentClasses.handleJump(viewTypeId, '1');
// print("button got pressed?");
// _registerViewFactory(r"""
// <h1>Welcome to My Website</h1>
// <p>This is a simple HTML page.</p>
// <h2>What is HTML?</h2>
// <p>HTML (HyperText Markup Language) is the most basic building block of the Web. It defines the meaning and structure of web content. Other technologies besides HTML are generally used to describe a web page's appearance/presentation (CSS) or functionality/behavior (JavaScript).</p>
// <h3>Here's a simple list:</h3>
// <ul>
// <li>HTML elements are the building blocks of HTML pages</li>
// <li>HTML uses tags like <code>&lt;tag&gt;</code> to organize and format content</li>
// <li>CSS is used with HTML to style pages</li>
// </ul>
// <p>Copyright © 2023</p>
// """);
// print("change");
// widget.emailContent = r"
// "
// },
),
Row(
// title of email
children: [ children: [
Text( EmailToolbar(
widget.subject, onJumpToSpan: _scrollToNumber,
style: TextStyle(fontSize: 30), onButtonPressed: () => {},
// AugmentClasses.handleJump(viewTypeId, '1');
// print("button got pressed?");
// _registerViewFactory(r"""
// <h1>Welcome to My Website</h1>
// <p>This is a simple HTML page.</p>
// <h2>What is HTML?</h2>
// <p>HTML (HyperText Markup Language) is the most basic building block of the Web. It defines the meaning and structure of web content. Other technologies besides HTML are generally used to describe a web page's appearance/presentation (CSS) or functionality/behavior (JavaScript).</p>
// <h3>Here's a simple list:</h3>
// <ul>
// <li>HTML elements are the building blocks of HTML pages</li>
// <li>HTML uses tags like <code>&lt;tag&gt;</code> to organize and format content</li>
// <li>CSS is used with HTML to style pages</li>
// </ul>
// <p>Copyright © 2023</p>
// """);
// print("change");
// widget.emailContent = r"
//
),
Row(
// title of email
children: [
Text(
widget.subject,
style: TextStyle(fontSize: 30),
),
],
),
Row(
children: [
Text(
'from ${widget.name}',
style: TextStyle(fontSize: 18),
),
Text(
'<${widget.from}>',
style: TextStyle(fontSize: 18),
),
Spacer(),
Text(
'${widget.date}',
textAlign: TextAlign.right,
)
],
),
// TODO: make a case where if one of these is the user's email it just says me :)))))
Row(
children: [
Text(
'to ${widget.to.toString()}',
style: TextStyle(fontSize: 15),
)
],
),
Expanded(
child: HtmlElementView(
key: UniqueKey(),
viewType: viewTypeId,
),
), ),
], ],
), ),
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( // Overlay widgets dynamically based on marker positions
child: HtmlElementView( FutureBuilder<List<Map<String, dynamic>>>(
key: UniqueKey(), future: _markerPositionsFuture,
viewType: viewTypeId, 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),
// ),
// ),
// ),
// ),
// ),
], ],
)); ));
} }

View File

@ -1,3 +1,5 @@
import 'package:crab_ui/api_service.dart';
import 'package:crab_ui/structs.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'dart:html' as html; import 'dart:html' as html;
import 'dart:js' as js; import 'dart:js' as js;
@ -64,8 +66,8 @@ class _DynamicClassesAugment extends State<EmailToolbar> {
child: Text('Reload'), child: Text('Reload'),
), ),
ElevatedButton( ElevatedButton(
onPressed: AugmentClasses.handleImages, onPressed: () => AugmentClasses.handleImages(context),
child: Text('Images'), child: Text('Attachments'),
), ),
SizedBox(width: 8), SizedBox(width: 8),
ElevatedButton( ElevatedButton(
@ -206,6 +208,8 @@ class _DynamicClassesAugment extends State<EmailToolbar> {
} }
class AugmentClasses { class AugmentClasses {
ApiService _apiService = ApiService();
static OverlayEntry? _overlayEntry;
static void handleHome(BuildContext context) { static void handleHome(BuildContext context) {
Navigator.of(context).popUntil((route) => route.isFirst); Navigator.of(context).popUntil((route) => route.isFirst);
} }
@ -214,8 +218,85 @@ class AugmentClasses {
print("reload"); print("reload");
} }
static void handleImages() { static void handleImages(BuildContext context) {
print("Images button pressed"); 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<Widget> _buildMenuItem() {
List<Widget> 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() { static void handleOpen() {
@ -517,6 +598,7 @@ class AugmentClasses {
} }
static void disableIframePointerEvents() { static void disableIframePointerEvents() {
//pretty sure these dont work
final iframes = html.document.getElementsByTagName('iframe'); final iframes = html.document.getElementsByTagName('iframe');
for (var iframe in iframes) { for (var iframe in iframes) {
if (iframe is html.Element) { if (iframe is html.Element) {

View File

@ -4,10 +4,12 @@ import 'structs.dart';
class EmailListScreen extends StatelessWidget { class EmailListScreen extends StatelessWidget {
final List<GetThreadResponse> emails; final List<GetThreadResponse> emails;
final Future<String> Function(List<String>) getEmailContent; final Future<String> Function(List<String>, 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
@ -24,7 +26,7 @@ class EmailListScreen extends StatelessWidget {
), ),
trailing: Text(email.date.toString()), trailing: Text(email.date.toString()),
onTap: () async { onTap: () async {
String emailContent = await getEmailContent(email.messages); String emailContent = await getEmailContent(email.messages, folder);
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
@ -51,7 +53,7 @@ class EmailListScreen extends StatelessWidget {
// ignore: must_be_immutable // ignore: must_be_immutable
class EmailPage extends StatefulWidget { class EmailPage extends StatefulWidget {
EmailPage({Key? key}) : super(key: key); EmailPage({Key? key}) : super(key: key);
String selectedFolder = "INBOX"; String selectedFolder = "INBOX"; //starter
int offset = 0; int offset = 0;
int page = 1; int page = 1;
@ -62,11 +64,14 @@ class EmailPage extends StatefulWidget {
class EmailPageState extends State<EmailPage> { class EmailPageState extends State<EmailPage> {
final ApiService apiService = ApiService(); final ApiService apiService = ApiService();
List<GetThreadResponse> emails = []; List<GetThreadResponse> emails = [];
int page = 1;
bool isBackDisabled = false;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
widget.page = widget.page; widget.page = page;
isBackDisabled = true;
} }
void updateSelectedFolder(String folder) { void updateSelectedFolder(String folder) {
@ -86,11 +91,16 @@ class EmailPageState extends State<EmailPage> {
setState(() { setState(() {
widget.offset += 50; widget.offset += 50;
widget.page += 1; widget.page += 1;
isBackDisabled = false;
}); });
} else if (option == "back") { } else if (option == "back") {
setState(() { setState(() {
widget.offset -= 50; widget.offset -= 50;
widget.page -= 1; widget.page -= 1;
if (widget.page == 1) {
isBackDisabled = true;
print("back dis");
}
}); });
} }
// print(currentPage); // print(currentPage);
@ -119,6 +129,7 @@ class EmailPageState extends State<EmailPage> {
body: EmailListScreen( body: EmailListScreen(
emails: emails, emails: emails,
getEmailContent: apiService.fetchEmailContent, getEmailContent: apiService.fetchEmailContent,
folder: widget.selectedFolder,//try to grab from it directly
), ),
); );
} }

View File

@ -1,9 +1,10 @@
import 'package:crab_ui/folder_drawer.dart'; import 'folder_drawer.dart';
import 'package:crab_ui/structs.dart'; import 'structs.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'api_service.dart'; import 'api_service.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'email.dart'; import 'email.dart';
// import 'package:shared_preferences/shared_preferences.dart';
// import 'serialize.dart'; // import 'serialize.dart';
class HomeScreen extends StatefulWidget { class HomeScreen extends StatefulWidget {
@ -119,7 +120,7 @@ class _HomeScreenState extends State<HomeScreen> with TickerProviderStateMixin {
body: ListView.separated( body: ListView.separated(
itemCount: result.length, itemCount: result.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
final email = result[index]; final SerializableMessage email = result[index];
return ListTile( return ListTile(
title: Text(email.from, title: Text(email.from,
style: TextStyle(fontWeight: FontWeight.bold)), style: TextStyle(fontWeight: FontWeight.bold)),
@ -131,7 +132,7 @@ class _HomeScreenState extends State<HomeScreen> with TickerProviderStateMixin {
onTap: () async { onTap: () async {
// print('tapped'); // print('tapped');
String emailContent = String emailContent =
await apiService.fetchEmailContent([email.id]); await apiService.fetchEmailContent([email.id], email.list);
// print('content below'); // print('content below');
// print(emailContent); // print(emailContent);
Navigator.push( Navigator.push(
@ -341,7 +342,7 @@ class _HomeScreenState extends State<HomeScreen> with TickerProviderStateMixin {
children: [ children: [
ElevatedButton( ElevatedButton(
onPressed: () { onPressed: () {
_emailPageKey.currentState _emailPageKey.currentState!.isBackDisabled ? null: _emailPageKey.currentState
?.updatePagenation('back'); ?.updatePagenation('back');
}, },
child: Icon(Icons.navigate_before), child: Icon(Icons.navigate_before),

View File

@ -18,6 +18,7 @@ dependencies:
encrypt: ^5.0.0 encrypt: ^5.0.0
pointycastle: ^3.4.0 pointycastle: ^3.4.0
mime: ^1.0.3 mime: ^1.0.3
pointer_interceptor: ^0.10.1+2
english_words: ^4.0.0 english_words: ^4.0.0
provider: ^6.0.0 provider: ^6.0.0