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,7 +403,9 @@ class _EmailViewState extends State<EmailView> {
appBar: AppBar( appBar: AppBar(
title: Text(widget.name), title: Text(widget.name),
), ),
body: Column( body: Stack(
children: [
Column(
children: [ children: [
EmailToolbar( EmailToolbar(
onJumpToSpan: _scrollToNumber, onJumpToSpan: _scrollToNumber,
@ -309,8 +429,7 @@ class _EmailViewState extends State<EmailView> {
// print("change"); // print("change");
// widget.emailContent = r" // widget.emailContent = r"
// " //
// },
), ),
Row( Row(
// title of email // title of email
@ -355,6 +474,71 @@ class _EmailViewState extends State<EmailView> {
), ),
), ),
], ],
),
// Overlay widgets dynamically based on marker positions
FutureBuilder<List<Map<String, dynamic>>>(
future: _markerPositionsFuture,
builder: (context, snapshot) {
print("FutureBuilder state: ${snapshot.connectionState}");
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator());
}
if (snapshot.hasError) {
print("Error in FutureBuilder: ${snapshot.error}");
return Center(child: Text('error loading markers'));
}
if (snapshot.hasData && snapshot.data != null) {
final markers = snapshot.data!;
return Stack(
children: markers.map((marker) {
return Positioned(
left: marker['x'].toDouble(),
top: marker['y'].toDouble(),
child: GestureDetector(
onTap: () {
print('Tapped on ${marker['id']}');
},
child: Container(
width: 50,
height: 50,
color: Colors.red,
child: Center(
child: Text(
marker['id'],
style: TextStyle(color: Colors.white),
),
),
),
),
);
}).toList(),
);
}
return SizedBox.shrink(); // No markers found
},
),
// Red widget overlay
// Positioned(
// left: 8, // Adjust based on your desired position
// top: 100 + 44 + 5, // Adjust based on your desired position
// child: IgnorePointer(
// ignoring: true, // Ensures the iframe remains interactive
// child: Container(
// color: Colors.red,
// width: 100,
// height: 50,
// child: Center(
// child: Text(
// 'Overlay',
// style: TextStyle(color: Colors.white),
// ),
// ),
// ),
// ),
// ),
],
)); ));
} }
} }

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