clean, and update to match the web
This commit is contained in:
		
							parent
							
								
									c1afc8875e
								
							
						
					
					
						commit
						3410007f55
					
				
					 3 changed files with 217 additions and 87 deletions
				
			
		| 
						 | 
				
			
			@ -1,6 +1,5 @@
 | 
			
		|||
import 'package:crab_ui/api_service.dart';
 | 
			
		||||
import 'package:crab_ui/attachmentDownload.dart';
 | 
			
		||||
import 'package:crab_ui/collapsableEmails.dart';
 | 
			
		||||
import 'package:crab_ui/structs.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:pointer_interceptor/pointer_interceptor.dart';
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,13 +7,19 @@ import 'package:markdown/markdown.dart' as md;
 | 
			
		|||
 | 
			
		||||
class CollapsableEmails extends StatefulWidget {
 | 
			
		||||
  final List<String> thread; // email id's in the form xyz@gmail.com
 | 
			
		||||
  final List<String> threadHTML;
 | 
			
		||||
  // final List<String> threadHTML;
 | 
			
		||||
  final List<String> threadMarkdown;
 | 
			
		||||
  final String threadIDs;
 | 
			
		||||
  final String? targetJumpNumbering;
 | 
			
		||||
  final String? targetViewspecs;
 | 
			
		||||
 | 
			
		||||
  CollapsableEmails(
 | 
			
		||||
      {required this.thread,
 | 
			
		||||
      required this.threadHTML,
 | 
			
		||||
      required this.threadIDs});
 | 
			
		||||
      required this.threadMarkdown,
 | 
			
		||||
      required this.threadIDs,
 | 
			
		||||
      this.targetJumpNumbering,
 | 
			
		||||
      this.targetViewspecs,
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  State<CollapsableEmails> createState() => _CollapsableEmailsState();
 | 
			
		||||
| 
						 | 
				
			
			@ -40,21 +46,41 @@ class _CollapsableEmailsState extends State<CollapsableEmails> {
 | 
			
		|||
  };
 | 
			
		||||
 | 
			
		||||
  List<String> tagsCollected = [];
 | 
			
		||||
  String markdown = '';
 | 
			
		||||
  List<String> allMarkdown = [];
 | 
			
		||||
  List<List<String>> sentinel = [];
 | 
			
		||||
  int level = 0;
 | 
			
		||||
  AugmentTree zoomTreeRoot = AugmentTree();
 | 
			
		||||
  late AugmentTree currentZoomNode;
 | 
			
		||||
  // late AugmentTree currentZoomNode;
 | 
			
		||||
  late List<AugmentTree> currentZoomTree = [];
 | 
			
		||||
  bool zoomOut = false;
 | 
			
		||||
  bool zoomIn = true;
 | 
			
		||||
  late List<AugmentTree> threadNodes = [];
 | 
			
		||||
  static bool leftNumbering = false;
 | 
			
		||||
  static bool rightNumbering = true;
 | 
			
		||||
  bool showWhole = false;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  void initState() {
 | 
			
		||||
    super.initState();
 | 
			
		||||
    _markdownConverter();
 | 
			
		||||
    threadNodes = [];
 | 
			
		||||
    currentZoomTree = [];
 | 
			
		||||
    // _markdownConverter();
 | 
			
		||||
    _serializableData(widget.threadIDs); // this
 | 
			
		||||
    _markdown2Tree(markdown);
 | 
			
		||||
    _buildForZooms();
 | 
			
		||||
    _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
 | 
			
		||||
| 
						 | 
				
			
			@ -62,10 +88,6 @@ class _CollapsableEmailsState extends State<CollapsableEmails> {
 | 
			
		|||
    super.dispose();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void _markdownConverter() async {
 | 
			
		||||
    markdown = html2md.convert(widget.threadHTML[0]);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void _add2Tree(AugmentTree tree, md.Element node2add) {
 | 
			
		||||
    // adds node to its corresponding place
 | 
			
		||||
    AugmentTree newNode = AugmentTree();
 | 
			
		||||
| 
						 | 
				
			
			@ -119,83 +141,67 @@ class _CollapsableEmailsState extends State<CollapsableEmails> {
 | 
			
		|||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void _markdown2Tree(String text) {
 | 
			
		||||
  void _markdown2Tree(List<String> text) {
 | 
			
		||||
    print("started markdown2tree");
 | 
			
		||||
    final List<md.Node> nakedList = md.Document().parseLines(text.split('\n'));
 | 
			
		||||
 | 
			
		||||
    for (var node in nakedList) {
 | 
			
		||||
      //maybe do an add function, but isn't this it?
 | 
			
		||||
      if (node is md.Element) {
 | 
			
		||||
        // print(node.textContent);
 | 
			
		||||
        AugmentTree temp = AugmentTree();
 | 
			
		||||
        temp.data = node.textContent;
 | 
			
		||||
        temp.ogTag = node.tag;
 | 
			
		||||
        if (hirarchyDict.containsKey(node.tag)) {
 | 
			
		||||
          _add2Tree(zoomTreeRoot, node);
 | 
			
		||||
    for (int emailsMD = 0; emailsMD < text.length; emailsMD++) {
 | 
			
		||||
      final List<md.Node> 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
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      currentZoomNode = zoomTreeRoot;
 | 
			
		||||
      if (!mounted) return;
 | 
			
		||||
      setState(() {
 | 
			
		||||
        _isLoaded = true;
 | 
			
		||||
      });
 | 
			
		||||
      zoomTreeRoot.addNumbering();
 | 
			
		||||
      threadNodes.add(zoomTreeRoot);
 | 
			
		||||
      currentZoomTree.add(zoomTreeRoot);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!mounted) return;
 | 
			
		||||
    setState(() {
 | 
			
		||||
      _isLoaded = true;
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void _goToChildren(int index) async {
 | 
			
		||||
    final target = currentZoomNode.children[index];
 | 
			
		||||
  void _goToChildren(int indexThread, int index) async {
 | 
			
		||||
    final target = currentZoomTree[indexThread].children[index];
 | 
			
		||||
    if (target.children.isNotEmpty) {
 | 
			
		||||
      setState(() {
 | 
			
		||||
        currentZoomNode = target;
 | 
			
		||||
        currentZoomTree[indexThread] = target;
 | 
			
		||||
      });
 | 
			
		||||
    } else {
 | 
			
		||||
      print("This child has no further children.");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // if (currentZoomNode.children.isNotEmpty) {
 | 
			
		||||
    //   setState(() {
 | 
			
		||||
    //     zoomIn = true;
 | 
			
		||||
    //     zoomOut = true;
 | 
			
		||||
    //     currentZoomNode = currentZoomNode.children[index];
 | 
			
		||||
    //     if (currentZoomNode.children[index].children.isEmpty) {
 | 
			
		||||
    //       print('disable in');
 | 
			
		||||
    //       setState(() {
 | 
			
		||||
    //         zoomIn = false;
 | 
			
		||||
    //       });
 | 
			
		||||
    //     }
 | 
			
		||||
    //   });
 | 
			
		||||
    // } else {
 | 
			
		||||
    //   print("disable zoom down");
 | 
			
		||||
    //   setState(() {
 | 
			
		||||
    //     zoomIn = false;
 | 
			
		||||
    //   });
 | 
			
		||||
    // }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void _goToParent() async {
 | 
			
		||||
    if (currentZoomNode.parent != null) {
 | 
			
		||||
  void _goToParent(int indexThread) async {
 | 
			
		||||
    if (currentZoomTree[indexThread].parent != null) {
 | 
			
		||||
      setState(() {
 | 
			
		||||
        currentZoomNode = currentZoomNode.parent!;
 | 
			
		||||
        currentZoomTree[indexThread] = currentZoomTree[indexThread].parent!;
 | 
			
		||||
      });
 | 
			
		||||
    } else {
 | 
			
		||||
      print("Already at root.");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // print("parent ${currentZoomNode.parent}");
 | 
			
		||||
    // print("parent ${currentZoomNode.parent!.parent}");
 | 
			
		||||
    // if (currentZoomNode.parent != null) {
 | 
			
		||||
    //   setState(() {
 | 
			
		||||
    //     currentZoomNode = currentZoomNode.parent!;
 | 
			
		||||
    //     if (currentZoomNode.parent == null) {
 | 
			
		||||
    //       setState(() {
 | 
			
		||||
    //         zoomOut = false;
 | 
			
		||||
    //       });
 | 
			
		||||
    //     }
 | 
			
		||||
    //   });
 | 
			
		||||
    // } else if (currentZoomNode.parent == null ||
 | 
			
		||||
    //     currentZoomNode.parent!.parent == null) {
 | 
			
		||||
    //   print("disable zoom up");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void _serializableData(String threadID) async {
 | 
			
		||||
| 
						 | 
				
			
			@ -207,19 +213,23 @@ class _CollapsableEmailsState extends State<CollapsableEmails> {
 | 
			
		|||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Widget _buildForZooms({Key? key}) {
 | 
			
		||||
  Widget _buildForZooms(int indexThread) {
 | 
			
		||||
    // IF I GIVE IT THE INDEX????
 | 
			
		||||
    if (!_isLoaded) {
 | 
			
		||||
      return const Center(child: CircularProgressIndicator()); // loading screen
 | 
			
		||||
    }
 | 
			
		||||
    final canZoomOut = currentZoomNode.parent != null;
 | 
			
		||||
 | 
			
		||||
    final AugmentTree currentZoomNodeForThisEmail =
 | 
			
		||||
        currentZoomTree[indexThread];
 | 
			
		||||
 | 
			
		||||
    final canZoomOut = currentZoomNodeForThisEmail.parent != null;
 | 
			
		||||
 | 
			
		||||
    return ListView.builder(
 | 
			
		||||
      key: key,
 | 
			
		||||
      itemCount: currentZoomNode.children.length,
 | 
			
		||||
      itemCount: currentZoomNodeForThisEmail.children.length,
 | 
			
		||||
      itemBuilder: (context, index) {
 | 
			
		||||
        final childNode = currentZoomNode.children[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(
 | 
			
		||||
| 
						 | 
				
			
			@ -236,25 +246,46 @@ class _CollapsableEmailsState extends State<CollapsableEmails> {
 | 
			
		|||
                    spacing: 4.0,
 | 
			
		||||
                    children: [
 | 
			
		||||
                      OutlinedButton(
 | 
			
		||||
                        onPressed: canZoomOut ? () => _goToParent() : null,
 | 
			
		||||
                        onPressed:
 | 
			
		||||
                            canZoomOut ? () => _goToParent(indexThread) : null,
 | 
			
		||||
                        child: Icon(Icons.north_west_sharp),
 | 
			
		||||
                      ),
 | 
			
		||||
                      OutlinedButton(
 | 
			
		||||
                        onPressed:
 | 
			
		||||
                            canZoomIn ? () => _goToChildren(index) : null,
 | 
			
		||||
                        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: currentZoomNode
 | 
			
		||||
                          .children[index].data, // one string of markdown
 | 
			
		||||
                      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)),
 | 
			
		||||
                      ),
 | 
			
		||||
                    ),
 | 
			
		||||
                ],
 | 
			
		||||
              ),
 | 
			
		||||
            ),
 | 
			
		||||
| 
						 | 
				
			
			@ -263,6 +294,100 @@ class _CollapsableEmailsState extends State<CollapsableEmails> {
 | 
			
		|||
      },
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
  void _handleJump(String queryNumbering) {
 | 
			
		||||
    print(queryNumbering);
 | 
			
		||||
    if (queryNumbering.isEmpty) {
 | 
			
		||||
      ScaffoldMessenger.of(context).showSnackBar(
 | 
			
		||||
        SnackBar(content: Text('Please enter a numbering to jump to.')),
 | 
			
		||||
      );
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    final int targetEmailIndex = _expandedEmails.first;
 | 
			
		||||
    if (targetEmailIndex >= threadNodes.length) {
 | 
			
		||||
      // Error handling
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    final AugmentTree rootOfCurrentEmail = threadNodes[targetEmailIndex];
 | 
			
		||||
    final AugmentTree? foundNode =
 | 
			
		||||
        _findNodeByNumbering(rootOfCurrentEmail, queryNumbering);
 | 
			
		||||
 | 
			
		||||
    if (foundNode != null) {
 | 
			
		||||
      setState(() {
 | 
			
		||||
        currentZoomTree[targetEmailIndex] = foundNode; // Update the state
 | 
			
		||||
      });
 | 
			
		||||
    } else {
 | 
			
		||||
      ScaffoldMessenger.of(context).showSnackBar(
 | 
			
		||||
        SnackBar(content: Text('Numbering "$queryNumbering" not found.')),
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void _handleViewspecs(String viewspecsQuery) {
 | 
			
		||||
    print(viewspecsQuery);
 | 
			
		||||
    if (viewspecsQuery.isEmpty) {
 | 
			
		||||
      ScaffoldMessenger.of(context).showSnackBar(
 | 
			
		||||
        SnackBar(content: Text('Please enter the viewspecs.')),
 | 
			
		||||
      );
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    final int targetEmailIndex = _expandedEmails.first;
 | 
			
		||||
    if (targetEmailIndex >= threadNodes.length) {
 | 
			
		||||
      // Error handling
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (viewspecsQuery.contains('n')) {
 | 
			
		||||
      setState(() {
 | 
			
		||||
        leftNumbering = false; // Update the state
 | 
			
		||||
        rightNumbering = false;
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
    if (viewspecsQuery.contains('m')) {
 | 
			
		||||
      setState(() {
 | 
			
		||||
        rightNumbering = true;
 | 
			
		||||
        leftNumbering = true;
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
    if (viewspecsQuery.contains('H')) {
 | 
			
		||||
      setState(() {
 | 
			
		||||
        leftNumbering = !leftNumbering;
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
    if (viewspecsQuery.contains('G')) {
 | 
			
		||||
      setState(() {
 | 
			
		||||
        rightNumbering = !rightNumbering;
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
    if (viewspecsQuery.contains('w')) {
 | 
			
		||||
      setState(() {
 | 
			
		||||
        showWhole = true;
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // else {
 | 
			
		||||
    //   ScaffoldMessenger.of(context).showSnackBar(
 | 
			
		||||
    //     SnackBar(content: Text('Numbering "$viewspecsQuery" not found.')),
 | 
			
		||||
    //   );
 | 
			
		||||
    // }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  AugmentTree? _findNodeByNumbering(AugmentTree root, String numbering) {
 | 
			
		||||
    //recursively finds the node you mentioned
 | 
			
		||||
    // to find the AugmentTree node corresponding to the `numbering`.
 | 
			
		||||
    if (root.numbering == numbering) {
 | 
			
		||||
      return root;
 | 
			
		||||
    }
 | 
			
		||||
    for (var child in root.children) {
 | 
			
		||||
      final found = _findNodeByNumbering(child, numbering);
 | 
			
		||||
      if (found != null) {
 | 
			
		||||
        return found;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
| 
						 | 
				
			
			@ -294,9 +419,10 @@ class _CollapsableEmailsState extends State<CollapsableEmails> {
 | 
			
		|||
                            constraints: BoxConstraints(
 | 
			
		||||
                                minHeight: 100,
 | 
			
		||||
                                maxHeight:
 | 
			
		||||
                                    MediaQuery.of(context).size.height * 0.6),
 | 
			
		||||
                            child:
 | 
			
		||||
                                _buildForZooms(key: ValueKey(currentZoomNode))),
 | 
			
		||||
                                    MediaQuery.of(context).size.height * 0.6,
 | 
			
		||||
                            ),
 | 
			
		||||
                            child: _buildForZooms(index),
 | 
			
		||||
                        ),
 | 
			
		||||
                      Divider(),
 | 
			
		||||
                    ],
 | 
			
		||||
                  );
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -43,6 +43,9 @@ class _EmailViewState extends State<EmailView> {
 | 
			
		|||
  void _scrollToNumber(String spanId) {
 | 
			
		||||
    // AugmentClasses.handleJump(spanId);
 | 
			
		||||
  }
 | 
			
		||||
  void _viewSpecs(String command){
 | 
			
		||||
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
| 
						 | 
				
			
			@ -56,7 +59,9 @@ class _EmailViewState extends State<EmailView> {
 | 
			
		|||
          children: [
 | 
			
		||||
            EmailToolbar(
 | 
			
		||||
              onButtonPressed: () => {}, 
 | 
			
		||||
              onJumpToSpan: _scrollToNumber
 | 
			
		||||
              onJumpToNumbering: _scrollToNumber,
 | 
			
		||||
              onViewspecs: _viewSpecs,
 | 
			
		||||
 | 
			
		||||
            ),
 | 
			
		||||
            Row(
 | 
			
		||||
              children: [
 | 
			
		||||
| 
						 | 
				
			
			@ -98,7 +103,7 @@ class _EmailViewState extends State<EmailView> {
 | 
			
		|||
            Expanded(
 | 
			
		||||
              child: CollapsableEmails(
 | 
			
		||||
                thread: widget.messages, 
 | 
			
		||||
                threadHTML: widget.emailContent, 
 | 
			
		||||
                threadMarkdown: widget.emailContent, 
 | 
			
		||||
                threadIDs: widget.id,
 | 
			
		||||
              ),
 | 
			
		||||
            ),
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		
		Reference in a new issue