Merge pull request 'HyM attachments resolve: #2' (#3) from login into main

Reviewed-on: #3
This commit is contained in:
Juan Marulanda De Los Rios 2025-04-24 16:52:58 +00:00
commit 4fa8e5b6fe
10 changed files with 874 additions and 420 deletions

View File

@ -1,360 +1,549 @@
// 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 '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;
class ApiService {
static String ip = "";
static String port = "";
Future<List<GetThreadResponse>> fetchEmailsFromFolder(
String folder, int pagenitaion) async {
// print(ip + " " + port);
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<GetThreadResponse> 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<int> threadIDs = List<int>.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<void> fetchThreads(
//populates allEmails, which is the List that contains all the emails in a thread
int threadId,
List<GetThreadResponse> allEmails) async {
try {
var url =
Uri.http('$ip:$port', 'get_thread', {'id': threadId.toString()});
var response = await http.get(url);
if (response.statusCode == 200) {
Map<String, dynamic> 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<List<SerializableMessage>> 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<dynamic> messagesJson = json.decode(response.body);
List<SerializableMessage> messages =
messagesJson.map((mj) => SerializableMessage.fromJson(mj)).toList();
return messages;
}
print(response.statusCode);
} catch (e) {
print("caught $e");
}
return [];
}
Future<String> fetchEmailContent(List<String> IDs) async {
String content = r"""
""";
try {
//attaches email after email from a thread
for (var id in IDs) {
var url = Uri.http('$ip:$port', 'email', {'id': id});
var response = await http.get(url);
if (response.statusCode == 200) {
content += response.body;
try {
getAttachmentsInfo("INBOX", id);
} catch (innerError) {
print('_getAttachment info caught error $innerError');
}
content += "<hr>";
}
}
} catch (e) {
print('_getEmailContent caught error: $e');
}
return content;
}
// void _addMailBox async(BuildContext context){
// //add email folder
// showDialog(context: context, builder: builder)
// }
Future<List<String>> fetchFolders() async {
try {
var url = Uri.http('$ip:$port', 'folders');
var response = await http.get(url);
return List<String>.from(json.decode(response.body));
} catch (e) {
print('fetchFolders caught error: $e');
return [];
}
}
Future<void> createFolder(String folderName) async {
var url = Uri.http('$ip:$port', 'create_folder');
Map<String, String> 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<void> deleteFolder(String folderName) async {
var url = Uri.http('$ip:$port', 'delete_folder');
Map<String, String> 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<bool> logIn(String json) async {
// var url = Uri.https('')
// try{
// String response = await http.post(
// url
// );
// }
return false;
}
Future<List<AttachmentInfo>> 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 response = await http.get(url);
print("response $response");
if (response.statusCode == 200) {
var result = response.body;
List<dynamic> attachmentList = json.decode(result);
print("attachment list $attachmentList");
List<AttachmentInfo> attachments =
attachmentList.map((al) => AttachmentInfo.fromJson(al)).toList();
print("attachments $attachments");
return attachments;
}
} catch (e) {
print(e);
}
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<EmailView> {
late Key iframeKey;
late String currentContent;
late String viewTypeId;
// TextEditingController _jumpController = TextEditingController();
@override
void initState() {
super.initState();
String currentContent = widget.emailContent;
viewTypeId = "iframe-${DateTime.now().millisecondsSinceEpoch}";
_registerViewFactory(currentContent);
}
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 )
@override
Widget build(BuildContext context) {
// print(currentContent);
return Scaffold(
appBar: AppBar(
title: Text(widget.name),
),
body: Column(
children: [
EmailToolbar(
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: [
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,
),
),
],
));
}
}
// 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<AttachmentResponse> threadAttachments = [];
static String currFolder = "";
static List<String> currThread = [];
Future<List<GetThreadResponse>> 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<GetThreadResponse> 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<int> threadIDs = List<int>.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<void> fetchThreads(
//populates allEmails, which is the List that contains all the emails in a thread
int threadId,
List<GetThreadResponse> 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<String, dynamic> 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<List<SerializableMessage>> 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<dynamic> messagesJson = json.decode(response.body);
List<SerializableMessage> 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<String> fetchEmailContent(
List<String> 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<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) {
print('_getAttachment info caught error $innerError');
}
content +=
"""<div id="JuanBedarramarker" style="width: 10px; height: 30px;"></div>""";
content += "<hr>";
}
}
} catch (e) {
print('_getEmailContent caught error: $e');
}
return content;
}
// void _addMailBox async(BuildContext context){
// //add email folder
// showDialog(context: context, builder: builder)
// }
Future<List<String>> fetchFolders() async {
try {
var url = Uri.http('$ip:$port', 'folders');
var response = await http.get(url);
return List<String>.from(json.decode(response.body));
} catch (e) {
print('fetchFolders caught error: $e');
return [];
}
}
Future<void> createFolder(String folderName) async {
var url = Uri.http('$ip:$port', 'create_folder');
Map<String, String> 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<void> deleteFolder(String folderName) async {
var url = Uri.http('$ip:$port', 'delete_folder');
Map<String, String> 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<bool> logIn(String json) async {
// var url = Uri.https('')
// try{
// String response = await http.post(
// url
// );
// }
return false;
}
Future<List<AttachmentInfo>> 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<dynamic> attachmentList = json.decode(result);
// Map<String, dynamic> attachmentList = json.decode(result);
// print("attachment list $attachmentList");
List<AttachmentInfo> attachments =
attachmentList.map((al) => AttachmentInfo.fromJson(al)).toList();
// print("attachments $attachments");
return attachments;
}
} catch (e) {
print(e);
}
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 {
//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<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 {
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<EmailView> {
//html css rendering thing
late Key iframeKey;
late String currentContent;
late String viewTypeId;
Future<List<Map<String, dynamic>>>? _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"""
// <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,
),
),
],
),
// 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

@ -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<void> 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)
);
}
}

103
lib/attachmentWidget.dart Normal file
View File

@ -0,0 +1,103 @@
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';
import 'package:photo_view/photo_view.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" || extension == "png") {
return Image.memory(att.data);
} else if (extension == "pdf") {
return PdfViewer.data(Uint8List.fromList(att.data),
sourceName: att.name,
params: PdfViewerParams(
enableTextSelection: true,
scrollByMouseWheel: 0.5,
annotationRenderingMode:
PdfAnnotationRenderingMode.annotationAndForms,
));
}
return Center(
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
Widget build(BuildContext context) {
return Container(
color: Colors.black38,
child: Stack(children: <Widget>[
Container(
color: Colors.white,
child: Padding(
padding: EdgeInsets.fromLTRB(10, 20, 0, 10),
child: Column(
children: [
Row(
children: [
CloseButton(onPressed: () => {Navigator.pop(context)}),
Text(
attachment.name
.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
),
],
),
Expanded(
child: attachmentViewer(attachment),
)
],
),
),
),
]));
}
}

View File

@ -1,6 +1,12 @@
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';
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;
@ -64,8 +70,8 @@ class _DynamicClassesAugment extends State<EmailToolbar> {
child: Text('Reload'),
),
ElevatedButton(
onPressed: AugmentClasses.handleImages,
child: Text('Images'),
onPressed: () => AugmentClasses.handleImages(context),
child: Text('Attachments'),
),
SizedBox(width: 8),
ElevatedButton(
@ -206,6 +212,8 @@ class _DynamicClassesAugment extends State<EmailToolbar> {
}
class AugmentClasses {
ApiService _apiService = ApiService();
static OverlayEntry? _overlayEntry;
static void handleHome(BuildContext context) {
Navigator.of(context).popUntil((route) => route.isFirst);
}
@ -214,8 +222,96 @@ 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
Container(
color: Colors.black54,
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
),
// 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(context),
),
),
],
),
),
),
),
),
],
),
);
if (_overlayEntry != null) {
overlay.insert(_overlayEntry!);
}
}
// Add missing widget builder methods
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<Widget> _buildMenuItem(BuildContext context) {
List<Widget> listOfFiles = [];
for (AttachmentResponse file in ApiService.threadAttachments) {
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(
context,
MaterialPageRoute(
builder: (context) => AttachmentWidget(attachment: file)));
}));
}
return listOfFiles;
}
static void handleOpen() {
@ -485,7 +581,7 @@ class AugmentClasses {
void handleFilter() {}
static Future<void> FilterButton(context) async {
//this is literally ctrl+F :skull:
//idea is to search in file, extract the <p> tags that contain these
//idea is to search in file, extract the <p> tags that contain these
//words and highlight, then when zoom, you just jump to that paragraph
AugmentClasses.disableIframePointerEvents();
@ -517,6 +613,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) {

View File

@ -4,10 +4,12 @@ import 'structs.dart';
class EmailListScreen extends StatelessWidget {
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
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<EmailPage> {
final ApiService apiService = ApiService();
List<GetThreadResponse> 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<EmailPage> {
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<EmailPage> {
body: EmailListScreen(
emails: emails,
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 '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<HomeScreen> 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<HomeScreen> 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<HomeScreen> with TickerProviderStateMixin {
children: [
ElevatedButton(
onPressed: () {
_emailPageKey.currentState
_emailPageKey.currentState!.isBackDisabled ? null: _emailPageKey.currentState
?.updatePagenation('back');
},
child: Icon(Icons.navigate_before),

View File

@ -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<bool> 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<String, dynamic> 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;

View File

@ -61,7 +61,7 @@ class _SerializableMessageListScreenState extends State<SerializableMessageListS
),
trailing: Text(message.date),
onTap: () async {
String emailContent = await apiService.fetchEmailContent([message.id]);
String emailContent = await apiService.fetchEmailContent([message.id], message.list);
Navigator.push(
context,
MaterialPageRoute(

View File

@ -1,5 +1,7 @@
//data structures
import 'dart:typed_data';
class GetThreadResponse {
final int id;
final List<String> 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<AttachmentInfo> {
final List<AttachmentInfo> _attachments;
AttachmentInfoList(this._attachments);
factory AttachmentInfoList.fromJsonList(List<Map<String, dynamic>> jsonList) {
return AttachmentInfoList(jsonList.map((json) => AttachmentInfo.fromJson(json)).toList());
}
@override
Iterator<AttachmentInfo> 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<String, dynamic> json) {
return AttachmentResponse(name: json["name"], data: Uint8List.fromList(List<int>.from(json["data"])));
}
}

View File

@ -1,42 +1,47 @@
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
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
pdfrx: ^1.0.94
photo_view: ^0.15.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