filterButton(
+ context, Function(String) onFilteringCallback) async {
//this is literally ctrl+F :skull:
//idea is to search in file, extract the tags that contain these
//words and highlight, then when zoom, you just jump to that paragraph
+ bool? numbering = false;
+ String filterQueue = '';
- // AugmentClasses.disableIframePointerEvents();
await showDialog(
- context: context,
- builder: (context) => Container(
- height: 150,
- width: 300,
- child: AlertDialog(
- title: Text('Filter'),
- content: Container(
- width: 400, // Set the width to simulate the Windows style
- child: Column(
- mainAxisSize: MainAxisSize.min,
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text('Set filter:'),
- SizedBox(
- width: 175,
- child: TextField(
- maxLines: 1,
- decoration: InputDecoration(
- border: OutlineInputBorder(),
- ),
+ context: context,
+ builder: (BuildContext dialogContext) {
+ return StatefulBuilder(builder:
+ (BuildContext statefulBuilderContext, StateSetter setState) {
+ return AlertDialog(
+ title: const Text('Filter'),
+ content: SizedBox(
+ width: 400, // Set the width to simulate the Windows style
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ const Text('Set filter:'),
+ SizedBox(
+ width: 175,
+ child: TextField(
+ autofocus: true,
+ maxLines: 1,
+ decoration: const InputDecoration(
+ border: OutlineInputBorder(),
),
- )
- ],
- )))));
+ onChanged: (value) {
+ print(value);
+ filterQueue = value;
+ },
+ ),
+ ),
+ SizedBox(
+ height: 10,
+ ),
+ Column(children: [
+ Row(children: [
+ Checkbox(
+ value: numbering,
+ activeColor:
+ Theme.of(context).colorScheme.tertiary,
+ onChanged: (newBool) {
+ setState(() {
+ numbering = newBool;
+ });
+ }),
+ Text("Start at top of file")
+ ]),
+ ]),
+ ])),
+ actions: [
+ ElevatedButton(
+ onPressed: () {
+ Navigator.of(context).pop();
+ },
+ child: Text("Cancel")),
+ ElevatedButton(
+ onPressed: () {
+ Navigator.of(context).pop({
+ 'filterQueue': filterQueue,
+ 'numbering': numbering,
+ });
+ },
+ child: Text("Apply")),
+ ],
+ );
+ });
+ },
+ ).then((result) {
+ if (result != null) {
+ print("filter done $result");
+ final String query = result['filterQueue'];
+ onFilteringCallback(query);
+ } else {
+ print('cancelled');
+ }
+ });
}
-
- // static void disableIframePointerEvents() {
- // //pretty sure these dont work
- // // final iframes = html.document.getElementsByTagName('iframe');
- // final iframes = web.document.getElementsByTagName('iframe');
- // for (var iframe in iframes) {
- // // if (iframe is html.Element) {
- // // iframe.style.pointerEvents = 'none'; // Disable pointer events
- // if (iframe is web.Element) {
- // iframe.
- // }
- // }
- // }
-
- // static void enableIframePointerEvents() {
- // // final iframes = html.document.getElementsByTagName('iframe');
- // final iframes = html.document.getElementsByTagName('iframe');
-
- // for (var iframe in iframes) {
- // if (iframe is html.Element) {
- // iframe.style.pointerEvents = 'auto'; // Re-enable pointer events
- // }
- // }
- // }
}
diff --git a/lib/collapsableEmails.dart b/lib/collapsableEmails.dart
index ceed92a..e8b144d 100644
--- a/lib/collapsableEmails.dart
+++ b/lib/collapsableEmails.dart
@@ -1,132 +1,3 @@
-import 'dart:js_interop';
-import 'package:web/web.dart' as web;
-import 'package:flutter/material.dart';
-import 'dart:ui_web' as ui;
-import 'api_service.dart';
-import 'structs.dart';
-
-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 viewtypeIDs = []; //IDs of the viewtypes, order matters
- List heightOfViewTypes = []; //the height of each viewtype
- List emailsInThread = [];
- bool _isLoaded = false;
-
- @override
- void initState() {
- // TODO: implement initState
- super.initState();
- _registerViewFactory(widget.threadHTML);
- _serializableData(widget.threadIDs);
- }
-
- void _registerViewFactory(List currentContent) async {
- // setState(() { //update to do item per item
- // each item to have itsviewtype ID
- // is this necessarey here??
-
- //could just move to collapsable
-
- for (var emailHTML in widget.threadHTML) {
- String viewTypeId = 'email-${DateTime.now().millisecondsSinceEpoch}';
-
- final ghost = web.document.createElement('div') as web.HTMLDivElement
- ..style.visibility = 'hidden'
- ..style.position = 'absolute'
- ..style.width = '100%'
- ..style.overflow = 'auto'
- ..innerHTML = emailHTML
- .toJS; // temporarily index because it has to do all of them
- web.document.body?.append(ghost);
- await Future.delayed(Duration(milliseconds: 10));
-
- final heightOfEmail = ghost.scrollHeight;
- ghost.remove();
-
- final HTMLsnippet = web.document.createElement('div')
- as web.HTMLDivElement
- ..id = viewTypeId
- ..innerHTML = emailHTML
- .toJS; // temporarily index because it has to do all of them
- HTMLsnippet.style
- ..width = '100%'
- ..height = '${heightOfEmail}px'
- ..overflow = 'auto'
- ..scrollBehavior = 'smooth';
-
- ui.platformViewRegistry.registerViewFactory(
- viewTypeId,
- (int viewId) => HTMLsnippet,
- );
- viewtypeIDs.add(viewTypeId);
- heightOfViewTypes.add(heightOfEmail);
- }
- }
-
- void _serializableData(String threadID) async {
- emailsInThread = await ApiService().threadsInSerializable(threadID);
- print("done thread serializable");
- if (!mounted) return;
- setState(() {
- _isLoaded = true;
- });
- }
-
- @override
- Widget build(BuildContext context) {
- return _isLoaded
- ?Column(children: [
- Expanded(
- child: ListView.builder(
- itemCount: widget.thread.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)
- // if(viewtypeIDs[index] == null || heightOfViewTypes[index] == null)
- // const SizedBox(height: 100, child: Center(child: CircularProgressIndicator())),
- SizedBox(
- height: heightOfViewTypes[index].toDouble(),
- child: HtmlElementView(
- key: UniqueKey(), viewType: viewtypeIDs[index]),
- ),
- Divider(),
- ],
- );
- },
- ),
- )
- ]): const Center(child:CircularProgressIndicator());
- }
-}
+export 'collapsableEmailsStub.dart'
+ if (dart.library.io) 'collapsableEmailsAndroid.dart'
+ if (dart.library.js_interop) 'collapsableEmailsWeb.dart';
\ No newline at end of file
diff --git a/lib/collapsableEmailsAndroid.dart b/lib/collapsableEmailsAndroid.dart
new file mode 100644
index 0000000..275eb6f
--- /dev/null
+++ b/lib/collapsableEmailsAndroid.dart
@@ -0,0 +1,442 @@
+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 List threadMarkdown;
+ final String threadIDs;
+ final String? targetJumpNumbering;
+ final String? targetViewspecs;
+ final String? targetFiltering;
+
+ CollapsableEmails({
+ required this.thread,
+ required this.threadMarkdown,
+ required this.threadIDs,
+ this.targetJumpNumbering,
+ this.targetViewspecs,
+ this.targetFiltering,
+ });
+
+ @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 = [];
+ List allMarkdown = [];
+ List> sentinel = [];
+ int level = 0;
+ AugmentTree zoomTreeRoot = AugmentTree();
+ // late AugmentTree currentZoomNode;
+ late List currentZoomTree = [];
+ bool zoomOut = false;
+ bool zoomIn = true;
+ late List threadNodes = [];
+ static bool leftNumbering = false;
+ static bool rightNumbering = true;
+ bool showWhole = false;
+
+ @override
+ void initState() {
+ super.initState();
+ threadNodes = [];
+ currentZoomTree = [];
+ // _markdownConverter();
+ _serializableData(widget.threadIDs); // this
+ _markdown2Tree(widget.threadMarkdown);
+ }
+
+ @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
+ void dispose() {
+ super.dispose();
+ }
+
+
+ List getThreads() {
+ return widget.thread;
+ }
+
+ 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(List text) {
+ print("started markdown2tree");
+ for (int emailsMD = 0; emailsMD < text.length; emailsMD++) {
+ final List nakedList =
+ md.Document().parseLines(text[emailsMD].split('\n'));
+ zoomTreeRoot = AugmentTree();
+ for (var node in nakedList) {
+ //maybe do an add function, but isn't this it?
+ if (node is md.Element) {
+ AugmentTree temp = AugmentTree();
+ temp.data = node.textContent;
+ temp.ogTag = node.tag;
+ 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();
+ threadNodes.add(zoomTreeRoot);
+ currentZoomTree.add(zoomTreeRoot);
+ }
+
+ if (!mounted) return;
+ setState(() {
+ _isLoaded = true;
+ });
+ }
+
+ void _goToChildren(int indexThread, int index) async {
+ final target = currentZoomTree[indexThread].children[index];
+ if (target.children.isNotEmpty) {
+ setState(() {
+ currentZoomTree[indexThread] = target;
+ });
+ } else {
+ print("This child has no further children.");
+ }
+ }
+
+ void _goToParent(int indexThread) async {
+ if (currentZoomTree[indexThread].parent != null) {
+ setState(() {
+ currentZoomTree[indexThread] = currentZoomTree[indexThread].parent!;
+ });
+ } else {
+ print("Already at root.");
+ }
+ }
+
+ void _serializableData(String threadID) async {
+ emailsInThread = await ApiService().threadsInSerializable(threadID);
+ print("done thread serializable");
+ if (!mounted) return;
+ setState(() {
+ _isLoaded = true;
+ });
+ }
+
+ Widget _buildForZooms(int indexThread) {
+ // IF I GIVE IT THE INDEX????
+ if (!_isLoaded) {
+ return const Center(child: CircularProgressIndicator()); // loading screen
+ }
+
+ final AugmentTree currentZoomNodeForThisEmail =
+ currentZoomTree[indexThread];
+
+ final canZoomOut = currentZoomNodeForThisEmail.parent != null;
+
+ return ListView.builder(
+ itemCount: currentZoomNodeForThisEmail.children.length,
+ itemBuilder: (context, index) {
+ final childNode = currentZoomNodeForThisEmail.children[index];
+ final canZoomIn = childNode.children.isNotEmpty;
+ // currentZoomNodeForThisEmail.addNumbering();
+ 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(indexThread) : null,
+ child: Icon(Icons.north_west_sharp),
+ ),
+ OutlinedButton(
+ onPressed: canZoomIn
+ ? () => _goToChildren(indexThread, index)
+ : null,
+ child: Icon(Icons.south_east_sharp),
+ ),
+ ],
+ ),
+ 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(
+ child: MarkdownBlock(
+ data: childNode.data,
+ // data: currentZoomNode
+ // .children[index].data, // one string of markdown
+ config: MarkdownConfig
+ .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)),
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ );
+ },
+ );
+ }
+
+ 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
+ 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(index),
+ ),
+ Divider(),
+ ],
+ );
+ },
+ ),
+ ),
+ ])
+ : const Center(child: CircularProgressIndicator());
+ }
+}
diff --git a/lib/collapsableEmailsStub.dart b/lib/collapsableEmailsStub.dart
new file mode 100644
index 0000000..e4ec443
--- /dev/null
+++ b/lib/collapsableEmailsStub.dart
@@ -0,0 +1,32 @@
+import 'structs.dart';
+import 'package:flutter/material.dart';
+
+class CollapsableEmails extends StatefulWidget {
+ final List thread; // email id's in the form xyz@gmail.com
+ final List threadMarkdown;
+ final String threadIDs;
+
+ CollapsableEmails(
+ {required this.thread,
+ required this.threadMarkdown,
+ required this.threadIDs, String? targetJumpNumbering, String? targetViewspecs, String? targetFiltering, required String nameOfDocument});
+
+ get getThreads => null;
+
+ get getAugmentRoot => null;
+
+ @override
+ State createState() => _CollapsableEmailsState();
+}
+
+class _CollapsableEmailsState extends State {
+
+ List getThreads() {
+ return widget.thread;
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(body: Text("collapsable stud"));
+ }
+}
diff --git a/lib/collapsableEmailsWeb.dart b/lib/collapsableEmailsWeb.dart
new file mode 100644
index 0000000..b758783
--- /dev/null
+++ b/lib/collapsableEmailsWeb.dart
@@ -0,0 +1,582 @@
+import 'package:flutter/material.dart';
+import 'api_service.dart';
+import 'structs.dart';
+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; to be replaced with the MD
+ final List threadMarkdown;
+ final String threadIDs;
+ final String? targetJumpNumbering;
+ final String? targetViewspecs;
+ final String? targetFiltering;
+ final String? nameOfDocument;
+
+ const CollapsableEmails({
+ required this.thread,
+ // required this.threadHTML,
+ required this.threadMarkdown,
+ required this.threadIDs,
+ this.targetJumpNumbering,
+ this.targetViewspecs,
+ this.targetFiltering,
+ this.nameOfDocument,
+ });
+
+ @override
+ State createState() => _CollapsableEmailsState();
+
+ AugmentTree? getAugmentRoot() {
+ return _CollapsableEmailsState().getAugmentRoot();
+ }
+}
+
+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": 5,
+ "h6": 6,
+ "p": 8,
+ "ul": 8,
+ "li": 8,
+ };
+
+ List tagsCollected = [];
+ List allMarkdown = [];
+ List> sentinel = [];
+ int level = 0;
+ AugmentTree zoomTreeRoot = AugmentTree();
+ // late AugmentTree currentZoomNode;
+ late List currentZoomTree =
+ []; // holds a list of list that holds the list of nodes on the currentzoom
+ bool zoomOut = false;
+ bool zoomIn = true;
+ late List threadNodes = [];
+ static bool leftNumbering = true;
+ static bool rightNumbering = true;
+ bool showWhole = false;
+ List queryResults = []; //results of conducting filtering
+ bool _isFilteringActive = false;
+
+ @override
+ void initState() {
+ super.initState();
+ threadNodes = [];
+ currentZoomTree = [];
+ // _markdownConverter();
+ _serializableData(widget.threadIDs); // this
+ _markdown2Tree(widget.threadMarkdown);
+ }
+
+ @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!);
+ }
+ if (widget.targetFiltering != null &&
+ widget.targetFiltering != oldWidget.targetFiltering) {
+ _handleFilterQuery(zoomTreeRoot, widget.targetFiltering!);
+ }
+ }
+
+ @override
+ void dispose() {
+ super.dispose();
+ }
+
+ List getThreads() {
+ return emailsInThread;
+ }
+
+ AugmentTree getAugmentRoot() {
+ return zoomTreeRoot;
+ }
+
+ 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(List text) {
+ print("started markdown2tree");
+ for (int emailsMD = 0; emailsMD < text.length; emailsMD++) {
+ final List nakedList =
+ md.Document().parseLines(text[emailsMD].split('\n'));
+ zoomTreeRoot = AugmentTree();
+ for (var node in nakedList) {
+ //maybe do an add function, but isn't this it?
+ if (node is md.Element) {
+ AugmentTree temp = AugmentTree();
+ temp.data = node.textContent;
+ temp.ogTag = node.tag;
+ //why did i do this???
+ if ( hirarchyDict.containsKey(node.tag)) {
+ _add2Tree(zoomTreeRoot, node);
+ }
+ }
+ }
+ zoomTreeRoot.addNumbering();
+ threadNodes.add(zoomTreeRoot);
+ currentZoomTree.add(zoomTreeRoot);
+ }
+
+ if (!mounted) return;
+ setState(() {
+ _isLoaded = true;
+ });
+ }
+
+ void _goToChildren(int indexThread, int index) async {
+ final target = currentZoomTree[indexThread].children[index];
+ if (target.children.isNotEmpty) {
+ setState(() {
+ currentZoomTree[indexThread] = target;
+ });
+ } else {
+ print("This child has no further children.");
+ }
+ }
+
+ void _goToChildrenFiltering(
+ int indexThread, int index, AugmentTree node) async {
+ final target = node;
+ if (target.children.isNotEmpty) {
+ setState(() {
+ currentZoomTree[indexThread] = target;
+ _isFilteringActive = false;
+ });
+ for (var child in target.children) {
+ print(child.data);
+ }
+ } else {
+ print("This child has no further children.");
+ }
+ }
+
+ void _goToParent(int indexThread) async {
+ if (currentZoomTree[indexThread].parent != null) {
+ setState(() {
+ currentZoomTree[indexThread] = currentZoomTree[indexThread].parent!;
+ });
+ } else {
+ print("Already at root.");
+ }
+ }
+
+ void _goToParentFiltering(int indexThread, AugmentTree node) async {
+ if (node.parent != null) {
+ setState(() {
+ currentZoomTree[indexThread] = node.parent!;
+ });
+ } else {
+ print("Already at root.");
+ }
+ }
+
+ void _serializableData(String threadID) async {
+ emailsInThread = await ApiService().threadsInSerializable(threadID);
+ print("done thread serializable");
+
+ if (!mounted) return;
+ setState(() {
+ _isLoaded = true;
+ });
+ }
+
+ Widget _buildForZooms(int indexThread) {
+ // index of email in thread, currentZoomTree,
+ //
+ if (!_isLoaded) {
+ return const Center(child: CircularProgressIndicator()); // loading screen
+ }
+
+ AugmentTree
+ currentZoomNodeForThisEmail = //each index is an email in the thread
+ currentZoomTree[indexThread];
+ print(currentZoomNodeForThisEmail.data);
+ print(currentZoomNodeForThisEmail.children);
+ print(currentZoomNodeForThisEmail.parent);
+
+ // if (_isFilteringActive) {
+ // nodesToDisplay = queryResults;
+ // } else {
+ // nodesToDisplay = currentZoomNodeForThisEmail.children;
+ // }
+ final canZoomOut = currentZoomNodeForThisEmail.parent != null;
+
+ if (_isFilteringActive) {
+ return ListView.builder(
+ itemCount: queryResults.length,
+ itemBuilder: (context, index) {
+ AugmentTree childNode = queryResults[index];
+ bool canZoomIn = childNode.children.isNotEmpty;
+ return Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
+ 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: () => {
+ setState(() {
+ _goToParentFiltering(indexThread, childNode);
+ _isFilteringActive = false;
+ })
+ },
+ child: Icon(Icons.north_west_sharp),
+ ),
+ OutlinedButton(
+ onPressed: canZoomIn
+ ? () => _goToChildrenFiltering(
+ indexThread, index, childNode)
+ : null,
+ child: Icon(Icons.south_east_sharp),
+ ),
+ ],
+ ),
+ 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(
+ child: MarkdownBlock(
+ data: childNode.data,
+ config: MarkdownConfig
+ .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)),
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ );
+ });
+ }
+
+ return ListView.builder(
+ itemCount: currentZoomNodeForThisEmail.children.length,
+ itemBuilder: (context, index) {
+ final childNode = currentZoomNodeForThisEmail.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(indexThread) : null,
+ child: Icon(Icons.north_west_sharp),
+ ),
+ OutlinedButton(
+ onPressed: canZoomIn
+ ? () => _goToChildren(indexThread, index)
+ : null,
+ child: Icon(Icons.south_east_sharp),
+ ),
+ ],
+ ),
+ 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(
+ child: MarkdownBlock(
+ data: childNode.data,
+ config: MarkdownConfig
+ .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)),
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ );
+ },
+ );
+ }
+
+ 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.')),
+ // );
+ // }
+ }
+
+ void _findNodesContainingStrDFS(
+ AugmentTree node, String query, List results) {
+ if (node.data.contains(query)) {
+ results.add(node);
+ }
+ for (var child in node.children) {
+ _findNodesContainingStrDFS(child, query, results);
+ }
+ }
+
+ List _handleFilterQuery(AugmentTree root, String query) {
+ List results = [];
+ final int targetEmailIndex = _expandedEmails.first;
+ _findNodesContainingStrDFS(root, query, results);
+ print(results);
+ for (var res in results) {
+ print(res.data);
+ }
+ if (results.isNotEmpty) {
+ setState(() {
+ queryResults = results;
+ // currentZoomTree[targetEmailIndex] = results.first; // Update the state
+ _isFilteringActive = true;
+ currentZoomTree[targetEmailIndex] = root;
+ });
+ print(currentZoomTree);
+ }
+ return results;
+ }
+
+ 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
+ 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(index), //show the tree
+ ),
+ Divider(),
+ ],
+ );
+ },
+ ),
+ ),
+ ])
+ : const Center(child: CircularProgressIndicator());
+ }
+}
diff --git a/lib/contact.dart b/lib/contact.dart
index 73d60fd..3829055 100644
--- a/lib/contact.dart
+++ b/lib/contact.dart
@@ -1,6 +1,4 @@
import 'package:flutter/material.dart';
-import 'package:http/http.dart' as http;
-import 'package:flutter_html/flutter_html.dart';
class ContactsPage extends StatefulWidget {
const ContactsPage({super.key});
diff --git a/lib/email.dart b/lib/email.dart
index 5dbf1a6..bae89c4 100644
--- a/lib/email.dart
+++ b/lib/email.dart
@@ -1,57 +1,227 @@
import 'package:flutter/material.dart';
-import 'package:flutter/widgets.dart';
+import 'package:markdown/markdown.dart' as md;
import 'api_service.dart';
import 'structs.dart';
+import 'emailView.dart';
+import 'Compose.dart';
-class EmailListScreen extends StatelessWidget {
+class EmailListScreen extends StatefulWidget {
final List emails;
final Future> Function(List, String) getEmailContent;
final String folder;
+ final GlobalKey<_EmailListScreenState> key;
+ final Function(List)? onSelectionChanged;
+
+ EmailListScreen({
+ required this.key,
+ required this.emails,
+ required this.getEmailContent,
+ required this.folder,
+ this.onSelectionChanged,
+ }) : super(key: key);
+
+ @override
+ _EmailListScreenState createState() => _EmailListScreenState();
+}
+
+class _EmailListScreenState extends State
+ with TickerProviderStateMixin {
+ late List selectStates; // for checkboxes if its selected or not
+ late List selectedEmails =
+ []; // holds the emails that are selected i.e. the emails that got the checkbox on
+ final Set _hoveredRows = {}; //the row that is being hovered over atm
+ bool bulkSelectMenu = false;
+ final GlobalKey _emailPageKey = GlobalKey();
+
+ @override
+ void initState() {
+ super.initState();
+ selectStates = List.filled(widget.emails.length, false);
+ }
+
+ @override
+ void didUpdateWidget(covariant EmailListScreen oldWidget) {
+ super.didUpdateWidget(oldWidget);
+ if (oldWidget.emails.length != widget.emails.length) {
+ selectStates = List.filled(widget.emails.length, false);
+ }
+ }
+
+ bool selectAllChecks(bool selectionType) {
+ //perhaps it should return a list of the selected
+ setState(() {
+ selectedEmails = [];
+ if (selectionType) {
+ bulkSelectMenu = true;
+ for (int email = 0; email < selectStates.length; email++) {
+ selectStates[email] = selectionType;
+ selectedEmails.add(widget.emails[email]);
+ }
+ } else {
+ for (int email = 0; email < selectStates.length; email++) {
+ selectStates[email] = selectionType;
+ }
+ selectedEmails = [];
+ }
+ });
+ widget.onSelectionChanged?.call(selectedEmails);
+ printTheSelected();
+ return false;
+ }
+
+ bool markAsRead(bool read) {
+ print("markasread $read");
+ setState(() {
+ if (read) {
+ //read
+ for (int email = 0; email < selectedEmails.length; email++) {
+ selectedEmails[email].seen = read;
+ ApiService()
+ .markAsSeen(selectedEmails[email].id); //the remote or .json
+ print(selectedEmails[email].id);
+
+ }
+ } else {
+ //unread
+ for (int email = 0; email < selectedEmails.length; email++) {
+ selectedEmails[email].seen = read;
+ ApiService()
+ .markAsUnseen(selectedEmails[email].id); //the remote or .json
+ print(selectedEmails[email].id);
+ }
+ }
+ });
+ return false;
+ }
+
+ bool moveOfSelected(String destinyFolder) {
+ //this should be called from a widget
+ print("move of folder");
+ setState(() {
+ for (int email = 0; email < selectedEmails.length; email++) {
+ ApiService().moveEmail(
+ widget.folder, selectedEmails[email].id.toString(), destinyFolder);
+ }
+ });
+ return false;
+ }
+
+ // Widget moveOfFolderWidget()
+
+ List listOfSelectedThreads() {
+ return selectedEmails;
+ }
+
+ void printTheSelected() {
+ for (int i = 0; i < selectedEmails.length; i++) {
+ print(selectedEmails[i].subject);
+ }
+ }
- EmailListScreen(
- {required this.emails,
- required this.getEmailContent,
- required this.folder});
-//fix the email list
@override
Widget build(BuildContext context) {
return Scaffold(
body: ListView.separated(
- itemCount: emails.length,
+ itemCount: widget.emails.length,
itemBuilder: (context, index) {
- final email = emails[index];
- return ListTile(
- title: Text(email.from_name,
- style: TextStyle(fontWeight: FontWeight.bold)),
- subtitle: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [Text(email.subject)],
- ),
- trailing: Text(email.date.toString()),
- onTap: () async {
- List emailContent = // list of the html
- await getEmailContent(email.messages, folder);
- // print("this is what email.messages look like in email.dart ${email.messages}");
- // List emailIds = email.messages;
+ Color seenColour;
+ final email = widget.emails[index];
+ if (email.seen) {
+ seenColour = ThemeData().highlightColor;
+ } else {
+ seenColour = Colors.transparent;
+ }
+ return MouseRegion(
+ onEnter: (_) => setState(() => _hoveredRows.add(index)),
+ onExit: (_) => setState(() => _hoveredRows.remove(index)),
+ child: ListTile(
+ leading: Checkbox(
+ value: selectStates[index],
+ onChanged: (bool? value) {
+ setState(() {
+ //works great
+ selectStates[index] = value ?? false;
- print(email.messages); //email ids of the thread
- Navigator.push(
- context,
- MaterialPageRoute(
- // could call collapsable and inside collable each calls email view?
- builder: (context) => EmailView(
- emailContent: emailContent,
- from: email.from_address,
- name: email.from_name,
- to: email.to.toString(),
- subject: email.subject,
- date: email.date.toString(),
- id: email.id.toString(), //i think this is thread id?
- messages: email.messages,
- ),
- ),
- );
- },
+ setState(() {
+ if (value!) {
+ selectedEmails.add(widget.emails[index]);
+ //here i must update the other side
+ _emailPageKey.currentState?.getListOfSelected();
+ } else {
+ selectedEmails.remove(widget.emails[index]);
+ _emailPageKey.currentState?.getListOfSelected();
+ }
+ widget.onSelectionChanged?.call(selectedEmails);
+ print(selectedEmails);
+ });
+ });
+ },
+ ),
+ title: Text(email.from_name,
+ style: TextStyle(fontWeight: FontWeight.bold)),
+ subtitle: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [Text(email.subject)],
+ ),
+ trailing: _hoveredRows.contains(index)
+ ? Row(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ IconButton(
+ icon: Icon(Icons.mark_email_read_outlined),
+ onPressed: () {
+ // mark email as read
+ setState(() {
+ widget.emails[index].seen = true;
+ ApiService().markAsSeen(email.id);
+ });
+ },
+ ),
+ IconButton(
+ icon: Icon(Icons.delete_outline),
+ onPressed: () {
+ // delete email
+ ApiService().deleteEmail(widget.folder, email.id);
+ },
+ ),
+ ],
+ )
+ : Text(email.date.toString()),
+ hoverColor: Colors.transparent,
+ tileColor: seenColour,
+ onTap: () async {
+ List emailContent = // list of the html
+ await widget.getEmailContent(email.messages, widget.folder);
+ // print("thread id? $email.id"); yes
+ print(email.messages); //email ids of the thread
+ if (widget.folder == "Drafts") {
+ print("IN DRAFTS MOVE THE CONTENT TO THE WRITING THING");
+ //open the compose
+ OverlayService _thisInstance = OverlayService();
+ _thisInstance.draftID = email;
+ _thisInstance.showPersistentWidget(context);
+ } else {
+ // print(email)
+ Navigator.push(
+ context,
+ MaterialPageRoute(
+ // could call collapsable and inside collable each calls email view?
+ builder: (context) => EmailView(
+ emailContent: emailContent,
+ from: email.from_address,
+ name: email.from_name,
+ to: email.to.toString(),
+ subject: email.subject,
+ date: email.date.toString(),
+ id: email.id.toString(), //i think this is thread id?
+ messages: email.messages,
+ ),
+ ),
+ );
+ ApiService().markAsSeen(email.id);
+ }
+ },
+ ),
);
},
separatorBuilder: (context, index) => Divider(),
@@ -62,10 +232,12 @@ class EmailListScreen extends StatelessWidget {
// ignore: must_be_immutable
class EmailPage extends StatefulWidget {
- EmailPage({Key? key}) : super(key: key);
String selectedFolder = "INBOX"; //starter
int offset = 0;
int page = 1;
+ final Function(List)? onSelectionChanged;
+
+ EmailPage({Key? key, this.onSelectionChanged}) : super(key: key);
@override
EmailPageState createState() => EmailPageState();
@@ -78,6 +250,9 @@ class EmailPageState extends State {
int page = 1;
bool isBackDisabled = false;
+ final GlobalKey<_EmailListScreenState> emailListKey =
+ GlobalKey<_EmailListScreenState>();
+
@override
void initState() {
super.initState();
@@ -86,6 +261,7 @@ class EmailPageState extends State {
_fetchEmails();
}
+ List get getEmails => emails;
String getPage() => widget.page.toString();
bool get backDisabled => isBackDisabled;
@@ -116,7 +292,6 @@ class EmailPageState extends State {
}
});
}
- // print(currentPage);
print(widget.page);
_fetchEmails();
}
@@ -124,7 +299,7 @@ class EmailPageState extends State {
void _fetchEmails() async {
try {
List fetchedEmails = await apiService
- .fetchEmailsFromFolder(widget.selectedFolder, widget.offset);
+ .fetchEmailsFromFolderReversed(widget.selectedFolder, widget.offset);
if (!mounted) return;
setState(() {
@@ -135,14 +310,36 @@ class EmailPageState extends State {
}
}
+ bool selectAllEmails(bool selectionType) {
+ emailListKey.currentState?.selectAllChecks(selectionType);
+ return selectionType;
+ }
+
+ bool markSelectedAsRead(bool selectionType) {
+ emailListKey.currentState?.markAsRead(selectionType);
+ return selectionType;
+ }
+
+ bool moveSelectedOfFolder(String folder) {
+ emailListKey.currentState?.moveOfSelected(folder);
+ return false;
+ }
+
+ List getListOfSelected() {
+ return emailListKey.currentState!.listOfSelectedThreads();
+ }
+ // return [GetThreadResponse(id: 1, messages: [], subject: "subject", date: DateTime(2025), from_name: "from_name", from_address: "from_address", to: [], seen: false)];
+
@override
Widget build(BuildContext context) {
return Scaffold(
- body: EmailListScreen(
- emails: emails,
- getEmailContent: apiService.fetchEmailContent,
- folder: widget.selectedFolder, //try to grab from it directly
- ),
- );
+ body: EmailListScreen(
+ key: emailListKey,
+ emails: emails,
+ // getEmailContent: apiService.fetchEmailContent,
+ getEmailContent: apiService.fetchMarkdownContent,
+ folder: widget.selectedFolder, //try to grab from it directly
+ onSelectionChanged: widget.onSelectionChanged,
+ ));
}
}
diff --git a/lib/emailView.dart b/lib/emailView.dart
new file mode 100644
index 0000000..61c9bf8
--- /dev/null
+++ b/lib/emailView.dart
@@ -0,0 +1,3 @@
+export 'emailViewStub.dart'
+ if (dart.library.io) 'emailViewAndroid.dart'
+ if (dart.library.js_interop) 'emailViewWeb.dart';
\ No newline at end of file
diff --git a/lib/emailViewAndroid.dart b/lib/emailViewAndroid.dart
new file mode 100644
index 0000000..5ef2efd
--- /dev/null
+++ b/lib/emailViewAndroid.dart
@@ -0,0 +1,120 @@
+import 'package:crab_ui/augment.dart';
+import 'package:crab_ui/collapsableEmailsAndroid.dart';
+import 'package:flutter/material.dart';
+// import 'dart:ui_web' as ui;
+// import 'augment.dart';
+// // import 'dart:js_interop' as js; //eventually for manipulating css
+// import 'package:pointer_interceptor/pointer_interceptor.dart';
+// import 'collapsableEmails.dart';
+// import 'api_service.dart';
+
+class EmailView extends StatefulWidget {
+ final List emailContent;
+ final String from;
+ final String name;
+ final String to;
+ final String subject;
+ final String date;
+ final String id;
+ final List messages;
+
+ const EmailView({
+ Key? key,
+ required this.emailContent,
+ required this.from,
+ required this.name, // tf is name
+ required this.to,
+ required this.subject,
+ required this.date,
+ required this.id,
+ required this.messages,
+ }) : super(key: key);
+ @override
+ _EmailViewState createState() => _EmailViewState();
+}
+
+class _EmailViewState extends State {
+
+ @override
+ void initState() {
+ super.initState();
+ }
+
+ void _scrollToNumber(String spanId) {
+ // AugmentClasses.handleJump(spanId);
+ }
+ void _viewSpecs(String command){
+
+ }
+
+ void _filteringQuery(String query){
+
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ appBar: AppBar(
+ title: Text(widget.name),
+ ),
+ body: Padding(
+ padding: const EdgeInsets.all(8.0),
+ child: Column(
+ children: [
+ EmailToolbar(
+ onButtonPressed: () => {},
+ onJumpToNumbering: _scrollToNumber,
+ onViewspecs: _viewSpecs,
+ onFiltering: _filteringQuery,
+ emails: widget.messages, subject: '', rootAugment: null,
+ ),
+ Row(
+ children: [
+ Expanded(
+ child: Text(
+ widget.subject,
+ style: TextStyle(fontSize: 15),
+ overflow: TextOverflow.visible,
+ softWrap: true,
+ ),
+ ),
+ ],
+ ),
+ Row(
+ children: [
+ Text(
+ 'from ${widget.name}',
+ style: TextStyle(fontSize: 8),
+ ),
+ Text(
+ '<${widget.from}>',
+ style: TextStyle(fontSize: 8),
+ ),
+ Spacer(),
+ Text(
+ widget.date,
+ textAlign: TextAlign.right,
+ )
+ ],
+ ),
+ Row(
+ children: [
+ Text(
+ 'to ${widget.to.toString()}',
+ style: TextStyle(fontSize: 8),
+ )
+ ],
+ ),
+ Expanded(
+ child: CollapsableEmails(
+ thread: widget.messages,
+ threadMarkdown: widget.emailContent,
+ threadIDs: widget.id,
+ ),
+ ),
+ ],
+ ),
+ )
+ );
+ }
+}
\ No newline at end of file
diff --git a/lib/emailViewStub.dart b/lib/emailViewStub.dart
new file mode 100644
index 0000000..5c5f657
--- /dev/null
+++ b/lib/emailViewStub.dart
@@ -0,0 +1,39 @@
+import 'package:flutter/material.dart';
+
+
+class EmailView extends StatefulWidget {
+ final List emailContent;
+ final String from;
+ final String name;
+ final String to;
+ final String subject;
+ final String date;
+ final String id;
+ final List messages;
+
+ const EmailView({
+ Key? key,
+ required this.emailContent,
+ required this.from,
+ required this.name,
+ required this.to,
+ required this.subject,
+ required this.date,
+ required this.id,
+ required this.messages,
+ }) : super(key: key);
+ @override
+ _EmailViewState createState() => _EmailViewState();
+}
+
+class _EmailViewState extends State {
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ body: Center(
+ child: Text(" emailview stub, not supported")
+ )
+ );
+ }
+
+}
\ No newline at end of file
diff --git a/lib/emailViewWeb.dart b/lib/emailViewWeb.dart
new file mode 100644
index 0000000..385af48
--- /dev/null
+++ b/lib/emailViewWeb.dart
@@ -0,0 +1,159 @@
+import 'package:flutter/material.dart';
+import 'dart:ui_web' as ui;
+import 'augment.dart';
+import 'collapsableEmails.dart';
+import 'api_service.dart';
+
+class EmailView extends StatefulWidget {
+ final List emailContent;
+ final String from;
+ final String name;
+ final String to;
+ final String subject;
+ final String date;
+ final String id;
+ final List messages;
+
+ const EmailView({
+ Key? key,
+ required this.emailContent,
+ required this.from,
+ required this.name,
+ required this.to,
+ required this.subject,
+ required this.date,
+ required this.id,
+ required this.messages,
+ }) : super(key: key);
+ @override
+ _EmailViewState createState() => _EmailViewState();
+}
+
+class _EmailViewState extends State {
+ //html css rendering thing
+ late Key iframeKey;
+ late String currentContent;
+ late String viewTypeId; //make this a list too???
+ // TextEditingController _jumpController = TextEditingController();
+ late EmailToolbar toolbarInstance = EmailToolbar(
+ onJumpToNumbering: _handleJumpRequest,
+ onViewspecs: _handleViewspecsRequest,
+ onButtonPressed: () => {print("email tool bar pressed")},
+ onFiltering: _handleFiltering,
+ emails: widget.messages,
+ subject: widget.subject,
+ rootAugment: localCollapsable.getAugmentRoot(),
+ );
+
+ late CollapsableEmails localCollapsable = CollapsableEmails(
+ //change here
+ thread: widget.messages, //this wont work in serializable
+ // threadHTML: widget.emailContent, // old html
+ threadMarkdown: widget.emailContent,
+ threadIDs: widget.id,
+ targetJumpNumbering: _targetJumpNumbering,
+ targetViewspecs: _targetViewspecs,
+ targetFiltering: _queryFiltering,
+ nameOfDocument: widget.subject,
+ );
+
+ final hardcodedMarkers = [
+ {'id': 'marker1', 'x': 50, 'y': 100},
+ {'id': 'marker2', 'x': 150, 'y': 200},
+ {'id': 'marker3', 'x': 250, 'y': 300},
+ ];
+ String? _targetJumpNumbering;
+ String? _targetViewspecs;
+ String? _queryFiltering;
+
+ @override
+ void initState() {
+ super.initState();
+ print("thread id? ${widget.id}");
+ List currentContent = widget
+ .emailContent; //html of the email/ actually entire thread, gives me little space to play in between
+ // i wonder if the other attributes change? because if so i have to add like some zooms in and out of the emails, as in collapse
+ // _registerViewFactory(currentContent);
+ print("email content in Collapsable ${widget.emailContent}");
+
+ }
+
+ void _scrollToNumber(String spanId) {
+ AugmentClasses.handleJump(spanId);
+ }
+
+ void _handleJumpRequest(String numbering) {
+ setState(() {
+ _targetJumpNumbering = numbering;
+ });
+ }
+
+ void _handleViewspecsRequest(String viewspecsCommand) {
+ setState(() {
+ _targetViewspecs = viewspecsCommand;
+ });
+ }
+
+ void _handleFiltering(String query) {
+ setState(() {
+ _queryFiltering = query;
+ });
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ ApiService.currThreadID = widget.id;
+ // AugmentClasses localAugment = AugmentClasses(localCollapsable);
+ return Scaffold(
+ appBar: AppBar(
+ title: Text(widget.name),
+ ),
+ body: Stack(
+ children: [
+ Column(
+ children: [
+ toolbarInstance,
+ Row(
+ // title of email
+ children: [
+ Text(
+ widget.subject,
+ style: TextStyle(fontSize: 30),
+ ),
+ ],
+ ),
+ Row(
+ children: [
+ Text(
+ 'from ${widget.name}',
+ style: TextStyle(fontSize: 18),
+ ),
+ Text(
+ '<${widget.from}>',
+ style: TextStyle(fontSize: 18),
+ ),
+ Spacer(),
+ Text(
+ '${widget.date}',
+ textAlign: TextAlign.right,
+ )
+ ],
+ ),
+ // TODO: make a case where if one of these is the user's email it just says me :)))))
+ Row(
+ children: [
+ Text(
+ 'to ${widget.to.toString()}',
+ style: TextStyle(fontSize: 15),
+ )
+ ],
+ ),
+ Expanded(
+ child: localCollapsable,
+ ),
+ ],
+ ),
+ ],
+ ));
+ }
+}
diff --git a/lib/home_page.dart b/lib/home_page.dart
index b4878b0..d4fe108 100644
--- a/lib/home_page.dart
+++ b/lib/home_page.dart
@@ -1,13 +1,13 @@
import 'package:crab_ui/sonicEmailView.dart';
+import 'package:go_router/go_router.dart';
+import 'package:pointer_interceptor/pointer_interceptor.dart';
import 'folder_drawer.dart';
import 'structs.dart';
-import 'package:flutter/widgets.dart';
import 'api_service.dart';
import 'package:flutter/material.dart';
import 'email.dart';
-// import 'package:shared_preferences/shared_preferences.dart';
-// import 'serialize.dart';
+import 'Compose.dart';
class HomeScreen extends StatefulWidget {
@override
@@ -21,10 +21,23 @@ class _HomeScreenState extends State with TickerProviderStateMixin {
bool _isSidebarOpen = true;
bool querySearches = false;
String? _selectedOption = "INBOX";
+ List _checkBulk = [
+ "All",
+ "None",
+ "Read",
+ "Unread",
+ "Starred",
+ "Unstarred"
+ ];
+ bool _checkboxState = false;
+ bool bulkOptionsState = false;
+ List selectedThreads =
+ []; //this should store the emails that are being stored downstream too
List _tabs = ['Emails'];
Map _tabWidgets = {};
TabController? _tabController;
+ OverlayEntry? _overlayEntry;
@override
void initState() {
@@ -32,6 +45,11 @@ class _HomeScreenState extends State with TickerProviderStateMixin {
_tabController = TabController(length: _tabs.length, vsync: this);
_tabWidgets['Emails'] = EmailPage(
key: _emailPageKey,
+ onSelectionChanged: (updatedList) {
+ setState(() {
+ selectedThreads = updatedList;
+ });
+ },
);
}
@@ -47,49 +65,50 @@ class _HomeScreenState extends State with TickerProviderStateMixin {
});
}
- void _showOptionsSearchDialog () async {
+ void _showOptionsSearchDialog() async {
List folders = await apiService.fetchFolders();
if (mounted) {
- showDialog(
- context: context,
- builder: (BuildContext context) {
- return AlertDialog(
- title: Text('Choose an Option'),
- content: Column(
- mainAxisSize: MainAxisSize.min,
- children: folders.map((option) {
- return ListTile(
- title: Text(option),
- leading: Radio(
- value: option,
- groupValue: _selectedOption, // Bind with _selectedOption
- onChanged: (String? value) {
- setState(() {
- _selectedOption = value;
- });
- Navigator.of(context).pop(); // Close the dialog on selection
- },
- ),
- );
- }).toList(),
- ),
- actions: [
- ElevatedButton(
- child: Text('Submit'),
- onPressed: () {
- Navigator.of(context).pop(); // Close the dialog
- ScaffoldMessenger.of(context).showSnackBar(SnackBar(
- content: Text('You selected: $_selectedOption'),
- ));
- },
+ showDialog(
+ context: context,
+ builder: (BuildContext context) {
+ return AlertDialog(
+ title: Text('Choose an Option'),
+ content: Column(
+ mainAxisSize: MainAxisSize.min,
+ children: folders.map((option) {
+ return ListTile(
+ title: Text(option),
+ leading: Radio(
+ value: option,
+ groupValue: _selectedOption, // Bind with _selectedOption
+ onChanged: (String? value) {
+ setState(() {
+ _selectedOption = value;
+ });
+ Navigator.of(context)
+ .pop(); // Close the dialog on selection
+ },
+ ),
+ );
+ }).toList(),
),
- ],
- );
- },
- );}
+ actions: [
+ ElevatedButton(
+ child: Text('Submit'),
+ onPressed: () {
+ Navigator.of(context).pop(); // Close the dialog
+ ScaffoldMessenger.of(context).showSnackBar(SnackBar(
+ content: Text('You selected: $_selectedOption'),
+ ));
+ },
+ ),
+ ],
+ );
+ },
+ );
+ }
}
-
// Remove a tab
void _removeTab(int index) {
@@ -122,7 +141,7 @@ class _HomeScreenState extends State with TickerProviderStateMixin {
body: ListView.separated(
itemCount: result.length,
itemBuilder: (context, index) {
- final SerializableMessage email = result[index];
+ final SerializableMessage email = result[index];
return ListTile(
title: Text(email.from,
style: TextStyle(fontWeight: FontWeight.bold)),
@@ -135,58 +154,25 @@ class _HomeScreenState extends State with TickerProviderStateMixin {
// print('tapped');
// List emailContent =
// await apiService.fetchEmailContent([email.id], email.list);
- //call the foldable
-
+ //call the foldable
+
List emailContent = // list of the html
- await apiService.fetchEmailContent([email.id], email.list);
+ await apiService
+ .fetchEmailContent([email.id], email.list);
// List emailIds = email.messages;
-
-
+
Navigator.push(
context,
MaterialPageRoute(
- builder: (context) =>SonicEmailView(
- email: email,
- emailHTML: emailContent[0])
- // builder: (context) => EmailView(
- // emailContent: emailContent,
- // from: email.from,
- // name: email.name,
- // to: email.to.toString(),
- // subject: email.subject,
- // date: email.date.toString(),
- // id: email.id.toString(),
- // messages: [email.id],
- // ),
- ),
+ builder: (context) => SonicEmailView(
+ email: email, emailHTML: emailContent[0])),
);
},
);
},
separatorBuilder: (context, index) => Divider(),
),
- // child: Column(
- // mainAxisAlignment: MainAxisAlignment.center,
- // children: [
- // Text("Results for: $query", style: TextStyle(fontSize: 24)),
- // // Display the actual data
- // Text(result[0].name), // Accessing the first result safely
- // Text(result[0].from), // Displaying the 'from' field as an example
- // Text(result[0].hash),
- // Text(result[0].subject),
- // Text(result[0].uid.toString()),
- // Text(result[0].list),
- // Text(result[0].id),
-
- // // Add more fields or customize the display
- // // SerializableEmailListScreen(emails: result, getEmailContent: getEmailContent)
- // // Expanded(
-
- // // child:
- // // ),
- // ],
);
- // );
}
},
);
@@ -201,265 +187,611 @@ class _HomeScreenState extends State with TickerProviderStateMixin {
@override
Widget build(BuildContext context) {
return Scaffold(
- key: _scaffoldKey,
- drawer: FolderDrawer(
- apiService: apiService,
- onFolderTap: (folder) {
- _emailPageKey.currentState?.updateSelectedFolder(folder);
- },
- ),
- body: Stack(
- children: [
- Row(
- children: [
- // Sidebar
- if (_isSidebarOpen)
- Container(
- width: 70,
- color: Color.fromARGB(17, 96, 122, 135),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
+ backgroundColor: Theme.of(context).colorScheme.onPrimary,
+ body: Padding(
+ padding: const EdgeInsets.fromLTRB(0, 20, 0, 20),
+ child: Scaffold(
+ key: _scaffoldKey,
+ drawer: FolderDrawer(
+ apiService: apiService,
+ onFolderTap: (folder) {
+ _emailPageKey.currentState?.updateSelectedFolder(folder);
+ },
+ ),
+ body: Scaffold(
+ backgroundColor: Theme.of(context).colorScheme.onPrimary,
+ body: Padding(
+ padding: const EdgeInsets.fromLTRB(0, 20, 0, 0),
+ child: Stack(
+ children: [
+ Row(
children: [
- ListTile(
- leading: Icon(Icons.home),
- onTap: () {
- // Navigate to Home
- },
- ),
- ListTile(
- leading: Icon(Icons.settings),
- onTap: () {
- // Navigate to Settings
- },
- ),
- ListTile(
- leading: Icon(Icons.email),
- onTap: () {
- _scaffoldKey.currentState?.openDrawer();
- },
- ),
- Spacer(),
- Padding(
- padding: const EdgeInsets.all(8.0),
- child: Align(
- alignment: Alignment.bottomLeft,
- child: IconButton(
- icon: Icon(Icons.close, color: Colors.white),
- onPressed: () {
- setState(() {
- _isSidebarOpen = false;
- });
- },
+ // Sidebar
+ if (_isSidebarOpen)
+ Container(
+ width: 70,
+ color: Color.fromARGB(17, 96, 122, 135),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ ListTile(
+ leading: Icon(Icons.edit_note_sharp),
+ onTap: () {
+ OverlayService()
+ .showPersistentWidget(context);
+ }),
+ ListTile(
+ leading: Icon(Icons.home),
+ onTap: () {
+ // Navigate to Home
+ context.go("/home");
+ },
+ ),
+ // ListTile(
+ // leading: Icon(Icons.settings),
+ // onTap: () {
+ // // Navigate to Settings
+ // },
+ // ),
+ // ListTile(
+ // leading: Icon(Icons.contact_mail),
+ // onTap: () {
+ // // Navigate to Contacts
+ // },
+ // ),
+
+ ListTile(
+ leading: Icon(Icons.email),
+ onTap: () {
+ _scaffoldKey.currentState?.openDrawer();
+ },
+ ),
+ Spacer(),
+ Padding(
+ padding: const EdgeInsets.all(8.0),
+ child: Align(
+ alignment: Alignment.bottomLeft,
+ child: IconButton(
+ icon:
+ Icon(Icons.close, color: Colors.white),
+ onPressed: () {
+ setState(() {
+ _isSidebarOpen = false;
+ });
+ },
+ ),
+ ),
+ ),
+ ],
),
),
+ // Main content
+ Expanded(
+ child: Column(
+ children: [
+ Container(
+ padding: EdgeInsets.fromLTRB(8.0, 8.0, 8.0, 4.0),
+ color: Color.fromARGB(42, 36, 102, 132),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ Flexible(
+ child: ConstrainedBox(
+ constraints: BoxConstraints(
+ maxWidth: 800,
+ ),
+ child: SizedBox(
+ height: 40,
+ child: TextField(
+ decoration: InputDecoration(
+ hintText: 'Search...',
+ border: OutlineInputBorder(),
+ prefixIcon: Icon(Icons.search),
+ ),
+ onSubmitted: (value) {
+ if (value.isNotEmpty) {
+ _performSearch(
+ value, _selectedOption);
+ }
+ //this is the input box i mentioned
+ // if (value == '') {
+ // setState(() {
+ // querySearches = false;
+ // });
+ // }
+ // Future> results = apiService
+ // .sonicSearch('INBOX', 20, 0, value);
+ // // print(value);
+ // print(results);
+ // setState(() {
+ // querySearches = true;
+ // });
+ },
+ ),
+ ),
+ ),
+ ),
+ SizedBox(
+ width: 8,
+ ),
+ Container(
+ height: 40,
+ child: ElevatedButton(
+ onPressed: _showOptionsSearchDialog,
+ child: Icon(Icons.manage_search),
+ ),
+ )
+ ],
+ ),
+ ),
+ Container(
+ padding: EdgeInsets.all(0.0),
+ color: Color.fromARGB(42, 36, 102, 132),
+ child: Row(
+ children: [
+ Container(
+ height: 2,
+ )
+ ],
+ ),
+ ),
+ Container(
+ padding: EdgeInsets.fromLTRB(0, 4, 0, 4),
+ color: Theme.of(context).colorScheme.onPrimary,
+ child: Row(
+ children: [
+ Padding(
+ padding:
+ const EdgeInsets.fromLTRB(4, 0, 0, 0),
+ child: Checkbox(
+ value: _checkboxState,
+ onChanged: (value) {
+ setState(() {
+ _checkboxState = !_checkboxState;
+ });
+ if (_checkboxState) {
+ // var a = _tabWidgets["Emails"].;
+ print(_emailPageKey.currentState!
+ .selectAllEmails(
+ true)); //now i got them all but how do i go down to select them all?
+ print("all");
+ bulkOptionsState = true;
+ } else {
+ _emailPageKey.currentState!
+ .selectAllEmails(false);
+ bulkOptionsState = false;
+ print("none");
+ }
+ }),
+ ),
+ const SizedBox(
+ width: 0,
+ ),
+ PopupMenuButton(
+ icon: const Icon(
+ Icons.arrow_drop_down_outlined),
+ itemBuilder: (BuildContext context) =>
+ >[
+ PopupMenuItem(
+ //select all
+ child: Text("All"),
+ onTap: () {
+ _emailPageKey.currentState!
+ .selectAllEmails(true);
+ },
+ ),
+ PopupMenuItem(
+ child: Text("None"),
+ onTap: () {
+ _emailPageKey.currentState!
+ .selectAllEmails(false);
+ },
+ ),
+ PopupMenuItem(
+ child: Text("Read"),
+ onTap: () {
+ //select the read
+ },
+ ),
+ PopupMenuItem(
+ //select the unread
+ child: Text("Unread"),
+ onTap: () {
+ //select the unread
+ },
+ ),
+ PopupMenuItem(
+ child: Text("Starred")),
+ PopupMenuItem(
+ child: Text("Unstarred")),
+ ],
+ onSelected: (String result) {
+ print("result $result");
+ },
+ ),
+ if (selectedThreads.isNotEmpty) ...[
+ //this needs to know if anything got selected,
+ IconButton(
+ onPressed: null,
+ icon: Icon(Icons.archive_outlined)),
+ IconButton(
+ onPressed: null,
+ icon: Icon(Icons.delete_outlined)),
+ IconButton(
+ onPressed: () {
+ _emailPageKey.currentState!
+ .markSelectedAsRead(
+ true); //mark as read
+ },
+ icon: Icon(
+ Icons.mark_email_read_outlined)),
+ IconButton(
+ onPressed: () {
+ final overlay = Overlay.of(context);
+ String?
+ selectedFolder; // Variable to store the selected folder
+
+ _overlayEntry = OverlayEntry(
+ builder: (context) => Stack(
+ children: [
+ // Dimmed background
+ Container(
+ color: Colors.black54,
+ width:
+ MediaQuery.of(context)
+ .size
+ .width,
+ height:
+ MediaQuery.of(context)
+ .size
+ .height,
+ ),
+ // Focused content window
+ PointerInterceptor(
+ child: Center(
+ child: Material(
+ elevation: 8,
+ borderRadius:
+ BorderRadius
+ .circular(12),
+ child: ConstrainedBox(
+ constraints:
+ const BoxConstraints(
+ maxWidth: 400,
+ maxHeight: 500,
+ ),
+ child: Column(
+ mainAxisSize:
+ MainAxisSize
+ .min,
+ children: [
+ Text(
+ 'Move email from folder ${ApiService.currFolder} to:',
+ style: TextStyle(
+ fontSize:
+ 16),
+ ),
+ Divider(
+ height: 1),
+ Expanded(
+ child: FutureBuilder<
+ List<
+ String>>(
+ future: ApiService()
+ .fetchFolders(),
+ builder: (context,
+ snapshot) {
+ if (snapshot
+ .connectionState ==
+ ConnectionState
+ .waiting) {
+ return const Center(
+ child:
+ CircularProgressIndicator());
+ } else if (snapshot
+ .hasError) {
+ return Center(
+ child:
+ Text('Error: ${snapshot.error}'));
+ } else if (snapshot
+ .hasData) {
+ return StatefulBuilder(
+ builder:
+ (context,
+ setState) {
+ return ListView(
+ shrinkWrap:
+ true,
+ children:
+ snapshot.data!.map((folder) {
+ return RadioListTile(
+ title: Text(folder),
+ value: folder,
+ groupValue: selectedFolder,
+ onChanged: (String? value) {
+ setState(() {
+ selectedFolder = value; // Update the selected folder
+ });
+ },
+ );
+ }).toList(),
+ );
+ },
+ );
+ } else {
+ return const Center(
+ child:
+ Text('No folders found.'));
+ }
+ },
+ ),
+ ),
+ Divider(
+ height: 1),
+ Row(
+ mainAxisAlignment:
+ MainAxisAlignment
+ .spaceEvenly,
+ children: [
+ ElevatedButton(
+ onPressed:
+ () {
+ // Handle Accept button
+ if (selectedFolder !=
+ null) {
+ print(
+ "Selected folder: $selectedFolder");
+ // Store the selected folder or perform any action
+ // ApiService.currFolder = selectedFolder!;
+ _emailPageKey
+ .currentState! //the one selected
+ .moveSelectedOfFolder(selectedFolder!);
+ _overlayEntry
+ ?.remove();
+ } else {
+ print(
+ "No folder selected");
+ }
+ },
+ child: Text(
+ 'Accept'),
+ ),
+ ElevatedButton(
+ onPressed:
+ () {
+ // Handle Cancel button
+ _overlayEntry
+ ?.remove();
+ },
+ child: Text(
+ 'Cancel'),
+ ),
+ ],
+ ),
+ ],
+ ),
+ ),
+ ),
+ ),
+ ),
+ ],
+ ),
+ );
+
+ if (_overlayEntry != null) {
+ overlay.insert(_overlayEntry!);
+ }
+ // _emailPageKey.currentState! //the one selected
+ // .moveSelectedOfFolder();
+ },
+ icon: Icon(
+ Icons.drive_file_move_outlined)),
+ ],
+ PopupMenuButton(
+ icon: const Icon(Icons.more_vert),
+ itemBuilder: (BuildContext context) {
+ if (selectedThreads.isEmpty) {
+ //why tf?
+ return >[
+ PopupMenuItem(
+ child: Row(
+ children: [
+ Icon(Icons
+ .mark_email_read_outlined),
+ const SizedBox(
+ width: 4.0,
+ ),
+ Text("Mark all as read")
+ ],
+ ),
+ onTap: () {
+ _emailPageKey.currentState!
+ .selectAllEmails(true);
+ _emailPageKey.currentState!
+ .markSelectedAsRead(true);
+ _emailPageKey.currentState!
+ .selectAllEmails(false);
+ },
+ ),
+ const PopupMenuDivider(),
+ PopupMenuItem(
+ child: Text(
+ "Select messages to see more actions",
+ style: TextStyle(
+ color: Colors
+ .blueGrey.shade300),
+ ),
+ )
+ ];
+ } else {
+ return >[
+ PopupMenuItem(
+ child: Row(
+ children: [
+ Icon(Icons
+ .mark_email_unread_outlined),
+ const SizedBox(
+ width: 4.0,
+ ),
+ Text("Mark as unread")
+ ],
+ ),
+ onTap: () {
+ _emailPageKey.currentState!
+ .markSelectedAsRead(
+ false);
+ },
+ ),
+ const PopupMenuItem(
+ child: Row(
+ children: [
+ Icon(Icons.snooze_outlined),
+ const SizedBox(
+ width: 4.0,
+ ),
+ Text("Snooze")
+ ],
+ ),
+ ),
+ const PopupMenuItem(
+ child: Row(
+ children: [
+ Icon(Icons
+ .star_border_outlined),
+ const SizedBox(
+ width: 4.0,
+ ),
+ Text("Add star")
+ ],
+ ),
+ ),
+ ];
+ }
+ }),
+ ],
+ )),
+ Container(
+ color: Color.fromARGB(255, 131, 110, 143),
+ child: TabBar(
+ controller: _tabController,
+ isScrollable: true,
+ tabs: _tabs
+ .asMap()
+ .entries
+ .map((entry) => Tab(
+ child: Row(
+ children: [
+ Text(entry.value),
+ if (entry.value != 'Emails')
+ GestureDetector(
+ onTap: () =>
+ _removeTab(entry.key),
+ child: Icon(Icons.close,
+ size: 16),
+ ),
+ ],
+ ),
+ ))
+ .toList(),
+ labelColor: Colors.white,
+ indicatorColor: Colors.white,
+ ),
+ ),
+ Container(
+ // alignment: Alignment.topLeft,
+ padding: EdgeInsets.all(8.0),
+ color: Colors.white,
+ child: Row(
+ children: [
+ ElevatedButton(
+ onPressed: () {
+ _emailPageKey.currentState!.isBackDisabled
+ ? null
+ : _emailPageKey.currentState
+ ?.updatePagenation('back');
+ },
+ child: Icon(Icons.navigate_before),
+ ),
+ Builder(
+ builder: (context) {
+ final emailState =
+ _emailPageKey.currentState;
+ if (emailState == null) {
+ // Schedule a rebuild once the state is available
+ Future.microtask(() => setState(() {}));
+ return Text('Loading...');
+ }
+
+ return ValueListenableBuilder(
+ valueListenable:
+ emailState.currentPageNotifier,
+ builder: (context, value, _) =>
+ Text('$value'),
+ );
+ },
+ ),
+ ElevatedButton(
+ onPressed: () {
+ _emailPageKey.currentState
+ ?.updatePagenation('next');
+ },
+ child: Icon(Icons.navigate_next),
+ ),
+ ],
+ ),
+ ),
+ Expanded(
+ child: TabBarView(
+ controller: _tabController,
+ children: _tabs.map((tab) {
+ return _tabWidgets[tab] ??
+ Center(child: Text("No content found"));
+ // return Center(
+ // child: EmailPage(
+ // key: _emailPageKey,
+ // ));
+ }).toList(),
+ ),
+ ),
+
+ // if (_tabs.isEmpty)
+ // Expanded(
+ // child: EmailPage(key: _emailPageKey),
+ // ),
+ // if (_tabs.isNotEmpty)
+ // Expanded(
+ // // child: Text('supposed to be mails'),
+ // child: TabBarView(
+ // controller: _tabController,
+ // children: _tabs
+ // .map((tab) => Center(child: Text('Results for $tab')))
+ // .toList(),
+ // ),
+ // ),
+ ],
+ ),
),
],
),
- ),
- // Main content
- Expanded(
- child: Column(
- children: [
- Container(
- padding: EdgeInsets.fromLTRB(8.0, 8.0, 8.0, 4.0),
- color: Color.fromARGB(42, 36, 102, 132),
- child: Row(
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- Container(
- width: 800,
- height: 40,
- child: TextField(
- decoration: InputDecoration(
- hintText: 'Search...',
- border: OutlineInputBorder(),
- prefixIcon: Icon(Icons.search),
- ),
- onSubmitted: (value) {
- if (value.isNotEmpty) {
- _performSearch(value, _selectedOption);
- }
- //this is the input box i mentioned
- // if (value == '') {
- // setState(() {
- // querySearches = false;
- // });
- // }
- // Future> results = apiService
- // .sonicSearch('INBOX', 20, 0, value);
- // // print(value);
- // print(results);
- // setState(() {
- // querySearches = true;
- // });
- },
- ),
- ),
- SizedBox(
- width: 16,
- ),
- Container(
- width: 80,
- height: 40,
- child: ElevatedButton(
- onPressed: _showOptionsSearchDialog,
- child: Icon(Icons.manage_search),
- ),
- )
- ],
+ if (!_isSidebarOpen)
+ Positioned(
+ bottom: 16,
+ left: 16,
+ child: FloatingActionButton(
+ child: Icon(Icons.menu),
+ onPressed: () {
+ setState(() {
+ _isSidebarOpen = true;
+ });
+ },
),
),
- Container(
- padding: EdgeInsets.all(0.0),
- color: Color.fromARGB(42, 36, 102, 132),
- child: Row(
- children: [
- Container(
- height: 2,
- )
- ],
- ),
- ),
- Container(
- color: Color.fromARGB(255, 131, 110, 143),
- child: TabBar(
- controller: _tabController,
- isScrollable: true,
- tabs: _tabs
- .asMap()
- .entries
- .map((entry) => Tab(
- child: Row(
- children: [
- Text(entry.value),
- if (entry.value != 'Emails')
- GestureDetector(
- onTap: () => _removeTab(entry.key),
- child: Icon(Icons.close, size: 16),
- ),
- ],
- ),
- ))
- .toList(),
- labelColor: Colors.white,
- indicatorColor: Colors.white,
- ),
- ),
- Container(
- // alignment: Alignment.topLeft,
- padding: EdgeInsets.all(8.0),
- color: Colors.white,
- child: Row(
- children: [
- ElevatedButton(
- onPressed: () {
- _emailPageKey.currentState!.isBackDisabled ? null: _emailPageKey.currentState
- ?.updatePagenation('back');
- },
- child: Icon(Icons.navigate_before),
- ),
- Builder(
- builder: (context) {
- final emailState = _emailPageKey.currentState;
- if (emailState == null) {
- // Schedule a rebuild once the state is available
- Future.microtask(() => setState(() {}));
- return Text('Loading...');
- }
-
- return ValueListenableBuilder(
- valueListenable: emailState.currentPageNotifier,
- builder: (context, value, _) => Text('$value'),
- );
- },
- ),
- ElevatedButton(
- onPressed: () {
- _emailPageKey.currentState
- ?.updatePagenation('next');
- },
- child: Icon(Icons.navigate_next),
- ),
- ],
- ),
- ),
- Expanded(
- child: TabBarView(
- controller: _tabController,
- children: _tabs.map((tab) {
- return _tabWidgets[tab] ??
- Center(child: Text("No content found"));
- // return Center(
- // child: EmailPage(
- // key: _emailPageKey,
- // ));
- }).toList(),
- ),
- ),
-
- // if (_tabs.isEmpty)
- // Expanded(
- // child: EmailPage(key: _emailPageKey),
- // ),
- // if (_tabs.isNotEmpty)
- // Expanded(
- // // child: Text('supposed to be mails'),
- // child: TabBarView(
- // controller: _tabController,
- // children: _tabs
- // .map((tab) => Center(child: Text('Results for $tab')))
- // .toList(),
- // ),
- // ),
- ],
- ),
- ),
- ],
- ),
- if (!_isSidebarOpen)
- Positioned(
- bottom: 16,
- left: 16,
- child: FloatingActionButton(
- child: Icon(Icons.menu),
- onPressed: () {
- setState(() {
- _isSidebarOpen = true;
- });
- },
+ ],
),
),
- ],
+ ),
+ ),
),
);
}
}
-// void _showPopupMenu(BuildContext context, Offset position) async {
-// final RenderBox overlay =
-// Overlay.of(context).context.findRenderObject() as RenderBox;
-
-// await showMenu(
-// context: context,
-// position: RelativeRect.fromLTRB(
-// position.dx,
-// position.dy,
-// overlay.size.width - position.dx,
-// overlay.size.height - position.dy,
-// ),
-// items: >[
-// PopupMenuItem(
-// value: 'Open',
-// child: Text('Open'),
-// ),
-// PopupMenuItem(
-// value: 'Reply',
-// child: Text('Reply'),
-// ),
-// PopupMenuItem(
-// value: 'Delete',
-// child: Text('Delete'),
-// ),
-// ],
-// );
-// }
-// }
diff --git a/lib/login.dart b/lib/login.dart
index ca2c3f9..1745355 100644
--- a/lib/login.dart
+++ b/lib/login.dart
@@ -1,25 +1,32 @@
import 'dart:convert';
// import 'package:crab_ui/api_service.dart';
+import 'package:go_router/go_router.dart';
+
import 'api_service.dart';
-import 'home_page.dart';
+// import 'home_page.dart';
import 'package:flutter/material.dart';
// import 'package:flutter_widget_from_html/flutter_widget_from_html.dart';
import 'package:http/http.dart' as http;
// import 'package:shared_preferences/shared_preferences.dart';
import 'package:flutter/services.dart' show rootBundle;
-class AuthService {
+class AuthService extends ChangeNotifier {
Future isUserLoggedIn() async {
+ // ApiService.ip = '127.0.0.1';
+ // ApiService.port = '3001';
+ // print("setted up");
+
+ // return true;
try {
final response =
- await http.get(Uri.http('localhost:6823', 'read-config'));
- // await http.get(Uri.parse('http://localhost:6823/read-config'));
+ await http.get(Uri.http('127.0.0.1:6767', 'login_check'));
print(response.statusCode);
print(response.body);
if (response.statusCode == 200) {
final data = jsonDecode(response.body);
- // return data['config'];
+ // print(data['state']);
+ // return true;
try {
var url = Uri.http('${data['ip']}:${data['port']}', 'is_logged_in');
var response = await http.get(url);
@@ -83,6 +90,7 @@ class LoginPage extends StatefulWidget {
}
class SplashScreen extends StatefulWidget {
+ //entry point
@override
_SplashScreenState createState() => _SplashScreenState();
}
@@ -92,19 +100,31 @@ class _SplashScreenState extends State {
@override
void initState() {
super.initState();
- _checkLoginStatus();
+ WidgetsBinding.instance.addPostFrameCallback((_) {
+ _checkLoginStatus();
+ });
}
Future _checkLoginStatus() async {
- // SharedPreferences prefs = await SharedPreferences.getInstance();
- // print(prefs);
- // bool isLoggedIn = prefs.getBool('isLoggedIn') ?? false;
- bool isLoggedIn = await _authService.isUserLoggedIn();
- print("is loogeed in $isLoggedIn");
- if (isLoggedIn) {
- Navigator.pushReplacementNamed(context, '/home');
- } else {
- Navigator.pushReplacementNamed(context, '/login');
+ try {
+ final response =
+ await http.get(Uri.parse('http://127.0.0.1:6767/login_check'));
+ print(response.statusCode);
+ print(response.body);
+ if (response.statusCode == 200) {
+ final data = jsonDecode(response.body);
+ print(data['state']);
+ if (data['state']) {
+ context.go("/home");
+ } else {
+ context.go("/login");
+ }
+ } else {
+ context.go("/login");
+ }
+ } catch (e) {
+ print("caught in checkloginstatus in login $e");
+ context.go("/login");
}
}
@@ -112,7 +132,7 @@ class _SplashScreenState extends State {
Widget build(BuildContext context) {
return Center(
child: Scaffold(
- body: Center(child: CircularProgressIndicator()),
+ body: Center(child: CircularProgressIndicator()), //nothing happens
),
);
}
@@ -126,13 +146,10 @@ class _LoginPageState extends State {
final TextEditingController _emailController = TextEditingController();
final TextEditingController _passwordController = TextEditingController();
- // final ConfigManager _configManager =
- // ConfigManager("${Directory.current.parent}../crabmail2.conf");
- // Key to identify the form
final _formKey = GlobalKey();
Future setIp(String ip) async {
- // _configManager.setField("api_addr", ip);
+ //this is not done :sob: :skull:
return false;
}
@@ -141,31 +158,16 @@ class _LoginPageState extends State {
}
Future login() async {
- bool result = await _handleLogin();
- if (result) {
- Navigator.pushReplacementNamed(context, '/home');
+ var result = await _handleLogin();
+ if (result[0]) {
+ ApiService.ip = result[1];
+ ApiService.port = result[2];
+ context.go('/home');
}
}
- // Future _checkConfiguration() async {
- // return false;
- // }
-
- // void checkLogin() async {
- // try {
- // var url = Uri.http('127.0.0.1:3001', 'is_logged_in');
- // var response = await http.get(url);
- // print(response.body);
- // if (response.statusCode == 200) {
- // print('all good on the west');
- // }
- // } catch (e) {
- // print(e);
- // }
- // // bool isLoggedIn = await _authService.isUserLoggedIn();
- // }
// Function to handle login action
- Future _handleLogin() async {
+ Future _handleLogin() async {
if (_formKey.currentState!.validate()) {
// Perform login action (e.g., authenticate with backend)
String ip = _ipController.text;
@@ -179,96 +181,64 @@ class _LoginPageState extends State {
print(ip);
print(port);
- String baseUrl = "http://$ip:$port";
- print("baseurl " + baseUrl);
- print(baseUrl);
try {
- final response =
- await http.get(Uri.parse('http://localhost:6823/read-config'));
+ Map requestBody = {
+ "user": email,
+ "password": password,
+ "ip": ip,
+ "port": port,
+ };
+ var url = Uri.http("$ip:6767", "login");
+
+ final response = await http.post(url,
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: jsonEncode(
+ requestBody)); //keep the port but change the ip to the server that will process it?
print(response.statusCode);
print(response.body);
if (response.statusCode == 200) {
final data = jsonDecode(response.body);
- // return data['config'];
+ if (data["state"] == true) {
+ try {
+ final response = await http
+ .get(Uri.parse('http://127.0.0.1:6767/login_check'));
+ print(response.statusCode);
+ print(response.body);
+ if (response.statusCode == 200) {
+ final data = jsonDecode(response.body);
+ print(data['state']);
+ if (data['state']) {
+ context.go("/home");
+ } else {
+ context.go("/login");
+ }
+ } else {
+ context.go("/login");
+ }
+ } catch (e) {
+ print("caught in checkloginstatus in login $e");
+ context.go("/login");
+ }
+ }
+ return [true, ip, port];
}
+ return [false];
} catch (e) {
print("caught in catch");
print(e);
- return false;
+ return [false];
}
- Map updates = {
- // "username": email,
- // "password": password,
- "ip": ip,
- "port": port,
- };
- print("past");
-
- try {
- final sending = await http.post(
- Uri.parse('http://localhost:6823/update-config'),
- headers: {'Content-Type': "application/json"},
- body: jsonEncode(updates));
- print("sending");
- } catch (e) {
- print(e);
- return false;
- }
- try {
- // String status = await http.post(Uri.parse(''))
- var url_log = Uri.http('$ip:$port', 'log_in');
- Map filteredData = {
- "email": email,
- "password": password,
- // 'email': updates['username'],
- // 'password': updates['password']
- };
- print(filteredData);
- var status = await http.post(
- url_log,
- headers: {
- 'Content-Type': 'application/json',
- },
- body: jsonEncode(filteredData),
- );
- if (status.statusCode == 200) {
- print('response status ${status.body}');
- if (status.body == "Successful log in") {
- ApiService.ip = ip;
- ApiService.port = port;
- return true;
- }
- }
- } catch (e) {
- print(e);
- return false;
- }
- print("after");
- return false;
- // final content = await _authService.readConfFile();
- // final config = await _authService.parseConfFile(content);
- // print("BASE URL ${config["base_url"]}");
- // print("api address ${config["api_addr"]}");
- // print(config);
- // const url = ''
- // Clear the input fields
- // _ipController.clear();
- // _portController.clear();
- // _emailController.clear();
- // _passwordController.clear();
}
- return false;
+ return [false];
}
@override
Widget build(BuildContext context) {
- // try {
- // _configManager.loadConfig();
- // print(_configManager.getField('base_url'));
- // } catch (e) {
- // print("broke at build $e");
- // }
- // _configManager.
+ _ipController.text = "127.0.0.1";
+ _portController.text = "3001";
+
return Center(
child: Scaffold(
appBar: AppBar(
@@ -303,14 +273,6 @@ class _LoginPageState extends State {
return 'Please enter your ip';
}
},
- // onSaved: (value) async {
- // final content = await _authService.readConfFile();
- // final config =
- // await _authService.parseConfFile(content);
- // print("BASE URL ${config["base_url"]}");
- // print("api address ${config["api_addr"]}");
- // //TODO: call a function to set the field ip in conf
- // },
),
),
Container(
@@ -389,17 +351,6 @@ class _LoginPageState extends State {
child: const Text('Login'),
),
),
- // SizedBox(
- // width: 200,
- // child: ElevatedButton(
- // // onPressed: checkLogin,
- // onPressed: () async {
- // await _authService.isUserLoggedIn();
- // // print(result);
- // },
- // child: const Text('checker'),
- // ),
- // )
],
),
),
diff --git a/lib/main.dart b/lib/main.dart
index da90a50..935ce87 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -1,12 +1,18 @@
import 'package:crab_ui/contact.dart';
+import 'package:crab_ui/email.dart';
import 'package:flutter/material.dart';
+import 'package:provider/provider.dart';
import 'home_page.dart';
import 'login.dart';
-
+import 'package:go_router/go_router.dart';
+import 'routingHandler.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
- runApp(HyM());
+ runApp(ChangeNotifierProvider(
+ create: (context) => AuthService(),
+ child: HyM(),
+ ));
}
class HyM extends StatelessWidget {
@@ -15,19 +21,44 @@ class HyM extends StatelessWidget {
@override
Widget build(BuildContext context) {
- return MaterialApp(
+ final GoRouter _router = GoRouter(
+ initialLocation: '/',
+ routes: [
+ GoRoute(
+ path: "/",
+ builder: (context, state) => SplashScreen(),
+ ),
+ GoRoute(
+ path: "/login",
+ builder: (context, state) => const LoginPage(),
+ ),
+ GoRoute(
+ path: "/home",
+ builder: (context, state) => HomeScreen(),
+ ),
+ GoRoute(
+ path: "/contacts",
+ builder: (context, state) => ContactsPage(),
+ ),
+ GoRoute(
+ path: "/email/:subject/:target/:viewspecs/:emailID",
+ builder: (context, state) {
+ final subject = state.pathParameters['subject']!;
+ final target = state.pathParameters['target']!;
+ final viewspecs = state.pathParameters['viewspecs']!;
+ final emailId = state.pathParameters['emailID']!;
+ return Routinghandler.fromParameters(
+ "main anchor", subject, target, viewspecs, emailId);
+ }),
+ ]);
+ return MaterialApp.router(
debugShowCheckedModeBanner: false,
- theme: ThemeData.light(),
+ theme: ThemeData(
+ colorScheme: ColorScheme.light(),
+ useMaterial3: true,
+ ),
title: 'HyM',
- // home: HomeScreen(),
- initialRoute: "/",
-
- routes: {
- "/": (context) => SplashScreen(),
- "/login": (context) => const LoginPage(),
- "/home": (context) => HomeScreen(),
- "/contacts": (context) => ContactsPage(),
- },
+ routerConfig: _router,
);
}
}
diff --git a/lib/routingHandler.dart b/lib/routingHandler.dart
new file mode 100644
index 0000000..1d51ba6
--- /dev/null
+++ b/lib/routingHandler.dart
@@ -0,0 +1,229 @@
+import 'package:crab_ui/collapsableEmails.dart';
+import 'package:flutter/material.dart';
+import 'package:go_router/go_router.dart';
+import 'package:markdown/markdown.dart' as md;
+import 'package:markdown_widget/markdown_widget.dart';
+import 'api_service.dart';
+import 'structs.dart';
+
+class Routinghandler extends StatefulWidget {
+ Routinghandler(String link) {
+ bool anchorDone = false;
+
+ bool docNameDone = false;
+
+ bool viewspecsDone = false;
+
+ bool targetDone = false;
+
+ bool emailIdDone = false;
+
+ for (int letter = 0; letter < link.length; letter++) {
+ if (!anchorDone) {
+ if (link[letter] != '<') {
+ //when the anchor hasnt been dissected
+ anchor += link[letter];
+ } else {
+ anchorDone = true;
+ }
+ } else if (!docNameDone) {
+ if (link[letter] != ',') {
+ //when the docName hasnt been dissected
+ docName += link[letter];
+ } else {
+ docNameDone = true;
+ }
+ } else if (!targetDone) {
+ if (link[letter] != ':') {
+ target += link[letter];
+ } else {
+ targetDone = true;
+ }
+ } else if (!viewspecsDone) {
+ if (link[letter] != '>') {
+ //when the docName hasnt been dissected
+ viewspecs += link[letter];
+ } else {
+ viewspecsDone = true;
+ }
+ } else if (!emailIdDone) {
+ emailID += link[letter];
+ }
+ }
+ anchor = anchor.trim();
+ docName = docName.trim();
+ target = target.trim();
+ viewspecs = viewspecs.trim();
+ print("inside constructor uwu $emailID");
+ emailID = emailID.trim();
+ }
+ Routinghandler.fromParameters(String anchor, String docName, String target,
+ String viewspecs, String emailID) {
+ this.anchor = anchor;
+ this.docName = docName;
+ this.viewspecs = viewspecs;
+ this.target = target;
+ this.emailID = emailID;
+ }
+ Routinghandler.copyConstructor(Routinghandler other) {
+ anchor = other.anchor;
+ docName = other.docName;
+ viewspecs = other.viewspecs;
+ target = other.target;
+ emailID = other.emailID;
+ }
+
+ String anchor = '';
+ String docName = '';
+ String viewspecs = '';
+ String target = '';
+ String emailID = '';
+
+ void goToLink() {
+ // bool anchorDone = false;
+
+ // bool docNameDone = false;
+
+ // bool viewspecsDone = false;
+
+ // bool targetDone = false;
+
+ // for (int letter = 0; letter < link.length; letter++) {
+ // if (!anchorDone) {
+ // if (link[letter] != '<') {
+ // //when the anchor hasnt been dissected
+ // anchor += link[letter];
+ // } else {
+ // anchorDone = true;
+ // }
+ // } else if (!docNameDone) {
+ // if (link[letter] != ',') {
+ // //when the docName hasnt been dissected
+ // docName += link[letter];
+ // } else {
+ // docNameDone = true;
+ // }
+ // } else if (!targetDone) {
+ // if (link[letter] != ':') {
+ // target += link[letter];
+ // } else {
+ // targetDone = true;
+ // }
+ // } else if (!viewspecsDone) {
+ // if (link[letter] != '>') {
+ // //when the docName hasnt been dissected
+ // viewspecs += link[letter];
+ // } else {
+ // viewspecsDone = true;
+ // }
+ // }
+ // }
+ print("anchor $anchor");
+ print("docName $docName");
+ print("target $target");
+ print("viewspecs $viewspecs");
+ print("emailID $emailID");
+ //now it should open a widget in that part
+ //maybe i need a rewrite
+ }
+
+ String getEmailID() {
+ return emailID;
+ }
+
+ @override
+ State createState() => _RoutingHandlerState();
+}
+
+class _RoutingHandlerState extends State {
+ List markdownContent = [];
+ bool _isLoaded = false;
+ AugmentTree? aug;
+
+ @override
+ void initState() {
+ // TODO: implement initState
+ super.initState();
+ _loadMarkdown();
+ }
+
+ Future _loadMarkdown() async {
+ String folder = ApiService.currFolder;
+ print(widget.getEmailID());
+ String emailID = widget.emailID;
+ print("inside _loadMarkdown in routinghandler $emailID");
+ markdownContent =
+ await ApiService().fetchMarkdownContent([emailID], folder);
+ // print(markdownContent);
+ aug = AugmentTree.fromMD(markdownContent[0]);
+ aug!.addNumbering();
+
+ setState(() {
+ _isLoaded = true;
+ });
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ if (!_isLoaded) {
+ return const Center(
+ child: CircularProgressIndicator(),
+ );
+ }
+ return Scaffold(
+ appBar: AppBar(
+ title: Text("Routing Handler"),
+ leading: IconButton(
+ onPressed: () {
+ GoRouter.of(context).go('/home');
+ },
+ icon: const Icon(Icons.arrow_back_ios)),
+ ),
+ body: ConstrainedBox(
+ constraints: BoxConstraints(
+ minHeight: 100,
+ maxHeight: MediaQuery.of(context).size.height * 0.7,
+ ),
+ child: SingleChildScrollView(
+ //inside here put the bunch rows
+ //make rows of markdownBlocks, but firstly i need to conveert the content into a tree
+ // child:MarkdownBlock(data: markdownContent[0])
+
+ child: Column(children: [
+ for (int i = 0; i < this.aug!.children![0]!.children.length; i++)
+ Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ // if (leftNumbering)
+ Padding(
+ padding: const EdgeInsets.fromLTRB(0, 10, 5, 0),
+ child: Text(
+ aug!.children![0]!.children![i]!.numbering,
+ style: TextStyle(
+ color: Color(Colors.purple[400]!.value)),
+ ),
+ ),
+ Expanded(
+ child: Align(
+ alignment: Alignment.topLeft,
+ child: Wrap(
+ children: [
+ MarkdownBlock(
+ data: aug!.children![0]!.children![i]!
+ .data ??
+ ''),
+ ],
+ ))),
+ Padding(
+ padding: const EdgeInsets.fromLTRB(0, 10, 5, 0),
+ child: Text(
+ aug!.children![0]!.children![i]!.numbering,
+ style: TextStyle(
+ color: Color(Colors.purple[400]!.value)),
+ ),
+ ),
+ ]),
+ ]))));
+ }
+}
diff --git a/lib/sonicEmailView.dart b/lib/sonicEmailView.dart
index bfe0bbe..aeb7301 100644
--- a/lib/sonicEmailView.dart
+++ b/lib/sonicEmailView.dart
@@ -1,145 +1,3 @@
-import 'package:crab_ui/augment.dart';
-import 'package:web/web.dart' as web;
-import 'dart:ui_web' as ui;
-import 'dart:js_interop';
-import 'structs.dart';
-import 'package:flutter/material.dart';
-
-class SonicEmailView extends StatefulWidget {
- SerializableMessage email;
- String emailHTML;
-
- SonicEmailView({required this.email, required this.emailHTML});
-
- @override
- _SonicEmailViewState createState() => _SonicEmailViewState();
-}
-
-class _SonicEmailViewState extends State {
- String viewTypeIDs = "";
- int heightOFViewtype = 0;
- bool _isLoaded = false;
-
- void _scrollToNumber(String spanId) {
- AugmentClasses.handleJump(spanId);
- }
-
- @override
- void initState() {
- super.initState();
- _init();
- }
-
- Future _init() async {
- await _registerViewFactory(widget.emailHTML);
- if (!mounted) return;
- setState(() {
- _isLoaded = true;
- });
- }
-
- Future _registerViewFactory(String currentContent) async {
- // setState(() { //update to do item per item
- // each item to have itsviewtype ID
- // is this necessarey here??
-
- //could just move to collapsable
-
- // for (var emailHTML in widget.threadHTML) {
- String viewTypeId = 'email-${DateTime.now().millisecondsSinceEpoch}';
-
- final ghost = web.document.createElement('div') as web.HTMLDivElement
- ..style.visibility = 'hidden'
- ..style.position = 'absolute'
- ..style.width = '100%'
- ..style.overflow = 'auto'
- ..innerHTML = currentContent.toJS;
- web.document.body?.append(ghost);
- await Future.delayed(Duration(milliseconds: 10));
-
- final heightOfEmail = ghost.scrollHeight;
- ghost.remove();
-
- final HTMLsnippet = web.document.createElement('div') as web.HTMLDivElement
- ..id = viewTypeId
- ..innerHTML = widget
- .emailHTML.toJS; // temporarily index because it has to do all of them
- HTMLsnippet.style
- ..width = '100%'
- ..height = '${heightOfEmail}px'
- ..overflow = 'auto'
- ..scrollBehavior = 'smooth';
-
- ui.platformViewRegistry.registerViewFactory(
- viewTypeId,
- (int viewId) => HTMLsnippet,
- );
- this.viewTypeIDs = viewTypeId;
- this.heightOFViewtype = heightOfEmail;
- print(viewTypeIDs);
- }
-
- @override
- Widget build(BuildContext context) {
- return _isLoaded
- ? Scaffold(
- appBar: AppBar(title: Text(widget.email.subject)),
- body: Stack(
- children: [
- Column(
- children: [
- EmailToolbar(
- onButtonPressed: () => {},
- onJumpToSpan: _scrollToNumber),
- Row(
- // title of email
- children: [
- Text(
- widget.email.subject,
- style: TextStyle(fontSize: 30),
- ),
- ],
- ),
- Row(
- children: [
- Text(
- 'from ${widget.email.name}',
- style: TextStyle(fontSize: 18),
- ),
- Text(
- '<${widget.email.from}>',
- style: TextStyle(fontSize: 18),
- ),
- Spacer(),
- Text(
- '${widget.email.date}',
- textAlign: TextAlign.right,
- )
- ],
- ),
- // TODO: make a case where if one of these is the user's email it just says me :)))))
- Row(
- children: [
- Text(
- 'to ${widget.email.to.toString()}',
- style: TextStyle(fontSize: 15),
- )
- ],
- ),
- Expanded(
- // child: SizedBox(
- // height: heightOFViewtype.toDouble(),
- child: HtmlElementView(
- key: UniqueKey(), viewType: this.viewTypeIDs,
- // ),
- ))
- ],
- ),
- ],
- ),
- )
- : const Center(
- child: CircularProgressIndicator(),
- );
- }
-}
+export 'SonicEmailViewStub.dart'
+ if (dart.library.js_interop) 'SonicEmailViewWeb.dart'
+ if (dart.library.io) 'SonicEmailViewAndroid.dart';
\ No newline at end of file
diff --git a/lib/structs.dart b/lib/structs.dart
index c7e9da4..2499c57 100644
--- a/lib/structs.dart
+++ b/lib/structs.dart
@@ -1,6 +1,7 @@
//data structures
import 'dart:typed_data';
+import 'package:markdown/markdown.dart' as md;
class GetThreadResponse {
final int id;
@@ -10,6 +11,7 @@ class GetThreadResponse {
final String from_name;
final String from_address;
final List to;
+ late bool seen;
GetThreadResponse({
required this.id,
@@ -19,19 +21,20 @@ class GetThreadResponse {
required this.from_name,
required this.from_address,
required this.to,
+ required this.seen,
});
factory GetThreadResponse.fromJson(Map json) {
var toList = json['to'] as List;
return GetThreadResponse(
- id: json['id'],
- messages: List.from(json['messages']),
- subject: json['subject'],
- date: DateTime.parse(json['date']),
- from_name: json['from_name'],
- from_address: json['from_address'],
- to: toList.map((i) => MailAddress.fromJson(i)).toList(),
- );
+ id: json['id'],
+ messages: List.from(json['messages']),
+ subject: json['subject'],
+ date: DateTime.parse(json['date']),
+ from_name: json['from_name'],
+ from_address: json['from_address'],
+ to: toList.map((i) => MailAddress.fromJson(i)).toList(),
+ seen: json['seen']);
}
}
@@ -78,7 +81,7 @@ class SerializableMessage {
required this.subject,
required this.date,
required this.uid,
- required this.list, //email list???
+ required this.list, //folder
required this.id,
required this.in_reply_to,
});
@@ -127,7 +130,8 @@ class AttachmentInfoList extends Iterable {
AttachmentInfoList(this._attachments);
factory AttachmentInfoList.fromJsonList(List