218 lines
5.6 KiB
Dart
218 lines
5.6 KiB
Dart
/// 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<bool> streamController = StreamController<bool>.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<Widget> children;
|
|
final bool startExpanded;
|
|
|
|
TreeView({
|
|
Key key,
|
|
@required List<Widget> 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<Widget> 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<Widget> 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<Widget> 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<TreeViewChild> with Common {
|
|
bool isExpanded;
|
|
final GlobalKey<AnimatedListState> listKey = GlobalKey<AnimatedListState>();
|
|
|
|
@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: <Widget>[
|
|
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;
|
|
}
|
|
}
|