clean, and update to match the web

This commit is contained in:
Juan Marulanda De Los Rios 2025-06-18 21:09:02 -04:00
parent c1afc8875e
commit 3410007f55
3 changed files with 217 additions and 87 deletions

View File

@ -1,6 +1,5 @@
import 'package:crab_ui/api_service.dart'; import 'package:crab_ui/api_service.dart';
import 'package:crab_ui/attachmentDownload.dart'; import 'package:crab_ui/attachmentDownload.dart';
import 'package:crab_ui/collapsableEmails.dart';
import 'package:crab_ui/structs.dart'; import 'package:crab_ui/structs.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:pointer_interceptor/pointer_interceptor.dart'; import 'package:pointer_interceptor/pointer_interceptor.dart';

View File

@ -7,13 +7,19 @@ 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
final List<String> threadHTML; // final List<String> threadHTML;
final List<String> threadMarkdown;
final String threadIDs; final String threadIDs;
final String? targetJumpNumbering;
final String? targetViewspecs;
CollapsableEmails( CollapsableEmails(
{required this.thread, {required this.thread,
required this.threadHTML, required this.threadMarkdown,
required this.threadIDs}); required this.threadIDs,
this.targetJumpNumbering,
this.targetViewspecs,
});
@override @override
State<CollapsableEmails> createState() => _CollapsableEmailsState(); State<CollapsableEmails> createState() => _CollapsableEmailsState();
@ -40,21 +46,41 @@ class _CollapsableEmailsState extends State<CollapsableEmails> {
}; };
List<String> tagsCollected = []; List<String> tagsCollected = [];
String markdown = ''; List<String> allMarkdown = [];
List<List<String>> sentinel = []; List<List<String>> sentinel = [];
int level = 0; int level = 0;
AugmentTree zoomTreeRoot = AugmentTree(); AugmentTree zoomTreeRoot = AugmentTree();
late AugmentTree currentZoomNode; // late AugmentTree currentZoomNode;
late List<AugmentTree> currentZoomTree = [];
bool zoomOut = false; bool zoomOut = false;
bool zoomIn = true; bool zoomIn = true;
late List<AugmentTree> threadNodes = [];
static bool leftNumbering = false;
static bool rightNumbering = true;
bool showWhole = false;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_markdownConverter(); threadNodes = [];
currentZoomTree = [];
// _markdownConverter();
_serializableData(widget.threadIDs); // this _serializableData(widget.threadIDs); // this
_markdown2Tree(markdown); _markdown2Tree(widget.threadMarkdown);
_buildForZooms(); }
@override
void didUpdateWidget(covariant CollapsableEmails oldWidget) {
// TODO: implement didUpdateWidget
super.didUpdateWidget(oldWidget);
if (widget.targetJumpNumbering != null &&
widget.targetJumpNumbering != oldWidget.targetJumpNumbering) {
_handleJump(widget.targetJumpNumbering!);
}
if (widget.targetViewspecs != null &&
widget.targetViewspecs != oldWidget.targetViewspecs) {
_handleViewspecs(widget.targetViewspecs!);
}
} }
@override @override
@ -62,10 +88,6 @@ class _CollapsableEmailsState extends State<CollapsableEmails> {
super.dispose(); super.dispose();
} }
void _markdownConverter() async {
markdown = html2md.convert(widget.threadHTML[0]);
}
void _add2Tree(AugmentTree tree, md.Element node2add) { void _add2Tree(AugmentTree tree, md.Element node2add) {
// adds node to its corresponding place // adds node to its corresponding place
AugmentTree newNode = AugmentTree(); AugmentTree newNode = AugmentTree();
@ -119,83 +141,67 @@ class _CollapsableEmailsState extends State<CollapsableEmails> {
} }
} }
void _markdown2Tree(String text) { void _markdown2Tree(List<String> text) {
print("started markdown2tree"); print("started markdown2tree");
final List<md.Node> nakedList = md.Document().parseLines(text.split('\n')); for (int emailsMD = 0; emailsMD < text.length; emailsMD++) {
final List<md.Node> nakedList =
for (var node in nakedList) { md.Document().parseLines(text[emailsMD].split('\n'));
//maybe do an add function, but isn't this it? zoomTreeRoot = AugmentTree();
if (node is md.Element) { for (var node in nakedList) {
// print(node.textContent); //maybe do an add function, but isn't this it?
AugmentTree temp = AugmentTree(); if (node is md.Element) {
temp.data = node.textContent; AugmentTree temp = AugmentTree();
temp.ogTag = node.tag; temp.data = node.textContent;
if (hirarchyDict.containsKey(node.tag)) { temp.ogTag = node.tag;
_add2Tree(zoomTreeRoot, node); if (node.tag == 'h1') {
// make this O(1)
_add2Tree(zoomTreeRoot, node);
} else if (node.tag == 'h2') {
// i dont add any since i dont have it, maybe the function makes sense
_add2Tree(zoomTreeRoot, node); // fix this
} else if (node.tag == 'h3') {
_add2Tree(zoomTreeRoot, node);
} else if (node.tag == 'h4') {
_add2Tree(zoomTreeRoot, node); // change to temp
} else if (node.tag == 'h5') {
_add2Tree(zoomTreeRoot, node);
} else if (node.tag == 'h6') {
_add2Tree(zoomTreeRoot, node); // fix this
} else if (node.tag == 'p' || node.tag == 'ul' || node.tag == 'li') {
_add2Tree(zoomTreeRoot, node); // fix this
}
} }
} }
zoomTreeRoot.addNumbering();
currentZoomNode = zoomTreeRoot; threadNodes.add(zoomTreeRoot);
if (!mounted) return; currentZoomTree.add(zoomTreeRoot);
setState(() {
_isLoaded = true;
});
} }
if (!mounted) return;
setState(() {
_isLoaded = true;
});
} }
void _goToChildren(int index) async { void _goToChildren(int indexThread, int index) async {
final target = currentZoomNode.children[index]; final target = currentZoomTree[indexThread].children[index];
if (target.children.isNotEmpty) { if (target.children.isNotEmpty) {
setState(() { setState(() {
currentZoomNode = target; currentZoomTree[indexThread] = target;
}); });
} 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(int indexThread) async {
if (currentZoomNode.parent != null) { if (currentZoomTree[indexThread].parent != null) {
setState(() { setState(() {
currentZoomNode = currentZoomNode.parent!; currentZoomTree[indexThread] = currentZoomTree[indexThread].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 {
@ -207,19 +213,23 @@ class _CollapsableEmailsState extends State<CollapsableEmails> {
}); });
} }
Widget _buildForZooms({Key? key}) { Widget _buildForZooms(int indexThread) {
// IF I GIVE IT THE INDEX????
if (!_isLoaded) { if (!_isLoaded) {
return const Center(child: CircularProgressIndicator()); // loading screen return const Center(child: CircularProgressIndicator()); // loading screen
} }
final canZoomOut = currentZoomNode.parent != null;
final AugmentTree currentZoomNodeForThisEmail =
currentZoomTree[indexThread];
final canZoomOut = currentZoomNodeForThisEmail.parent != null;
return ListView.builder( return ListView.builder(
key: key, itemCount: currentZoomNodeForThisEmail.children.length,
itemCount: currentZoomNode.children.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
final childNode = currentZoomNode.children[index]; final childNode = currentZoomNodeForThisEmail.children[index];
final canZoomIn = childNode.children.isNotEmpty; final canZoomIn = childNode.children.isNotEmpty;
// currentZoomNodeForThisEmail.addNumbering();
return Padding( return Padding(
padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 6.0), padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 6.0),
child: Material( child: Material(
@ -236,25 +246,46 @@ class _CollapsableEmailsState extends State<CollapsableEmails> {
spacing: 4.0, spacing: 4.0,
children: [ children: [
OutlinedButton( OutlinedButton(
onPressed: canZoomOut ? () => _goToParent() : null, onPressed:
canZoomOut ? () => _goToParent(indexThread) : null,
child: Icon(Icons.north_west_sharp), child: Icon(Icons.north_west_sharp),
), ),
OutlinedButton( OutlinedButton(
onPressed: onPressed: canZoomIn
canZoomIn ? () => _goToChildren(index) : null, ? () => _goToChildren(indexThread, index)
: null,
child: Icon(Icons.south_east_sharp), child: Icon(Icons.south_east_sharp),
), ),
], ],
), ),
SizedBox(width: 12.0), SizedBox(width: 12.0),
if (leftNumbering)
Padding(
padding: const EdgeInsets.fromLTRB(0, 10, 5, 0),
child: Text(
childNode.numbering,
style:
TextStyle(color: Color(Colors.purple[400]!.value)),
),
),
Expanded( Expanded(
child: MarkdownBlock( child: MarkdownBlock(
data: currentZoomNode data: childNode.data,
.children[index].data, // one string of markdown // data: currentZoomNode
// .children[index].data, // one string of markdown
config: MarkdownConfig config: MarkdownConfig
.darkConfig, // or lightConfig depending on theme .darkConfig, // or lightConfig depending on theme
), ),
), ),
if (rightNumbering)
Padding(
padding: const EdgeInsets.fromLTRB(5, 10, 5, 0),
child: Text(
childNode.numbering,
style:
TextStyle(color: Color(Colors.purple[400]!.value)),
),
),
], ],
), ),
), ),
@ -263,6 +294,100 @@ class _CollapsableEmailsState extends State<CollapsableEmails> {
}, },
); );
} }
void _handleJump(String queryNumbering) {
print(queryNumbering);
if (queryNumbering.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Please enter a numbering to jump to.')),
);
return;
}
final int targetEmailIndex = _expandedEmails.first;
if (targetEmailIndex >= threadNodes.length) {
// Error handling
return;
}
final AugmentTree rootOfCurrentEmail = threadNodes[targetEmailIndex];
final AugmentTree? foundNode =
_findNodeByNumbering(rootOfCurrentEmail, queryNumbering);
if (foundNode != null) {
setState(() {
currentZoomTree[targetEmailIndex] = foundNode; // Update the state
});
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Numbering "$queryNumbering" not found.')),
);
}
}
void _handleViewspecs(String viewspecsQuery) {
print(viewspecsQuery);
if (viewspecsQuery.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Please enter the viewspecs.')),
);
return;
}
final int targetEmailIndex = _expandedEmails.first;
if (targetEmailIndex >= threadNodes.length) {
// Error handling
return;
}
if (viewspecsQuery.contains('n')) {
setState(() {
leftNumbering = false; // Update the state
rightNumbering = false;
});
}
if (viewspecsQuery.contains('m')) {
setState(() {
rightNumbering = true;
leftNumbering = true;
});
}
if (viewspecsQuery.contains('H')) {
setState(() {
leftNumbering = !leftNumbering;
});
}
if (viewspecsQuery.contains('G')) {
setState(() {
rightNumbering = !rightNumbering;
});
}
if (viewspecsQuery.contains('w')) {
setState(() {
showWhole = true;
});
}
// else {
// ScaffoldMessenger.of(context).showSnackBar(
// SnackBar(content: Text('Numbering "$viewspecsQuery" not found.')),
// );
// }
}
AugmentTree? _findNodeByNumbering(AugmentTree root, String numbering) {
//recursively finds the node you mentioned
// to find the AugmentTree node corresponding to the `numbering`.
if (root.numbering == numbering) {
return root;
}
for (var child in root.children) {
final found = _findNodeByNumbering(child, numbering);
if (found != null) {
return found;
}
}
return null;
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -294,9 +419,10 @@ class _CollapsableEmailsState extends State<CollapsableEmails> {
constraints: BoxConstraints( constraints: BoxConstraints(
minHeight: 100, minHeight: 100,
maxHeight: maxHeight:
MediaQuery.of(context).size.height * 0.6), MediaQuery.of(context).size.height * 0.6,
child: ),
_buildForZooms(key: ValueKey(currentZoomNode))), child: _buildForZooms(index),
),
Divider(), Divider(),
], ],
); );

View File

@ -43,6 +43,9 @@ class _EmailViewState extends State<EmailView> {
void _scrollToNumber(String spanId) { void _scrollToNumber(String spanId) {
// AugmentClasses.handleJump(spanId); // AugmentClasses.handleJump(spanId);
} }
void _viewSpecs(String command){
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -56,7 +59,9 @@ class _EmailViewState extends State<EmailView> {
children: [ children: [
EmailToolbar( EmailToolbar(
onButtonPressed: () => {}, onButtonPressed: () => {},
onJumpToSpan: _scrollToNumber onJumpToNumbering: _scrollToNumber,
onViewspecs: _viewSpecs,
), ),
Row( Row(
children: [ children: [
@ -98,7 +103,7 @@ class _EmailViewState extends State<EmailView> {
Expanded( Expanded(
child: CollapsableEmails( child: CollapsableEmails(
thread: widget.messages, thread: widget.messages,
threadHTML: widget.emailContent, threadMarkdown: widget.emailContent,
threadIDs: widget.id, threadIDs: widget.id,
), ),
), ),