/// 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({ this.children, }); @override __TreeViewDataState createState() => __TreeViewDataState(); } class __TreeViewDataState extends State<_TreeViewData> { final ScrollController _controller = ScrollController(); final Stream stream = TreeViewStream().stream; 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) { assert(parent != null); assert(children != null); } @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, ); } } class TreeViewChildState extends State with Common { 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(); Offset position = box.localToGlobal(Offset.zero); //this is global position double y = position.dy; return y; } }