api_service.dart 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431
  1. import 'package:flutter/material.dart';
  2. import 'package:http/http.dart' as http;
  3. import 'dart:convert';
  4. import 'dart:ui_web' as ui;
  5. import 'dart:html' as html;
  6. import 'augment.dart';
  7. import 'dart:js' as js;
  8. class MailAddress {
  9. final String? name;
  10. final String address;
  11. MailAddress({this.name, required this.address});
  12. factory MailAddress.fromJson(Map<String, dynamic> json) {
  13. return MailAddress(
  14. name: json['name'],
  15. address: json['address'],
  16. );
  17. }
  18. @override
  19. String toString() {
  20. // TODO: implement toString
  21. return '${name} <${address}>';
  22. }
  23. }
  24. class SerializableMessage {
  25. final String name;
  26. final String from;
  27. final List<MailAddress> to;
  28. final List<MailAddress> cc;
  29. final String hash;
  30. // final String path;
  31. final String subject;
  32. final String date;
  33. final int uid;
  34. final String list;
  35. final String id;
  36. final String in_reply_to;
  37. SerializableMessage({
  38. required this.name,
  39. required this.from,
  40. required this.to,
  41. required this.cc,
  42. required this.hash,
  43. required this.subject,
  44. required this.date,
  45. required this.uid,
  46. required this.list,
  47. required this.id,
  48. required this.in_reply_to,
  49. });
  50. factory SerializableMessage.fromJson(Map<String, dynamic> json) {
  51. var toList = json['to'] as List;
  52. var ccList = json['cc'] as List;
  53. return SerializableMessage(
  54. name: json['name'],
  55. from: json['from'],
  56. // to: json['name', 'address']
  57. to: toList.map((i) => MailAddress.fromJson(i)).toList(),
  58. cc: ccList.map((i) => MailAddress.fromJson(i)).toList(),
  59. // path: json['path'],
  60. hash: json['hash'],
  61. subject: json['subject'],
  62. date: json['date'],
  63. uid: json['uid'],
  64. list: json['list'],
  65. id: json['id'],
  66. in_reply_to: json['in_reply_to'],
  67. );
  68. }
  69. }
  70. class EmailPage extends StatefulWidget {
  71. const EmailPage({super.key});
  72. final String title = 'Emails';
  73. @override
  74. State<EmailPage> createState() => _EmailPageState();
  75. }
  76. class _EmailPageState extends State<EmailPage> {
  77. List emails = [];
  78. void _displayEmailsFromFolder(String folder) async {
  79. // Map<String, List<SerializableMessage>> messagesMap = {};
  80. List<SerializableMessage> allEmails = [];
  81. try {
  82. var url = Uri.http(
  83. '127.0.0.1:3001', 'sorted_threads_by_date', {'folder': folder});
  84. var response = await http.get(url);
  85. // print(response.body);
  86. // Map<String, dynamic> json = jsonDecode(response.body); original
  87. // json.forEach((key, value) {
  88. // List<SerializableMessage> messages = (value as List)
  89. // .map((item) => SerializableMessage.fromJson(item))
  90. // .toList();
  91. // messagesMap[key] = messages;
  92. // });
  93. // new shit
  94. if (response.statusCode == 200) {
  95. List<dynamic> json = jsonDecode(response.body);
  96. for (var item in json) {
  97. if (item.length > 1 && item[0] is String && item[1] is List) {
  98. List<int> threadIDs = List<int>.from(item[1]);
  99. for (var threadId in threadIDs) {
  100. await fetchThreadMessages(threadId, allEmails);
  101. }
  102. }
  103. }
  104. } else {
  105. throw Exception('Failed to load threads');
  106. }
  107. } catch (e) {
  108. print('_displayEmailsFromFolder caught error: $e');
  109. }
  110. setState(() {
  111. emails.clear();
  112. // emails = messagesMap.values.toList().expand((list) => list).toList();
  113. emails.addAll(allEmails);
  114. ;
  115. });
  116. }
  117. Future<void> fetchThreadMessages(
  118. int threadId, List<SerializableMessage> allEmails) async {
  119. try {
  120. var url = Uri.http(
  121. '127.0.0.1:3001', 'get_thread_messages', {'id': threadId.toString()});
  122. var response = await http.get(url);
  123. if (response.statusCode == 200) {
  124. List<dynamic> messagesJson = jsonDecode(response.body);
  125. List<SerializableMessage> messages =
  126. messagesJson.map((mj) => SerializableMessage.fromJson(mj)).toList();
  127. allEmails.addAll(messages);
  128. } else {
  129. throw Exception(
  130. 'Failed to fetch thread messages for thread ID: $threadId');
  131. }
  132. } catch (e) {
  133. print('Error fetching thread messages: $e');
  134. }
  135. }
  136. Future<String> _getEmailContent(String id) async {
  137. String content = r"""
  138. """;
  139. try {
  140. var url = Uri.http('127.0.0.1:3001', 'email', {'id': id});
  141. var response = await http.get(url);
  142. if (response.statusCode == 200) {
  143. content = response.body;
  144. }
  145. } catch (e) {
  146. print('_getEmailContent caught error: $e');
  147. }
  148. return content;
  149. }
  150. Future<List<Widget>> _getDrawerItems() async {
  151. List<String> drawerItems = [];
  152. try {
  153. var url = Uri.http('127.0.0.1:3001', 'folders');
  154. var response = await http.get(url);
  155. drawerItems = List<String>.from(json.decode(response.body));
  156. } catch (e) {
  157. print('_getDrawerItems caught error: $e');
  158. }
  159. List<Widget> drawerWidgets = [];
  160. for (String item in drawerItems) {
  161. drawerWidgets.add(
  162. ListTile(
  163. leading: Icon(Icons.mail),
  164. title: Text(item),
  165. onTap: () {
  166. _displayEmailsFromFolder(item);
  167. Navigator.pop(context);
  168. },
  169. ),
  170. );
  171. }
  172. return drawerWidgets;
  173. }
  174. @override
  175. Widget build(BuildContext context) {
  176. return Scaffold(
  177. appBar: AppBar(
  178. backgroundColor: Theme.of(context).colorScheme.inversePrimary,
  179. title: Text(widget.title),
  180. ),
  181. drawer: Drawer(
  182. child: FutureBuilder<List<Widget>>(
  183. future:
  184. _getDrawerItems(), // call the async function to get the future
  185. builder:
  186. (BuildContext context, AsyncSnapshot<List<Widget>> snapshot) {
  187. if (snapshot.connectionState == ConnectionState.waiting) {
  188. // While data is loading, show a progress indicator
  189. return Center(child: CircularProgressIndicator());
  190. } else if (snapshot.hasError) {
  191. // If something went wrong, show an error message
  192. return Center(child: Text('Error: ${snapshot.error}'));
  193. } else {
  194. // When data is fetched successfully, display the items
  195. return ListView(
  196. padding: EdgeInsets.zero,
  197. children:
  198. snapshot.data!, // Unwrap the data once confirmed it's there
  199. );
  200. }
  201. },
  202. ),
  203. ),
  204. body: EmailListScreen(
  205. emails: emails,
  206. getEmailContent: _getEmailContent,
  207. // getJsonEmail: _getThreadMessagesJson
  208. ),
  209. );
  210. }
  211. }
  212. class EmailListScreen extends StatelessWidget {
  213. final List emails;
  214. final Future<String> Function(String) getEmailContent;
  215. EmailListScreen({
  216. required this.emails,
  217. required this.getEmailContent,
  218. });
  219. @override
  220. Widget build(BuildContext context) {
  221. return Scaffold(
  222. appBar: AppBar(
  223. title: Text('Emails'),
  224. ),
  225. body: ListView.separated(
  226. itemCount: emails.length,
  227. itemBuilder: (context, index) {
  228. return ListTile(
  229. title: Text(emails[index].from,
  230. style: TextStyle(fontWeight: FontWeight.bold)),
  231. subtitle: Column(
  232. crossAxisAlignment: CrossAxisAlignment.start,
  233. children: [
  234. Text(emails[index].subject),
  235. ],
  236. ),
  237. trailing: Text(emails[index].date.toString()),
  238. onTap: () async {
  239. String emailContent = await getEmailContent(emails[index].id);
  240. String from = emails[index].from.toString();
  241. String name = emails[index].name.toString();
  242. String to = emails[index].to.toString();
  243. String cc = emails[index].cc.toString();
  244. String hash = emails[index].hash.toString();
  245. String subject = emails[index].subject.toString();
  246. String date = emails[index].date.toString();
  247. String uid = emails[index].uid.toString();
  248. String list = emails[index].list.toString();
  249. String id = emails[index].id.toString();
  250. String in_reply_to = emails[index].in_reply_to.toString();
  251. // String jsonbuilt =
  252. Navigator.push(
  253. context,
  254. MaterialPageRoute(
  255. builder: (context) => EmailView(
  256. emailContent: emailContent,
  257. // jsonEmail: jsonContent,
  258. from: from, name: name, to: to, cc: cc, hash: hash,
  259. subject: subject, date: date,
  260. uid: uid, list: list, id: id,
  261. in_reply_to: in_reply_to,
  262. )),
  263. );
  264. });
  265. },
  266. separatorBuilder: (context, index) {
  267. return Divider();
  268. },
  269. ),
  270. );
  271. }
  272. }
  273. class EmailView extends StatefulWidget {
  274. final String emailContent;
  275. // final String jsonEmail;
  276. final String from;
  277. final String name;
  278. final String to;
  279. final String cc;
  280. final String hash;
  281. final String subject;
  282. final String date;
  283. final String uid;
  284. final String list;
  285. final String id;
  286. final String in_reply_to;
  287. const EmailView(
  288. {Key? key,
  289. required this.emailContent,
  290. // required this.jsonEmail,
  291. required this.from,
  292. required this.name,
  293. required this.to,
  294. required this.cc,
  295. required this.hash,
  296. required this.subject,
  297. required this.date,
  298. required this.uid,
  299. required this.list,
  300. required this.id,
  301. required this.in_reply_to})
  302. : super(key: key);
  303. @override
  304. _EmailViewState createState() => _EmailViewState();
  305. }
  306. class _EmailViewState extends State<EmailView> {
  307. late Key iframeKey;
  308. @override
  309. void initState() {
  310. super.initState();
  311. print(widget.id);
  312. iframeKey = Key("iframe-${widget.id}");
  313. ui.platformViewRegistry.registerViewFactory(
  314. // 'html-view33'
  315. iframeKey.toString(),
  316. (int viewId) => html.IFrameElement()
  317. ..width = '100%'
  318. ..height = '100%'
  319. ..srcdoc = widget.emailContent
  320. ..style.border = 'none',
  321. // ..style.width = '100%',
  322. // ..style.height = '100%'
  323. );
  324. }
  325. @override
  326. Widget build(BuildContext context) {
  327. void _handleNumbering() {
  328. // Check if the iframe content window is accessible
  329. try {
  330. js.context.callMethod('applyNumberingVisibility');
  331. } catch (e) {
  332. print('Error accessing iframe method: $e');
  333. // Optionally, use postMessage for cross-origin communication
  334. // js.context['iframeElement'].contentWindow.postMessage(jsify({'action': 'toggleNumbering'}), '*');
  335. }
  336. }
  337. return Scaffold(
  338. appBar: AppBar(
  339. title: Text(widget.name),
  340. ),
  341. body: Column(
  342. children: [
  343. EmailToolbar(onButtonPressed: _handleNumbering),
  344. Row(
  345. // title of email
  346. children: [
  347. Text(
  348. widget.subject,
  349. style: TextStyle(fontSize: 30),
  350. ),
  351. ],
  352. ),
  353. Row(
  354. children: [
  355. Text(
  356. 'from ${widget.name}',
  357. style: TextStyle(fontSize: 18),
  358. ),
  359. Text(
  360. '<${widget.from}>',
  361. style: TextStyle(fontSize: 18),
  362. ),
  363. Spacer(),
  364. Text(
  365. '${widget.date}',
  366. textAlign: TextAlign.right,
  367. )
  368. ],
  369. ),
  370. // TODO: make a case where if one of these is the user's email it just says me :)))))
  371. Row(
  372. children: [
  373. Text(
  374. 'to ${widget.to.toString()}',
  375. style: TextStyle(fontSize: 15),
  376. )
  377. ],
  378. ),
  379. Expanded(
  380. child: HtmlElementView(
  381. key: iframeKey,
  382. viewType: iframeKey.toString(),
  383. // 'html-view33',
  384. ),
  385. ),
  386. ],
  387. ));
  388. }
  389. }