api_service.dart 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510
  1. import 'package:crab_ui/structs.dart';
  2. import 'package:english_words/english_words.dart';
  3. import 'package:flutter/material.dart';
  4. import 'package:http/http.dart' as http;
  5. import 'dart:convert';
  6. import 'dart:ui_web' as ui;
  7. import 'dart:html' as html;
  8. import 'augment.dart';
  9. import 'dart:js' as js;
  10. //data structure
  11. class MailAddress {
  12. final String? name;
  13. final String address;
  14. MailAddress({this.name, required this.address});
  15. factory MailAddress.fromJson(Map<String, dynamic> json) {
  16. return MailAddress(
  17. name: json['name'],
  18. address: json['address'],
  19. );
  20. }
  21. @override
  22. String toString() {
  23. // TODO: implement toString
  24. return '${name} <${address}>';
  25. }
  26. }
  27. // //data structure
  28. // class SerializableMessage {
  29. // final String name;
  30. // final String from;
  31. // final List<MailAddress> to;
  32. // final List<MailAddress> cc;
  33. // final String hash;
  34. // final String subject;
  35. // final String date;
  36. // final int uid;
  37. // final String list;
  38. // final String id;
  39. // final String in_reply_to;
  40. // SerializableMessage({
  41. // required this.name,
  42. // required this.from,
  43. // required this.to,
  44. // required this.cc,
  45. // required this.hash,
  46. // required this.subject,
  47. // required this.date,
  48. // required this.uid,
  49. // required this.list,
  50. // required this.id,
  51. // required this.in_reply_to,
  52. // });
  53. // factory SerializableMessage.fromJson(Map<String, dynamic> json) {
  54. // var toList = json['to'] as List;
  55. // var ccList = json['cc'] as List;
  56. // return SerializableMessage(
  57. // name: json['name'],
  58. // from: json['from'],
  59. // // to: json['name', 'address']
  60. // to: toList.map((i) => MailAddress.fromJson(i)).toList(),
  61. // cc: ccList.map((i) => MailAddress.fromJson(i)).toList(),
  62. // // path: json['path'],
  63. // hash: json['hash'],
  64. // subject: json['subject'],
  65. // date: json['date'],
  66. // uid: json['uid'],
  67. // list: json['list'],
  68. // id: json['id'],
  69. // in_reply_to: json['in_reply_to'],
  70. // );
  71. // }
  72. // }
  73. class EmailPage extends StatefulWidget {
  74. const EmailPage({super.key});
  75. final String title = 'Emails';
  76. @override
  77. State<EmailPage> createState() => _EmailPageState();
  78. }
  79. class _EmailPageState extends State<EmailPage> {
  80. List emails = [];
  81. void _displayEmailsFromFolder(String folder) async {
  82. // Map<String, List<SerializableMessage>> messagesMap = {};
  83. List<GetThreadResponse> allEmails = []; //all the emails
  84. try {
  85. var url = Uri.http(
  86. '127.0.0.1:3001', 'sorted_threads_by_date', {'folder': folder});
  87. var response = await http.get(url);
  88. if (response.statusCode == 200) {
  89. List json = jsonDecode(response.body);
  90. for (var item in json.take(1)) {
  91. //each item in the json is a date
  92. if (item.length > 1 && item[0] is String && item[1] is List) {
  93. List<int> threadIDs = List<int>.from(item[1]);
  94. for (var threadId in threadIDs) {
  95. // print(threadId);
  96. // await fetchThreadMessages(threadId, allEmails);
  97. await fetchThreads(threadId, allEmails);
  98. }
  99. //TODO: get exact thread with new api endpoint from chosen thread?
  100. }
  101. }
  102. } else {
  103. throw Exception('Failed to load threads');
  104. }
  105. } catch (e) {
  106. print('_displayEmailsFromFolder caught error: $e');
  107. }
  108. print("Done");
  109. setState(() {
  110. emails = allEmails;
  111. });
  112. // print(emails[0]);
  113. // Print allEmails to debug its structure
  114. // print("allEmails: ${allEmails[0].messages}");
  115. // Convert allEmails to a list
  116. }
  117. // }
  118. Future<void> fetchThreads(
  119. //complete
  120. int threadId,
  121. List<GetThreadResponse> allEmails) async {
  122. try {
  123. var url =
  124. Uri.http('127.0.0.1:3001', 'get_thread', {'id': threadId.toString()});
  125. var response = await http.get(url);
  126. // print(response.body);
  127. if (response.statusCode == 200) {
  128. Map<String, dynamic> messagesJson = jsonDecode(response.body);
  129. // print(messagesJson);
  130. // List<String> messagesofThread = messagesJson['messages'];
  131. // messagesJson.map((mj) => GetThreadResponse.fromJson(mj)).toList();
  132. // List<GetThreadResponse> messages = messagesJson.values.map((mj) {
  133. // return GetThreadResponse.fromJson(mj as Map<String, dynamic>);
  134. // }).toList();
  135. // List<GetThreadResponse> thread =
  136. // messagesJson.map((mj) => GetThreadResponse.fromJson(mj)).toList();
  137. GetThreadResponse threadResponse =
  138. GetThreadResponse.fromJson(messagesJson);
  139. allEmails.add(
  140. threadResponse); //adds all the messages of the thread into all emails
  141. //perhaps should change
  142. } else {
  143. throw Exception(
  144. 'Failed to fetch thread messages for thread ID: $threadId');
  145. }
  146. } catch (e) {
  147. print('Error fetching thread messages: $e');
  148. }
  149. }
  150. // Future<void> fetchThreadMessages(
  151. // int threadId, List<SerializableMessage> allEmails) async {
  152. // try {
  153. // var url = Uri.http(
  154. // '127.0.0.1:3001', 'get_thread_messages', {'id': threadId.toString()});
  155. // var response = await http.get(url);
  156. // if (response.statusCode == 200) {
  157. // List<dynamic> messagesJson = jsonDecode(response.body);
  158. // List<SerializableMessage> messages =
  159. // messagesJson.map((mj) => SerializableMessage.fromJson(mj)).toList();
  160. // allEmails.addAll(messages);
  161. // } else {
  162. // throw Exception(
  163. // 'Failed to fetch thread messages for thread ID: $threadId');
  164. // }
  165. // } catch (e) {
  166. // print('Error fetching thread messages: $e');
  167. // }
  168. // }
  169. Future<String> _getEmailContent(List<String> IDs) async {
  170. String content = r"""
  171. """;
  172. try {
  173. for (var id in IDs) {
  174. var url = Uri.http('127.0.0.1:3001', 'email', {'id': id});
  175. var response = await http.get(url);
  176. if (response.statusCode == 200) {
  177. content += response.body;
  178. }
  179. }
  180. } catch (e) {
  181. print('_getEmailContent caught error: $e');
  182. }
  183. print(content);
  184. return content;
  185. }
  186. Future<List<Widget>> _getDrawerItems() async {
  187. List<String> drawerItems = [];
  188. try {
  189. var url = Uri.http('127.0.0.1:3001', 'folders');
  190. var response = await http.get(url);
  191. drawerItems = List<String>.from(json.decode(response.body));
  192. } catch (e) {
  193. print('_getDrawerItems caught error: $e');
  194. }
  195. List<Widget> drawerWidgets = [];
  196. for (String item in drawerItems) {
  197. drawerWidgets.add(
  198. ListTile(
  199. leading: Icon(Icons.mail),
  200. title: Text(item),
  201. onTap: () {
  202. _displayEmailsFromFolder(item);
  203. Navigator.pop(context);
  204. },
  205. ),
  206. );
  207. }
  208. return drawerWidgets;
  209. }
  210. @override
  211. Widget build(BuildContext context) {
  212. return Scaffold(
  213. appBar: AppBar(
  214. backgroundColor: Theme.of(context).colorScheme.inversePrimary,
  215. title: Text(widget.title),
  216. ),
  217. drawer: Drawer(
  218. child: FutureBuilder<List<Widget>>(
  219. future:
  220. _getDrawerItems(), // call the async function to get the future
  221. builder:
  222. (BuildContext context, AsyncSnapshot<List<Widget>> snapshot) {
  223. if (snapshot.connectionState == ConnectionState.waiting) {
  224. // While data is loading, show a progress indicator
  225. return Center(child: CircularProgressIndicator());
  226. } else if (snapshot.hasError) {
  227. // If something went wrong, show an error message
  228. return Center(child: Text('Error: ${snapshot.error}'));
  229. } else {
  230. // When data is fetched successfully, display the items
  231. return ListView(
  232. padding: EdgeInsets.zero,
  233. children:
  234. snapshot.data!, // Unwrap the data once confirmed it's there
  235. );
  236. }
  237. },
  238. ),
  239. ),
  240. body: EmailListScreen(
  241. emails: emails,
  242. getEmailContent: _getEmailContent,
  243. // getJsonEmail: _getThreadMessagesJson
  244. ),
  245. );
  246. }
  247. }
  248. class EmailListScreen extends StatelessWidget {
  249. //this is the bulding of the drawer with all emails
  250. // try to only get the subject and id, date, sender to make it faster
  251. final List emails;
  252. final Future<String> Function(List<String>) getEmailContent;
  253. EmailListScreen({
  254. required this.emails,
  255. required this.getEmailContent,
  256. });
  257. // instead of getting the entire email, just the from, text, subject, and id
  258. @override
  259. Widget build(BuildContext context) {
  260. return Scaffold(
  261. appBar: AppBar(
  262. title: Text('Emails'),
  263. ),
  264. body: ListView.separated(
  265. itemCount: emails.length,
  266. itemBuilder: (context, index) {
  267. return ListTile(
  268. title: Text(emails[index].from_name,
  269. style: TextStyle(fontWeight: FontWeight.bold)),
  270. subtitle: Column(
  271. crossAxisAlignment: CrossAxisAlignment.start,
  272. children: [
  273. Text(emails[index].subject),
  274. ],
  275. ),
  276. trailing: Text(emails[index].date.toString()),
  277. //here we assign each part of json to a var, this could be changed so it only happens,
  278. // when clicking on email for modularity
  279. onTap: () async {
  280. String emailContent =
  281. await getEmailContent(emails[index].messages);
  282. String messages = emails[index].messages.toString();
  283. String fromName = emails[index].from_name.toString();
  284. String fromAddress = emails[index].from_address.toString();
  285. String to = emails[index].to.toString();
  286. // String cc = emails[index].cc.toString();
  287. // String hash = emails[index].hash.toString();
  288. String subject = emails[index].subject.toString();
  289. String date = emails[index].date.toString();
  290. // String uid = emails[index].uid.toString();
  291. // String list = emails[index].list.toString();
  292. String id = emails[index].id.toString();
  293. // String in_reply_to = emails[index].in_reply_to.toString();
  294. Navigator.push(
  295. context,
  296. MaterialPageRoute(
  297. builder: (context) => EmailView(
  298. emailContent: emailContent,
  299. from: fromAddress,
  300. name: fromName,
  301. to: to,
  302. // cc: cc,
  303. // hash: hash,
  304. subject: subject,
  305. date: date,
  306. // uid: uid,
  307. // list: list,
  308. id: id,
  309. // in_reply_to: in_reply_to,
  310. )
  311. //nada hpta
  312. ),
  313. );
  314. });
  315. },
  316. separatorBuilder: (context, index) {
  317. return Divider();
  318. },
  319. ),
  320. );
  321. }
  322. }
  323. class EmailView extends StatefulWidget {
  324. final String emailContent;
  325. // final String jsonEmail;
  326. final String from;
  327. final String name;
  328. final String to;
  329. // final String to;
  330. // final String cc;
  331. // final String hash;
  332. final String subject;
  333. final String date;
  334. // final String uid;
  335. // final String list;
  336. final String id;
  337. // final String in_reply_to;
  338. const EmailView({
  339. Key? key,
  340. required this.emailContent,
  341. // required this.jsonEmail,
  342. required this.from,
  343. required this.name,
  344. required this.to,
  345. // required this.to,
  346. // required this.cc,
  347. // required this.hash,
  348. required this.subject,
  349. required this.date,
  350. // required this.uid,
  351. // required this.list,
  352. required this.id,
  353. // required this.in_reply_to
  354. }) : super(key: key);
  355. @override
  356. _EmailViewState createState() => _EmailViewState();
  357. }
  358. class _EmailViewState extends State<EmailView> {
  359. late Key iframeKey;
  360. late String currentContent;
  361. late String viewTypeId;
  362. TextEditingController _jumpController = TextEditingController();
  363. @override
  364. void initState() {
  365. super.initState();
  366. String currentContent = widget.emailContent;
  367. viewTypeId = "iframe-${DateTime.now().millisecondsSinceEpoch}";
  368. _registerViewFactory(currentContent);
  369. }
  370. void _registerViewFactory(String currentContent) {
  371. setState(() {
  372. viewTypeId = 'iframe-${DateTime.now().millisecondsSinceEpoch}';
  373. ui.platformViewRegistry.registerViewFactory(
  374. viewTypeId,
  375. (int viewId) => html.IFrameElement()
  376. ..width = '100%'
  377. ..height = '100%'
  378. ..srcdoc = currentContent
  379. ..style.border = 'none');
  380. });
  381. }
  382. void _scrollToNumber(String spanId) {
  383. AugmentClasses.handleJump(spanId);
  384. }
  385. // void _invisibility(String )
  386. @override
  387. Widget build(BuildContext context) {
  388. // print(currentContent);
  389. return Scaffold(
  390. appBar: AppBar(
  391. title: Text(widget.name),
  392. ),
  393. body: Column(
  394. children: [
  395. EmailToolbar(
  396. onJumpToSpan: _scrollToNumber,
  397. onButtonPressed: () => {},
  398. // AugmentClasses.handleJump(viewTypeId, '1');
  399. // print("button got pressed?");
  400. // _registerViewFactory(r"""
  401. // <h1>Welcome to My Website</h1>
  402. // <p>This is a simple HTML page.</p>
  403. // <h2>What is HTML?</h2>
  404. // <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>
  405. // <h3>Here's a simple list:</h3>
  406. // <ul>
  407. // <li>HTML elements are the building blocks of HTML pages</li>
  408. // <li>HTML uses tags like <code>&lt;tag&gt;</code> to organize and format content</li>
  409. // <li>CSS is used with HTML to style pages</li>
  410. // </ul>
  411. // <p>Copyright © 2023</p>
  412. // """);
  413. // print("change");
  414. // widget.emailContent = r"
  415. // "
  416. // },
  417. ),
  418. Row(
  419. // title of email
  420. children: [
  421. Text(
  422. widget.subject,
  423. style: TextStyle(fontSize: 30),
  424. ),
  425. ],
  426. ),
  427. Row(
  428. children: [
  429. Text(
  430. 'from ${widget.name}',
  431. style: TextStyle(fontSize: 18),
  432. ),
  433. Text(
  434. '<${widget.from}>',
  435. style: TextStyle(fontSize: 18),
  436. ),
  437. Spacer(),
  438. Text(
  439. '${widget.date}',
  440. textAlign: TextAlign.right,
  441. )
  442. ],
  443. ),
  444. // TODO: make a case where if one of these is the user's email it just says me :)))))
  445. Row(
  446. children: [
  447. Text(
  448. 'to ${widget.to.toString()}',
  449. style: TextStyle(fontSize: 15),
  450. )
  451. ],
  452. ),
  453. Expanded(
  454. child: HtmlElementView(
  455. key: UniqueKey(),
  456. viewType: viewTypeId,
  457. ),
  458. ),
  459. ],
  460. ));
  461. }
  462. }