Compare commits

...

2 Commits

Author SHA1 Message Date
160fe25be3 works, and optimized time for checking the node type 2025-06-11 15:12:58 -04:00
69b5408f73 dynamicish sizing and cleanup 2025-06-10 17:26:31 -04:00
2 changed files with 291 additions and 99 deletions

View File

@ -1,6 +1,9 @@
import 'structs.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_widget_from_html/flutter_widget_from_html.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 { class CollapsableEmails extends StatefulWidget {
final List<String> thread; // email id's in the form xyz@gmail.com final List<String> thread; // email id's in the form xyz@gmail.com
@ -20,29 +23,287 @@ class _CollapsableEmailsState extends State<CollapsableEmails> {
List<String> emailsHTML = []; //html of the emails in the thread List<String> emailsHTML = []; //html of the emails in the thread
// build attachments with the forldar name and id // build attachments with the forldar name and id
Set<int> _expandedEmails = {}; //open emails Set<int> _expandedEmails = {}; //open emails
List viewtypeIDs = []; //IDs of the viewtypes, order matters
List heightOfViewTypes = []; //the height of each viewtype
List<SerializableMessage> emailsInThread = []; List<SerializableMessage> emailsInThread = [];
bool _isLoaded = false; bool _isLoaded = false;
List<String> hirarchy = ["h1", "h2", "h3", "h4", "h5", "h6", "p"];
Map<String, int> hirarchyDict = {
"h1": 1,
"h2": 2,
"h3": 3,
"h4": 4,
"h5": 6,
"h6": 7,
"p": 8,
"ul": 8,
"li": 8,
};
List<String> tagsCollected = [];
String markdown = '';
List<List<String>> sentinel = [];
int level = 0;
AugmentTree zoomTreeRoot = AugmentTree();
late AugmentTree currentZoomNode;
bool zoomOut = false;
bool zoomIn = true;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
//html _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<md.Node> 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return _isLoaded
body: ListView( ? Column(children: [
children: [ Expanded(
HtmlWidget( child: ListView.builder(
widget.threadHTML[0], itemCount: emailsInThread.length,
// renderMode: RenderMode.listView, 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());
} }
} }

View File

@ -1,9 +1,6 @@
import 'dart:collection';
import 'dart:js_interop'; import 'dart:js_interop';
import 'dart:js_interop_unsafe';
import 'package:web/web.dart' as web; import 'package:web/web.dart' as web;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'dart:ui_web' as ui;
import 'api_service.dart'; import 'api_service.dart';
import 'structs.dart'; import 'structs.dart';
import 'package:html2md/html2md.dart' as html2md; import 'package:html2md/html2md.dart' as html2md;
@ -117,13 +114,6 @@ class _CollapsableEmailsState extends State<CollapsableEmails> {
newNode.parent = tree; newNode.parent = tree;
tree.children.add(newNode); tree.children.add(newNode);
} }
// } else{ // so the node should go high
// _add2Tree(tree.children.last, node2add);
// }
//maybe?
// } else {
// }
} else if ((hirarchyDict[node2add.tag] ?? } else if ((hirarchyDict[node2add.tag] ??
-1) > // go down e.g. new node is h3 and old is h2 or something -1) > // go down e.g. new node is h3 and old is h2 or something
(hirarchyDict[tree.children.last.ogTag] ?? -1)) { (hirarchyDict[tree.children.last.ogTag] ?? -1)) {
@ -135,8 +125,6 @@ class _CollapsableEmailsState extends State<CollapsableEmails> {
return; return;
} }
// print("> ${node2add.tag}");
_add2Tree(tree.children.last, node2add); _add2Tree(tree.children.last, node2add);
} else if ((hirarchyDict[node2add.tag] ?? -1) == } else if ((hirarchyDict[node2add.tag] ?? -1) ==
(hirarchyDict[tree.children.last.ogTag] ?? -1)) { (hirarchyDict[tree.children.last.ogTag] ?? -1)) {
@ -194,32 +182,6 @@ class _CollapsableEmailsState extends State<CollapsableEmails> {
this.sentinel = [h1List, h2List, h3List, h4List, h5List, h6List, pList]; this.sentinel = [h1List, h2List, h3List, h4List, h5List, h6List, pList];
sentinel.removeWhere((hList) => hList.isEmpty); sentinel.removeWhere((hList) => hList.isEmpty);
// print('algorithm adding: ');
// print("first layer: ${zoomTreeRoot.children}");
// print("first node: ${zoomTreeRoot.children[0].data}"); //good
// print("second layer: ${zoomTreeRoot.children.last.children}"); //good
// print(
// "first node: ${zoomTreeRoot.children.last.children.first.data}"); //good
// for (int n = 1; n < zoomTreeRoot.children.last.children.length - 1; n++) {
// //good
// print(zoomTreeRoot.children.last.children[n].data);
// }
// print("last node: ${zoomTreeRoot.children.last.children.last.data}"); //good
// print("third layer");
// for (int thirdLayer = 0;
// thirdLayer < zoomTreeRoot.children.last.children.length;
// thirdLayer++) {
// print(zoomTreeRoot.children.last.children[thirdLayer].children);
// }
// print("third layer contents first"); //not sure
// print(zoomTreeRoot.children.last.children[5].children[0].data); //good
// for (int contentsOf4 = 1;
// contentsOf4 < zoomTreeRoot.children.last.children[5].children.length;
// contentsOf4++) {
// print(zoomTreeRoot.children.last.children[5].children[contentsOf4].data);
// }
currentZoomNode = zoomTreeRoot; currentZoomNode = zoomTreeRoot;
if (!mounted) return; if (!mounted) return;
setState(() { setState(() {
@ -253,50 +215,16 @@ class _CollapsableEmailsState extends State<CollapsableEmails> {
} else { } else {
print("This child has no further children."); 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 { void _goToParent() async {
if (currentZoomNode.parent != null) { if (currentZoomNode.parent != null) {
setState(() { setState(() {
currentZoomNode = currentZoomNode.parent!; currentZoomNode = currentZoomNode.parent!;
}); });
} else { } else {
print("Already at root."); 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 { void _serializableData(String threadID) async {
@ -449,16 +377,19 @@ class _CollapsableEmailsState extends State<CollapsableEmails> {
}, },
), ),
if (isExpanded) if (isExpanded)
SizedBox( ConstrainedBox(
height: 800, constraints: BoxConstraints(
child: minHeight: 100,
_buildForZooms(key: ValueKey(currentZoomNode))), maxHeight: MediaQuery.of(context).size.height * 0.6,
),
child: _buildForZooms(key: ValueKey(currentZoomNode)),
),
Divider(), Divider(),
], ],
); );
}, },
), ),
) ),
]) ])
: const Center(child: CircularProgressIndicator()); : const Center(child: CircularProgressIndicator());
} }