import 'package:flutter/material.dart'; import 'api_service.dart'; import 'structs.dart'; import 'package:html2md/html2md.dart' as html2md; import 'package:markdown_widget/markdown_widget.dart'; import 'package:markdown/markdown.dart' as md; class CollapsableEmails extends StatefulWidget { final List thread; // email id's in the form xyz@gmail.com final List threadHTML; final String threadIDs; CollapsableEmails( {required this.thread, required this.threadHTML, required this.threadIDs}); @override State createState() => _CollapsableEmailsState(); } class _CollapsableEmailsState extends State { List emailsHTML = []; //html of the emails in the thread // build attachments with the forldar name and id Set _expandedEmails = {}; //open emails List emailsInThread = []; bool _isLoaded = false; List hirarchy = ["h1", "h2", "h3", "h4", "h5", "h6", "p"]; Map hirarchyDict = { "h1": 1, "h2": 2, "h3": 3, "h4": 4, "h5": 6, "h6": 7, "p": 8, "ul": 8, "li": 8, }; List tagsCollected = []; String markdown = ''; List> sentinel = []; int level = 0; AugmentTree zoomTreeRoot = AugmentTree(); late AugmentTree currentZoomNode; bool zoomOut = false; bool zoomIn = true; @override void initState() { super.initState(); _markdownConverter(); _serializableData(widget.threadIDs); // this _markdown2Tree(markdown); _buildForZooms(); } @override void dispose() { super.dispose(); } void _markdownConverter() async { markdown = html2md.convert(widget.threadHTML[0]); } void _add2Tree(AugmentTree tree, md.Element node2add) { // adds node to its corresponding place AugmentTree newNode = AugmentTree(); newNode.setData(node2add.textContent); newNode.ogTag = node2add.tag; // cases, //1. a node that comes is lower than the root.children last, if so it goes beneath it if (tree.children.isEmpty) { // new level to be created when totally empty tree.children.add(newNode); newNode.parent = tree; } else if (tree.children.isNotEmpty && tree.children.last.ogTag.isNotEmpty) { if ((hirarchyDict[node2add.tag] ?? -1) < // e.g. new node is h1 and old is h2, heapify (hirarchyDict[tree.children.last.ogTag] ?? -1)) { //have to figure out the borthers //assuming it all goes right if ((hirarchyDict[node2add.tag] ?? -1) == -1 || (hirarchyDict[tree.children.last.ogTag] ?? -1) == -1) { print( 'failed and got -1 at _add2Tree \n ${hirarchyDict[node2add.tag] ?? -1} < ${hirarchyDict[tree.children.last.ogTag] ?? -1}'); return; } else if (tree.children.last.parent == null) { // becomes the new top level for (AugmentTree brother in tree.children) { brother.parent = newNode; } tree.children = [newNode]; } else { newNode.parent = tree; tree.children.add(newNode); } } else if ((hirarchyDict[node2add.tag] ?? -1) > // go down e.g. new node is h3 and old is h2 or something (hirarchyDict[tree.children.last.ogTag] ?? -1)) { if ((hirarchyDict[node2add.tag] ?? -1) == -1 || (hirarchyDict[tree.children.last.ogTag] ?? -1) == -1) { print( 'failed and got -1 at _add2Tree \n ${hirarchyDict[node2add.tag] ?? -1} > ${hirarchyDict[tree.children.last.ogTag] ?? -1}'); print("-1 ${tree.children.last.ogTag}"); return; } _add2Tree(tree.children.last, node2add); } else if ((hirarchyDict[node2add.tag] ?? -1) == (hirarchyDict[tree.children.last.ogTag] ?? -1)) { tree.children.add(newNode); newNode.parent = tree; } } } void _markdown2Tree(String text) { print("started markdown2tree"); final List nakedList = md.Document().parseLines(text.split('\n')); for (var node in nakedList) { //maybe do an add function, but isn't this it? if (node is md.Element) { // print(node.textContent); AugmentTree temp = AugmentTree(); temp.data = node.textContent; temp.ogTag = node.tag; if (hirarchyDict.containsKey(node.tag)) { _add2Tree(zoomTreeRoot, node); } } currentZoomNode = zoomTreeRoot; if (!mounted) return; setState(() { _isLoaded = true; }); } } void _goToChildren(int index) async { final target = currentZoomNode.children[index]; if (target.children.isNotEmpty) { setState(() { currentZoomNode = target; }); } else { print("This child has no further children."); } // if (currentZoomNode.children.isNotEmpty) { // setState(() { // zoomIn = true; // zoomOut = true; // currentZoomNode = currentZoomNode.children[index]; // if (currentZoomNode.children[index].children.isEmpty) { // print('disable in'); // setState(() { // zoomIn = false; // }); // } // }); // } else { // print("disable zoom down"); // setState(() { // zoomIn = false; // }); // } } void _goToParent() async { if (currentZoomNode.parent != null) { setState(() { currentZoomNode = currentZoomNode.parent!; }); } else { print("Already at root."); } // print("parent ${currentZoomNode.parent}"); // print("parent ${currentZoomNode.parent!.parent}"); // if (currentZoomNode.parent != null) { // setState(() { // currentZoomNode = currentZoomNode.parent!; // if (currentZoomNode.parent == null) { // setState(() { // zoomOut = false; // }); // } // }); // } else if (currentZoomNode.parent == null || // currentZoomNode.parent!.parent == null) { // print("disable zoom up"); } void _serializableData(String threadID) async { emailsInThread = await ApiService().threadsInSerializable(threadID); print("done thread serializable"); if (!mounted) return; setState(() { _isLoaded = true; }); } Widget _buildForZooms({Key? key}) { if (!_isLoaded) { return const Center(child: CircularProgressIndicator()); // loading screen } final canZoomOut = currentZoomNode.parent != null; return ListView.builder( key: key, itemCount: currentZoomNode.children.length, itemBuilder: (context, index) { final childNode = currentZoomNode.children[index]; final canZoomIn = childNode.children.isNotEmpty; return Padding( padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 6.0), child: Material( elevation: 1, borderRadius: BorderRadius.circular(12), color: Theme.of(context).colorScheme.surface, surfaceTintColor: Theme.of(context).colorScheme.surfaceBright, child: Padding( padding: const EdgeInsets.all(16.0), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Wrap( spacing: 4.0, children: [ OutlinedButton( onPressed: canZoomOut ? () => _goToParent() : null, child: Icon(Icons.north_west_sharp), ), OutlinedButton( onPressed: canZoomIn ? () => _goToChildren(index) : null, child: Icon(Icons.south_east_sharp), ), ], ), SizedBox(width: 12.0), Expanded( child: MarkdownBlock( data: currentZoomNode .children[index].data, // one string of markdown config: MarkdownConfig .darkConfig, // or lightConfig depending on theme ), ), ], ), ), ), ); }, ); } @override Widget build(BuildContext context) { return _isLoaded ? Column(children: [ Expanded( child: ListView.builder( itemCount: emailsInThread.length, itemBuilder: (context, index) { final isExpanded = _expandedEmails .contains(index); //check if email is expanded return Column( children: [ ListTile( title: Text(emailsInThread[index].from), trailing: Text(emailsInThread[index].date), onTap: () { setState(() { if (isExpanded) { _expandedEmails.remove(index); } else { _expandedEmails.add(index); } }); }, ), if (isExpanded) ConstrainedBox( constraints: BoxConstraints( minHeight: 100, maxHeight: MediaQuery.of(context).size.height * 0.6), child: _buildForZooms(key: ValueKey(currentZoomNode))), Divider(), ], ); }, ), ), ]) : const Center(child: CircularProgressIndicator()); } }