/// Tree view widget library library tree_view; import 'dart:async'; import 'package:aitrainer_app/util/common.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; class TreeViewStream { static final TreeViewStream _singleton = TreeViewStream._internal(); final StreamController streamController = StreamController.broadcast(); double positionY = 0; Stream get stream => streamController.stream; StreamController getStreamController() => streamController; factory TreeViewStream() => _singleton; TreeViewStream._internal(); void dispose() { streamController.close(); } } class TreeView extends InheritedWidget { final List children; final bool startExpanded; TreeView({ Key? key, required List children, bool startExpanded = false, }) : this.children = children, this.startExpanded = startExpanded, super( key: key, child: _TreeViewData( children: children, ), ); static TreeView? of(BuildContext context) { return context.dependOnInheritedWidgetOfExactType(aspect: TreeView); } @override bool updateShouldNotify(TreeView oldWidget) { if (oldWidget.children == this.children && oldWidget.startExpanded == this.startExpanded) { return false; } return true; } } class _TreeViewData extends StatefulWidget { final List children; _TreeViewData({ required this.children, }); @override __TreeViewDataState createState() => __TreeViewDataState(); } class __TreeViewDataState extends State<_TreeViewData> { final ScrollController _controller = ScrollController(); final Stream stream = TreeViewStream().stream; late var subscription; @override void initState() { super.initState(); /// We require the initializers to run after the loading screen is rendered SchedulerBinding.instance!.addPostFrameCallback((_) { final double cHeight = MediaQuery.of(context).size.height; subscription = stream.listen((value) { if (value) { final double positionY = TreeViewStream().positionY; /* print("pos " + positionY.toString() + " height: " + cHeight.toString() + " controller offset " + _controller.offset.toString() + " controller initial " + _controller.initialScrollOffset.toString()); */ if (positionY > cHeight - 190) { final double offset = positionY + 40; //print("antimateTo " + offset.toString()); _controller.animateTo(offset, duration: Duration(milliseconds: 300), curve: Curves.easeIn); } } }); }); } @override void dispose() { _controller.dispose(); subscription.cancel(); super.dispose(); } @override Widget build(BuildContext context) { return ListView.builder( scrollDirection: Axis.vertical, controller: _controller, itemCount: widget.children.length, itemBuilder: (context, index) { return widget.children.elementAt(index); }, ); } } class TreeViewChild extends StatefulWidget { final bool? startExpanded; final Widget parent; final List children; final VoidCallback? onTap; TreeViewChild({ required this.parent, required this.children, this.startExpanded, this.onTap, Key? key, }) : super(key: key); @override TreeViewChildState createState() => TreeViewChildState(); TreeViewChild copyWith( TreeViewChild source, { bool? startExpanded, Widget? parent, List? children, VoidCallback? onTap, }) { return TreeViewChild( parent: parent ?? source.parent, children: children ?? source.children, startExpanded: startExpanded ?? source.startExpanded, onTap: onTap ?? source.onTap, key: key, ); } } class TreeViewChildState extends State with Common { late bool? isExpanded; final GlobalKey listKey = GlobalKey(); @override void initState() { super.initState(); isExpanded = widget.startExpanded; } @override void didChangeDependencies() { isExpanded = widget.startExpanded ?? TreeView.of(context)!.startExpanded; super.didChangeDependencies(); } @override Widget build(BuildContext context) { return (Column( key: listKey, mainAxisSize: MainAxisSize.min, children: [ GestureDetector(child: widget.parent, onTap: widget.onTap ?? () => toggleExpanded()), Flexible( child: Container( child: AnimatedSwitcher( duration: Duration(milliseconds: 200), reverseDuration: Duration(milliseconds: 200), switchInCurve: Curves.easeIn, child: isExpanded! ? Column( mainAxisSize: MainAxisSize.min, children: widget.children, ) : Offstage(), ), ), ) ], )); } void toggleExpanded() { setState(() { this.isExpanded = !this.isExpanded!; TreeViewStream().positionY = getPosition(); TreeViewStream().getStreamController().add(this.isExpanded); }); } double getPosition() { RenderBox? box = listKey.currentContext!.findRenderObject() as RenderBox; Offset position = box.localToGlobal(Offset.zero); //this is global position double y = position.dy; return y; } }