Initial commit

This commit is contained in:
luckyrat 2021-07-07 10:14:27 +01:00
commit 6d8b63618b
26 changed files with 4424 additions and 0 deletions

View file

@ -0,0 +1,9 @@
library flutter_treeview;
export 'src/expander_theme_data.dart';
export 'src/models/node.dart';
export 'src/tree_node.dart';
export 'src/tree_view.dart';
export 'src/tree_view_controller.dart';
export 'src/tree_view_theme.dart';
export 'src/utilities.dart';

View file

@ -0,0 +1,133 @@
import 'package:flutter/widgets.dart';
import 'package:flutter_treeview/src/tree_view_theme.dart';
const double _kDefaultExpanderSize = 30.0;
/// Defines whether expander icon is shown on the
/// left or right side of the parent node label.
enum ExpanderPosition {
start,
end,
}
/// Defines the type expander icon displayed. All
/// types except the plus-minus type will be animated
enum ExpanderType {
caret,
arrow,
chevron,
plusMinus,
}
/// Defines whether expander icon has a circle or square shape
/// and whether it is outlined or filled.
enum ExpanderModifier {
none,
circleFilled,
circleOutlined,
squareFilled,
squareOutlined,
}
/// Defines the appearance of the expander icons.
///
/// Used by [TreeViewTheme] to control the appearance of the expander icons for a
/// parent tree node in the [TreeView] widget.
class ExpanderThemeData {
/// The [ExpanderPosition] for expander icon.
final ExpanderPosition position;
/// The [ExpanderType] for expander icon.
final ExpanderType type;
/// The size for expander icon.
final double size;
/// The color for expander icon.
final Color? color;
/// The [ExpanderModifier] for expander icon.
final ExpanderModifier modifier;
/// The animation state for expander icon. It determines whether
/// the icon animates when changing states
final bool animated;
const ExpanderThemeData({
this.color,
this.position: ExpanderPosition.start,
this.type: ExpanderType.caret,
this.size: _kDefaultExpanderSize,
this.modifier: ExpanderModifier.none,
this.animated: true,
});
/// Creates an expander icon theme with some reasonable default values.
///
/// The [color] is black,
/// the [position] is [ExpanderPosition.start],
/// the [type] is [ExpanderType.caret],
/// the [modifier] is [ExpanderModifier.none],
/// the [animated] property is true,
/// and the [size] is 30.0.
const ExpanderThemeData.fallback()
: color = const Color(0xFF000000),
position = ExpanderPosition.start,
type = ExpanderType.caret,
modifier = ExpanderModifier.none,
animated = true,
size = _kDefaultExpanderSize;
/// Creates a copy of this theme but with the given fields replaced with
/// the new values.
ExpanderThemeData copyWith({
Color? color,
ExpanderType? type,
ExpanderPosition? position,
ExpanderModifier? modifier,
bool? animated,
double? size,
}) {
return ExpanderThemeData(
color: color ?? this.color,
type: type ?? this.type,
position: position ?? this.position,
modifier: modifier ?? this.modifier,
size: size ?? this.size,
animated: animated ?? this.animated,
);
}
/// Returns a new theme that matches this expander theme but with some values
/// replaced by the non-null parameters of the given icon theme. If the given
/// expander theme is null, simply returns this theme.
ExpanderThemeData merge(ExpanderThemeData? other) {
if (other == null) return this;
return copyWith(
color: other.color,
type: other.type,
position: other.position,
modifier: other.modifier,
animated: other.animated,
size: other.size,
);
}
ExpanderThemeData resolve(BuildContext context) => this;
@override
bool operator ==(Object other) {
if (other.runtimeType != runtimeType) return false;
return other is ExpanderThemeData &&
other.color == color &&
other.position == position &&
other.type == type &&
other.modifier == modifier &&
other.animated == animated &&
other.size == size;
}
@override
int get hashCode =>
hashValues(color, position, type, size, modifier, animated);
}

179
lib/src/models/node.dart Normal file
View file

@ -0,0 +1,179 @@
import 'dart:convert';
import 'dart:ui';
import 'package:flutter/widgets.dart';
import '../tree_node.dart';
import '../utilities.dart';
/// Defines the data used to display a [TreeNode].
///
/// Used by [TreeView] to display a [TreeNode].
///
/// This object allows the creation of key, label and icon to display
/// a node on the [TreeView] widget. The key and label properties are
/// required. The key is needed for events that occur on the generated
/// [TreeNode]. It should always be unique.
class Node<T> {
/// The unique string that identifies this object.
final String key;
/// The string value that is displayed on the [TreeNode].
final String label;
/// An optional icon that is displayed on the [TreeNode].
final IconData? icon;
/// The open or closed state of the [TreeNode]. Applicable only if the
/// node is a parent
final bool expanded;
/// Generic data model that can be assigned to the [TreeNode]. This makes
/// it useful to assign and retrieve data associated with the [TreeNode]
final T? data;
/// The sub [Node]s of this object.
final List<Node> children;
/// Force the node to be a parent so that node can show expander without
/// having children node.
final bool parent;
const Node({
required this.key,
required this.label,
this.children: const [],
this.expanded: false,
this.parent: false,
this.icon,
this.data,
});
/// Creates a [Node] from a string value. It generates a unique key.
factory Node.fromLabel(String label) {
String _key = Utilities.generateRandom();
return Node(
key: '${_key}_$label',
label: label,
);
}
/// Creates a [Node] from a Map<String, dynamic> map. The map
/// should contain a "label" value. If the key value is
/// missing, it generates a unique key.
/// If the expanded value, if present, can be any 'truthful'
/// value. Excepted values include: 1, yes, true and their
/// associated string values.
factory Node.fromMap(Map<String, dynamic> map) {
String? _key = map['key'];
String _label = map['label'];
var _data = map['data'];
List<Node> _children = [];
if (_key == null) {
_key = Utilities.generateRandom();
}
// if (map['icon'] != null) {
// int _iconData = int.parse(map['icon']);
// if (map['icon'].runtimeType == String) {
// _iconData = int.parse(map['icon']);
// } else if (map['icon'].runtimeType == double) {
// _iconData = (map['icon'] as double).toInt();
// } else {
// _iconData = map['icon'];
// }
// _icon = const IconData(_iconData);
// }
if (map['children'] != null) {
List<Map<String, dynamic>> _childrenMap = List.from(map['children']);
_children = _childrenMap
.map((Map<String, dynamic> child) => Node.fromMap(child))
.toList();
}
return Node(
key: '$_key',
label: _label,
data: _data,
expanded: Utilities.truthful(map['expanded']),
parent: Utilities.truthful(map['parent']),
children: _children,
);
}
/// Creates a copy of this object but with the given fields
/// replaced with the new values.
Node copyWith({
String? key,
String? label,
List<Node>? children,
bool? expanded,
bool? parent,
IconData? icon,
T? data,
}) =>
Node(
key: key ?? this.key,
label: label ?? this.label,
icon: icon ?? this.icon,
expanded: expanded ?? this.expanded,
parent: parent ?? this.parent,
children: children ?? this.children,
data: data ?? this.data,
);
/// Whether this object has children [Node].
bool get isParent => children.isNotEmpty || parent;
/// Whether this object has a non-null icon.
bool get hasIcon => icon != null && icon != null;
/// Whether this object has data associated with it.
bool get hasData => data != null;
/// Map representation of this object
Map<String, dynamic> get asMap {
Map<String, dynamic> _map = {
"key": key,
"label": label,
"icon": icon == null ? null : icon!.codePoint,
"expanded": expanded,
"parent": parent,
"children": children.map((Node child) => child.asMap).toList(),
};
if (data != null) {
_map['data'] = data;
}
//TODO: figure out a means to check for getter or method on generic to include map from generic
return _map;
}
@override
String toString() {
return JsonEncoder().convert(asMap);
}
@override
int get hashCode {
return hashValues(
key,
label,
icon,
expanded,
parent,
children,
);
}
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
if (other.runtimeType != runtimeType) return false;
return other is Node &&
other.key == key &&
other.label == label &&
other.icon == icon &&
other.expanded == expanded &&
other.parent == parent &&
other.data.runtimeType == T &&
other.children.length == children.length;
}
}

479
lib/src/tree_node.dart Normal file
View file

@ -0,0 +1,479 @@
import 'dart:math' show pi;
import 'package:flutter/material.dart';
import 'tree_view.dart';
import 'tree_view_theme.dart';
import 'expander_theme_data.dart';
import 'models/node.dart';
const double _kBorderWidth = 0.75;
/// Defines the [TreeNode] widget.
///
/// This widget is used to display a tree node and its children. It requires
/// a single [Node] value. It uses this node to display the state of the
/// widget. It uses the [TreeViewTheme] to handle the appearance and the
/// [TreeView] properties to handle to user actions.
///
/// __This class should not be used directly!__
/// The [TreeView] and [TreeViewController] handlers the data and rendering
/// of the nodes.
class TreeNode extends StatefulWidget {
/// The node object used to display the widget state
final Node node;
const TreeNode({Key? key, required this.node}) : super(key: key);
@override
_TreeNodeState createState() => _TreeNodeState();
}
class _TreeNodeState extends State<TreeNode>
with SingleTickerProviderStateMixin {
static final Animatable<double> _easeInTween =
CurveTween(curve: Curves.easeIn);
late AnimationController _controller;
late Animation<double> _heightFactor;
bool _isExpanded = false;
@override
void initState() {
super.initState();
_controller =
AnimationController(duration: Duration(milliseconds: 200), vsync: this);
_heightFactor = _controller.drive(_easeInTween);
_isExpanded = widget.node.expanded;
if (_isExpanded) _controller.value = 1.0;
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
TreeView? _treeView = TreeView.of(context);
_controller.duration = _treeView!.theme.expandSpeed;
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
void didUpdateWidget(TreeNode oldWidget) {
if (widget.node.expanded != oldWidget.node.expanded) {
setState(() {
_isExpanded = widget.node.expanded;
if (_isExpanded) {
_controller.forward();
} else {
_controller.reverse().then<void>((void value) {
if (!mounted) return;
setState(() {});
});
}
});
} else if (widget.node != oldWidget.node) {
setState(() {});
}
super.didUpdateWidget(oldWidget);
}
void _handleExpand() {
TreeView? _treeView = TreeView.of(context);
assert(_treeView != null, 'TreeView must exist in context');
setState(() {
_isExpanded = !_isExpanded;
if (_isExpanded) {
_controller.forward();
} else {
_controller.reverse().then<void>((void value) {
if (!mounted) return;
setState(() {});
});
}
});
if (_treeView!.onExpansionChanged != null)
_treeView.onExpansionChanged!(widget.node.key, _isExpanded);
}
void _handleTap() {
TreeView? _treeView = TreeView.of(context);
assert(_treeView != null, 'TreeView must exist in context');
if (_treeView!.onNodeTap != null) {
_treeView.onNodeTap!(widget.node.key);
}
}
void _handleDoubleTap() {
TreeView? _treeView = TreeView.of(context);
assert(_treeView != null, 'TreeView must exist in context');
if (_treeView!.onNodeDoubleTap != null) {
_treeView.onNodeDoubleTap!(widget.node.key);
}
}
Widget _buildNodeExpander() {
TreeView? _treeView = TreeView.of(context);
assert(_treeView != null, 'TreeView must exist in context');
TreeViewTheme _theme = _treeView!.theme;
return widget.node.isParent
? GestureDetector(
onTap: () => _handleExpand(),
child: _TreeNodeExpander(
speed: _controller.duration!,
expanded: widget.node.expanded,
themeData: _theme.expanderTheme,
),
)
: Container(width: _theme.expanderTheme.size);
}
Widget _buildNodeIcon() {
TreeView? _treeView = TreeView.of(context);
assert(_treeView != null, 'TreeView must exist in context');
TreeViewTheme _theme = _treeView!.theme;
bool isSelected = _treeView.controller.selectedKey != null &&
_treeView.controller.selectedKey == widget.node.key;
return Container(
alignment: Alignment.center,
width:
widget.node.hasIcon ? _theme.iconTheme.size! + _theme.iconPadding : 0,
child: widget.node.hasIcon
? Icon(
widget.node.icon,
size: _theme.iconTheme.size,
color: isSelected
? _theme.colorScheme.onPrimary
: _theme.iconTheme.color,
)
: null,
);
}
Widget _buildNodeLabel() {
TreeView? _treeView = TreeView.of(context);
assert(_treeView != null, 'TreeView must exist in context');
TreeViewTheme _theme = _treeView!.theme;
bool isSelected = _treeView.controller.selectedKey != null &&
_treeView.controller.selectedKey == widget.node.key;
final icon = _buildNodeIcon();
return Container(
padding: EdgeInsets.symmetric(
vertical: _theme.verticalSpacing ?? (_theme.dense ? 10 : 15),
horizontal: 0,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
icon,
Expanded(
child: Text(
widget.node.label,
softWrap: widget.node.isParent
? _theme.parentLabelOverflow == null
: _theme.labelOverflow == null,
overflow: widget.node.isParent
? _theme.parentLabelOverflow
: _theme.labelOverflow,
style: widget.node.isParent
? _theme.parentLabelStyle.copyWith(
fontWeight: _theme.parentLabelStyle.fontWeight,
color: isSelected
? _theme.colorScheme.onPrimary
: _theme.parentLabelStyle.color,
)
: _theme.labelStyle.copyWith(
fontWeight: _theme.labelStyle.fontWeight,
color: isSelected ? _theme.colorScheme.onPrimary : null,
),
),
),
],
),
);
}
Widget _buildNodeWidget() {
TreeView? _treeView = TreeView.of(context);
assert(_treeView != null, 'TreeView must exist in context');
TreeViewTheme _theme = _treeView!.theme;
bool isSelected = _treeView.controller.selectedKey != null &&
_treeView.controller.selectedKey == widget.node.key;
bool canSelectParent = _treeView.allowParentSelect;
final arrowContainer = _buildNodeExpander();
final labelContainer = _treeView.nodeBuilder != null
? _treeView.nodeBuilder!(context, widget.node)
: _buildNodeLabel();
Widget _tappable = _treeView.onNodeDoubleTap != null
? InkWell(
onTap: _handleTap,
onDoubleTap: _handleDoubleTap,
child: labelContainer,
)
: InkWell(
onTap: _handleTap,
child: labelContainer,
);
if (widget.node.isParent) {
if (_treeView.supportParentDoubleTap && canSelectParent) {
_tappable = InkWell(
onTap: canSelectParent ? _handleTap : _handleExpand,
onDoubleTap: () {
_handleExpand();
_handleDoubleTap();
},
child: labelContainer,
);
} else if (_treeView.supportParentDoubleTap) {
_tappable = InkWell(
onTap: _handleExpand,
onDoubleTap: _handleDoubleTap,
child: labelContainer,
);
} else {
_tappable = InkWell(
onTap: canSelectParent ? _handleTap : _handleExpand,
child: labelContainer,
);
}
}
return Container(
color: isSelected ? _theme.colorScheme.primary : null,
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: _theme.expanderTheme.position == ExpanderPosition.end
? <Widget>[
Expanded(
child: _tappable,
),
arrowContainer,
]
: <Widget>[
arrowContainer,
Expanded(
child: _tappable,
),
],
),
);
}
@override
Widget build(BuildContext context) {
TreeView? _treeView = TreeView.of(context);
assert(_treeView != null, 'TreeView must exist in context');
final bool closed =
(!_isExpanded || !widget.node.expanded) && _controller.isDismissed;
final nodeWidget = _buildNodeWidget();
return widget.node.isParent
? AnimatedBuilder(
animation: _controller.view,
builder: (BuildContext context, Widget? child) {
return Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
nodeWidget,
ClipRect(
child: Align(
heightFactor: _heightFactor.value,
child: child,
),
),
],
);
},
child: closed
? null
: Container(
margin: EdgeInsets.only(
left: _treeView!.theme.horizontalSpacing ??
_treeView.theme.iconTheme.size!),
child: Column(
mainAxisSize: MainAxisSize.min,
children: widget.node.children.map((Node node) {
return TreeNode(node: node);
}).toList()),
),
)
: Container(
child: nodeWidget,
);
}
}
class _TreeNodeExpander extends StatefulWidget {
final ExpanderThemeData themeData;
final bool expanded;
final Duration _expandSpeed;
const _TreeNodeExpander({
required Duration speed,
required this.themeData,
required this.expanded,
}) : _expandSpeed = speed;
@override
_TreeNodeExpanderState createState() => _TreeNodeExpanderState();
}
class _TreeNodeExpanderState extends State<_TreeNodeExpander>
with SingleTickerProviderStateMixin {
late Animation<double> animation;
late AnimationController controller;
@override
void initState() {
bool isEnd = widget.themeData.position == ExpanderPosition.end;
if (widget.themeData.type != ExpanderType.plusMinus) {
controller = AnimationController(
duration: widget.themeData.animated
? isEnd
? widget._expandSpeed * 0.625
: widget._expandSpeed
: Duration(milliseconds: 0),
vsync: this,
);
animation = Tween<double>(
begin: 0,
end: isEnd ? 180 : 90,
).animate(controller);
} else {
controller =
AnimationController(duration: Duration(milliseconds: 0), vsync: this);
animation = Tween<double>(begin: 0, end: 0).animate(controller);
}
super.initState();
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
@override
void didUpdateWidget(_TreeNodeExpander oldWidget) {
if (widget.themeData != oldWidget.themeData ||
widget.expanded != oldWidget.expanded) {
bool isEnd = widget.themeData.position == ExpanderPosition.end;
setState(() {
if (widget.themeData.type != ExpanderType.plusMinus) {
controller.duration = widget.themeData.animated
? isEnd
? widget._expandSpeed * 0.625
: widget._expandSpeed
: Duration(milliseconds: 0);
animation = Tween<double>(
begin: 0,
end: isEnd ? 180 : 90,
).animate(controller);
} else {
controller.duration = Duration(milliseconds: 0);
animation = Tween<double>(begin: 0, end: 0).animate(controller);
}
});
}
super.didUpdateWidget(oldWidget);
}
Color? _onColor(Color? color) {
if (color != null) {
if (color.computeLuminance() > 0.6) {
return Colors.black;
} else {
return Colors.white;
}
}
return null;
}
@override
Widget build(BuildContext context) {
IconData _arrow;
double _iconSize = widget.themeData.size;
double _borderWidth = 0;
BoxShape _shapeBorder = BoxShape.rectangle;
Color _backColor = Colors.transparent;
Color? _iconColor =
widget.themeData.color ?? Theme.of(context).iconTheme.color;
switch (widget.themeData.modifier) {
case ExpanderModifier.none:
break;
case ExpanderModifier.circleFilled:
_shapeBorder = BoxShape.circle;
_backColor = widget.themeData.color ?? Colors.black;
_iconColor = _onColor(_backColor);
break;
case ExpanderModifier.circleOutlined:
_borderWidth = _kBorderWidth;
_shapeBorder = BoxShape.circle;
break;
case ExpanderModifier.squareFilled:
_backColor = widget.themeData.color ?? Colors.black;
_iconColor = _onColor(_backColor);
break;
case ExpanderModifier.squareOutlined:
_borderWidth = _kBorderWidth;
break;
}
switch (widget.themeData.type) {
case ExpanderType.chevron:
_arrow = Icons.expand_more;
break;
case ExpanderType.arrow:
_arrow = Icons.arrow_downward;
_iconSize = widget.themeData.size > 20
? widget.themeData.size - 8
: widget.themeData.size;
break;
case ExpanderType.caret:
_arrow = Icons.arrow_drop_down;
break;
case ExpanderType.plusMinus:
_arrow = widget.expanded ? Icons.remove : Icons.add;
break;
}
Icon _icon = Icon(
_arrow,
size: _iconSize,
color: _iconColor,
);
if (widget.expanded) {
controller.reverse();
} else {
controller.forward();
}
return Container(
width: widget.themeData.size + 2,
height: widget.themeData.size + 2,
alignment: Alignment.center,
decoration: BoxDecoration(
shape: _shapeBorder,
border: _borderWidth == 0
? null
: Border.all(
width: _borderWidth,
color: widget.themeData.color ?? Colors.black,
),
color: _backColor,
),
child: AnimatedBuilder(
animation: controller,
child: _icon,
builder: (context, child) {
return Transform.rotate(
angle: animation.value * (-pi / 180),
child: child,
);
},
),
);
}
}

142
lib/src/tree_view.dart Normal file
View file

@ -0,0 +1,142 @@
import 'package:flutter/material.dart';
import 'tree_view_controller.dart';
import 'tree_view_theme.dart';
import 'tree_node.dart';
import 'models/node.dart';
/// Defines the [TreeView] widget.
///
/// This is the main widget for the package. It requires a controller
/// and allows you to specify other optional properties that manages
/// the appearance and handle events.
///
/// ```dart
/// TreeView(
/// controller: _treeViewController,
/// allowParentSelect: false,
/// supportParentDoubleTap: false,
/// onExpansionChanged: _expandNodeHandler,
/// onNodeTap: (key) {
/// setState(() {
/// _treeViewController = _treeViewController.copyWith(selectedKey: key);
/// });
/// },
/// theme: treeViewTheme
/// ),
/// ```
class TreeView extends InheritedWidget {
/// The controller for the [TreeView]. It manages the data and selected key.
final TreeViewController controller;
/// The tap handler for a node. Passes the node key.
final Function(String)? onNodeTap;
/// Custom builder for nodes. Parameters are the build context and tree node.
final Widget Function(BuildContext, Node)? nodeBuilder;
/// The double tap handler for a node. Passes the node key.
final Function(String)? onNodeDoubleTap;
/// The expand/collapse handler for a node. Passes the node key and the
/// expansion state.
final Function(String, bool)? onExpansionChanged;
/// The theme for [TreeView].
final TreeViewTheme theme;
/// Determines whether the user can select a parent node. If false,
/// tapping the parent will expand or collapse the node. If true, the node
/// will be selected and the use has to use the expander to expand or
/// collapse the node.
final bool allowParentSelect;
/// How the [TreeView] should respond to user input.
final ScrollPhysics? physics;
/// Whether the extent of the [TreeView] should be determined by the contents
/// being viewed.
///
/// Defaults to false.
final bool shrinkWrap;
/// Whether the [TreeView] is the primary scroll widget associated with the
/// parent PrimaryScrollController..
///
/// Defaults to true.
final bool primary;
/// Determines whether the parent node can receive a double tap. This is
/// useful if [allowParentSelect] is true. This allows the user to double tap
/// the parent node to expand or collapse the parent when [allowParentSelect]
/// is true.
/// ___IMPORTANT___
/// _When true, the tap handler is delayed. This is because the double tap
/// action requires a short delay to determine whether the user is attempting
/// a single or double tap._
final bool supportParentDoubleTap;
TreeView({
Key? key,
required this.controller,
this.onNodeTap,
this.onNodeDoubleTap,
this.physics,
this.onExpansionChanged,
this.allowParentSelect: false,
this.supportParentDoubleTap: false,
this.shrinkWrap: false,
this.primary: true,
this.nodeBuilder,
TreeViewTheme? theme,
}) : this.theme = theme ?? const TreeViewTheme(),
super(
key: key,
child: _TreeViewData(
controller,
shrinkWrap: shrinkWrap,
primary: primary,
physics: physics,
),
);
static TreeView? of(BuildContext context) =>
context.dependOnInheritedWidgetOfExactType(aspect: TreeView);
@override
bool updateShouldNotify(TreeView oldWidget) {
return oldWidget.controller.children != this.controller.children ||
oldWidget.onNodeTap != this.onNodeTap ||
oldWidget.onExpansionChanged != this.onExpansionChanged ||
oldWidget.theme != this.theme ||
oldWidget.supportParentDoubleTap != this.supportParentDoubleTap ||
oldWidget.allowParentSelect != this.allowParentSelect;
}
}
class _TreeViewData extends StatelessWidget {
final TreeViewController _controller;
final bool? shrinkWrap;
final bool? primary;
final ScrollPhysics? physics;
const _TreeViewData(this._controller,
{this.shrinkWrap, this.primary, this.physics});
@override
Widget build(BuildContext context) {
ThemeData _parentTheme = Theme.of(context);
return Theme(
data: _parentTheme.copyWith(hoverColor: Colors.grey.shade100),
child: ListView(
shrinkWrap: shrinkWrap!,
primary: primary,
physics: physics,
padding: EdgeInsets.zero,
children: _controller.children.map((Node node) {
return TreeNode(node: node);
}).toList(),
),
);
}
}

View file

@ -0,0 +1,487 @@
import 'dart:convert' show jsonDecode, jsonEncode;
import 'models/node.dart';
/// Defines the insertion mode adding a new [Node] to the [TreeView].
enum InsertMode {
prepend,
append,
insert,
}
/// Defines the controller needed to display the [TreeView].
///
/// Used by [TreeView] to display the nodes and selected node.
///
/// This class also defines methods used to manipulate data in
/// the [TreeView]. The methods ([addNode], [updateNode],
/// and [deleteNode]) are non-mutilating, meaning they will not
/// modify the tree but instead they will return a mutilated
/// copy of the data. You can then use your own logic to appropriately
/// update the [TreeView]. e.g.
///
/// ```dart
/// TreeViewController controller = TreeViewController(children: nodes);
/// Node node = controller.getNode('unique_key')!;
/// Node updatedNode = node.copyWith(
/// key: 'another_unique_key',
/// label: 'Another Node',
/// );
/// List<Node> newChildren = controller.updateNode(node.key, updatedNode);
/// controller = TreeViewController(children: newChildren);
/// ```
class TreeViewController {
/// The data for the [TreeView].
final List<Node> children;
/// The key of the select node in the [TreeView].
final String? selectedKey;
TreeViewController({
this.children: const [],
this.selectedKey,
});
/// Creates a copy of this controller but with the given fields
/// replaced with the new values.
TreeViewController copyWith({List<Node>? children, String? selectedKey}) {
return TreeViewController(
children: children ?? this.children,
selectedKey: selectedKey ?? this.selectedKey,
);
}
/// Loads this controller with data from a JSON String
/// This method expects the user to properly update the state
///
/// ```dart
/// setState((){
/// controller = controller.loadJSON(json: jsonString);
/// });
/// ```
TreeViewController loadJSON({String json: '[]'}) {
List jsonList = jsonDecode(json);
List<Map<String, dynamic>> list = List<Map<String, dynamic>>.from(jsonList);
return loadMap(list: list);
}
/// Loads this controller with data from a Map.
/// This method expects the user to properly update the state
///
/// ```dart
/// setState((){
/// controller = controller.loadMap(map: dataMap);
/// });
/// ```
TreeViewController loadMap({List<Map<String, dynamic>> list: const []}) {
List<Node> treeData =
list.map((Map<String, dynamic> item) => Node.fromMap(item)).toList();
return TreeViewController(
children: treeData,
selectedKey: this.selectedKey,
);
}
/// Adds a new node to an existing node identified by specified key.
/// It returns a new controller with the new node added. This method
/// expects the user to properly place this call so that the state is
/// updated.
///
/// See [TreeViewController.addNode] for info on optional parameters.
///
/// ```dart
/// setState((){
/// controller = controller.withAddNode(key, newNode);
/// });
/// ```
TreeViewController withAddNode(
String key,
Node newNode, {
Node? parent,
int? index,
InsertMode mode: InsertMode.append,
}) {
List<Node> _data =
addNode(key, newNode, parent: parent, mode: mode, index: index);
return TreeViewController(
children: _data,
selectedKey: this.selectedKey,
);
}
/// Replaces an existing node identified by specified key with a new node.
/// It returns a new controller with the updated node replaced. This method
/// expects the user to properly place this call so that the state is
/// updated.
///
/// See [TreeViewController.updateNode] for info on optional parameters.
///
/// ```dart
/// setState((){
/// controller = controller.withUpdateNode(key, newNode);
/// });
/// ```
TreeViewController withUpdateNode(String key, Node newNode, {Node? parent}) {
List<Node> _data = updateNode(key, newNode, parent: parent);
return TreeViewController(
children: _data,
selectedKey: this.selectedKey,
);
}
/// Removes an existing node identified by specified key.
/// It returns a new controller with the node removed. This method
/// expects the user to properly place this call so that the state is
/// updated.
///
/// See [TreeViewController.deleteNode] for info on optional parameters.
///
/// ```dart
/// setState((){
/// controller = controller.withDeleteNode(key);
/// });
/// ```
TreeViewController withDeleteNode(String key, {Node? parent}) {
List<Node> _data = deleteNode(key, parent: parent);
return TreeViewController(
children: _data,
selectedKey: this.selectedKey,
);
}
/// Toggles the expanded property of an existing node identified by
/// specified key. It returns a new controller with the node toggled.
/// This method expects the user to properly place this call so
/// that the state is updated.
///
/// See [TreeViewController.toggleNode] for info on optional parameters.
///
/// ```dart
/// setState((){
/// controller = controller.withToggleNode(key, newNode);
/// });
/// ```
TreeViewController withToggleNode(String key, {Node? parent}) {
List<Node> _data = toggleNode(key, parent: parent);
return TreeViewController(
children: _data,
selectedKey: this.selectedKey,
);
}
/// Expands all nodes down to Node identified by specified key.
/// It returns a new controller with the nodes expanded.
/// This method expects the user to properly place this call so
/// that the state is updated.
///
/// Internally uses [TreeViewController.expandToNode].
///
/// ```dart
/// setState((){
/// controller = controller.withExpandToNode(key, newNode);
/// });
/// ```
TreeViewController withExpandToNode(String key) {
List<Node> _data = expandToNode(key);
return TreeViewController(
children: _data,
selectedKey: this.selectedKey,
);
}
/// Collapses all nodes down to Node identified by specified key.
/// It returns a new controller with the nodes collapsed.
/// This method expects the user to properly place this call so
/// that the state is updated.
///
/// Internally uses [TreeViewController.collapseToNode].
///
/// ```dart
/// setState((){
/// controller = controller.withCollapseToNode(key, newNode);
/// });
/// ```
TreeViewController withCollapseToNode(String key) {
List<Node> _data = collapseToNode(key);
return TreeViewController(
children: _data,
selectedKey: this.selectedKey,
);
}
/// Expands all nodes down to parent Node.
/// It returns a new controller with the nodes expanded.
/// This method expects the user to properly place this call so
/// that the state is updated.
///
/// Internally uses [TreeViewController.expandAll].
///
/// ```dart
/// setState((){
/// controller = controller.withExpandAll();
/// });
/// ```
TreeViewController withExpandAll({Node? parent}) {
List<Node> _data = expandAll(parent: parent);
return TreeViewController(
children: _data,
selectedKey: this.selectedKey,
);
}
/// Collapses all nodes down to parent Node.
/// It returns a new controller with the nodes collapsed.
/// This method expects the user to properly place this call so
/// that the state is updated.
///
/// Internally uses [TreeViewController.collapseAll].
///
/// ```dart
/// setState((){
/// controller = controller.withCollapseAll();
/// });
/// ```
TreeViewController withCollapseAll({Node? parent}) {
List<Node> _data = collapseAll(parent: parent);
return TreeViewController(
children: _data,
selectedKey: this.selectedKey,
);
}
/// Gets the node that has a key value equal to the specified key.
Node? getNode(String key, {Node? parent}) {
Node? _found;
List<Node> _children = parent == null ? this.children : parent.children;
Iterator iter = _children.iterator;
while (iter.moveNext()) {
Node child = iter.current;
if (child.key == key) {
_found = child;
break;
} else {
if (child.isParent) {
_found = this.getNode(key, parent: child);
if (_found != null) {
break;
}
}
}
}
return _found;
}
/// Expands all node that are children of the parent node parameter. If no parent is passed, uses the root node as the parent.
List<Node> expandAll({Node? parent}) {
List<Node> _children = [];
Iterator iter =
parent == null ? this.children.iterator : parent.children.iterator;
while (iter.moveNext()) {
Node child = iter.current;
if (child.isParent) {
_children.add(child.copyWith(
expanded: true,
children: this.expandAll(parent: child),
));
} else {
_children.add(child);
}
}
return _children;
}
/// Collapses all node that are children of the parent node parameter. If no parent is passed, uses the root node as the parent.
List<Node> collapseAll({Node? parent}) {
List<Node> _children = [];
Iterator iter =
parent == null ? this.children.iterator : parent.children.iterator;
while (iter.moveNext()) {
Node child = iter.current;
if (child.isParent) {
_children.add(child.copyWith(
expanded: false,
children: this.expandAll(parent: child),
));
} else {
_children.add(child);
}
}
return _children;
}
/// Gets the parent of the node identified by specified key.
Node? getParent(String key, {Node? parent}) {
Node? _found;
List<Node> _children = parent == null ? this.children : parent.children;
Iterator iter = _children.iterator;
while (iter.moveNext()) {
Node child = iter.current;
if (child.key == key) {
_found = parent ?? child;
break;
} else {
if (child.isParent) {
_found = this.getParent(key, parent: child);
if (_found != null) {
break;
}
}
}
}
return _found;
}
/// Expands a node and all of the node's ancestors so that the node is
/// visible without the need to manually expand each node.
List<Node> expandToNode(String key) {
List<String> _ancestors = [];
String _currentKey = key;
_ancestors.add(_currentKey);
Node? _parent = this.getParent(_currentKey);
if (_parent != null) {
while (_parent!.key != _currentKey) {
_currentKey = _parent.key;
_ancestors.add(_currentKey);
_parent = this.getParent(_currentKey);
}
TreeViewController _this = this;
_ancestors.forEach((String k) {
Node _node = _this.getNode(k)!;
Node _updated = _node.copyWith(expanded: true);
_this = _this.withUpdateNode(k, _updated);
});
return _this.children;
}
return this.children;
}
/// Collapses a node and all of the node's ancestors without the need to
/// manually collapse each node.
List<Node> collapseToNode(String key) {
List<String> _ancestors = [];
String _currentKey = key;
_ancestors.add(_currentKey);
Node? _parent = this.getParent(_currentKey);
if (_parent != null) {
while (_parent!.key != _currentKey) {
_currentKey = _parent.key;
_ancestors.add(_currentKey);
_parent = this.getParent(_currentKey);
}
TreeViewController _this = this;
_ancestors.forEach((String k) {
Node _node = _this.getNode(k)!;
Node _updated = _node.copyWith(expanded: false);
_this = _this.withUpdateNode(k, _updated);
});
return _this.children;
}
return this.children;
}
/// Adds a new node to an existing node identified by specified key. It optionally
/// accepts an [InsertMode] and index. If no [InsertMode] is specified,
/// it appends the new node as a child at the end. This method returns
/// a new list with the added node.
List<Node> addNode(
String key,
Node newNode, {
Node? parent,
int? index,
InsertMode mode: InsertMode.append,
}) {
List<Node> _children = parent == null ? this.children : parent.children;
return _children.map((Node child) {
if (child.key == key) {
List<Node> _children = child.children.toList(growable: true);
if (mode == InsertMode.prepend) {
_children.insert(0, newNode);
} else if (mode == InsertMode.insert) {
_children.insert(index ?? _children.length, newNode);
} else {
_children.add(newNode);
}
return child.copyWith(children: _children);
} else {
return child.copyWith(
children: addNode(
key,
newNode,
parent: child,
mode: mode,
index: index,
),
);
}
}).toList();
}
/// Updates an existing node identified by specified key. This method
/// returns a new list with the updated node.
List<Node> updateNode(String key, Node newNode, {Node? parent}) {
List<Node> _children = parent == null ? this.children : parent.children;
return _children.map((Node child) {
if (child.key == key) {
return newNode;
} else {
if (child.isParent) {
return child.copyWith(
children: updateNode(
key,
newNode,
parent: child,
),
);
}
return child;
}
}).toList();
}
/// Toggles an existing node identified by specified key. This method
/// returns a new list with the specified node toggled.
List<Node> toggleNode(String key, {Node? parent}) {
Node? _node = getNode(key, parent: parent);
return updateNode(key, _node!.copyWith(expanded: !_node.expanded));
}
/// Deletes an existing node identified by specified key. This method
/// returns a new list with the specified node removed.
List<Node> deleteNode(String key, {Node? parent}) {
List<Node> _children = parent == null ? this.children : parent.children;
List<Node> _filteredChildren = [];
Iterator iter = _children.iterator;
while (iter.moveNext()) {
Node child = iter.current;
if (child.key != key) {
if (child.isParent) {
_filteredChildren.add(child.copyWith(
children: deleteNode(key, parent: child),
));
} else {
_filteredChildren.add(child);
}
}
}
return _filteredChildren;
}
/// Get the current selected node. Returns null if there is no selectedKey
Node? get selectedNode {
return this.selectedKey!.isEmpty ? null : getNode(this.selectedKey!);
}
/// Map representation of this object
List<Map<String, dynamic>> get asMap {
return children.map((Node child) => child.asMap).toList();
}
@override
String toString() {
return jsonEncode(asMap);
}
}

View file

@ -0,0 +1,191 @@
import 'package:flutter/material.dart';
import 'expander_theme_data.dart';
import 'tree_node.dart';
const double _kDefaultLevelPadding = 20;
const int _kExpandSpeed = 130;
/// Defines the appearance of the [TreeView].
///
/// Used by [TreeView] to control the appearance of the sub-widgets
/// in the [TreeView] widget.
class TreeViewTheme {
/// The [ColorScheme] for [TreeView] widget.
final ColorScheme colorScheme;
/// The horizontal padding for the children of a [TreeNode] parent.
final double levelPadding;
/// Whether the [TreeNode] is vertically dense.
///
/// If this property is null then its value is based on [ListTileTheme.dense].
///
/// A dense [TreeNode] defaults to a smaller height.
final bool dense;
/// Vertical spacing between tabs.
/// If this property is null then [dense] attribute will work and vice versa.
final double? verticalSpacing;
/// Horizontal spacing between tabs.
/// If this property is null then horizontal spacing between tabs is default [_treeView.theme.iconTheme.size + 5]
final double? horizontalSpacing;
/// Horizontal padding for node icons.
final double iconPadding;
/// The default appearance theme for [TreeNode] icons.
final IconThemeData iconTheme;
/// The appearance theme for [TreeNode] expander icons.
final ExpanderThemeData expanderTheme;
/// The text style for child [TreeNode] text.
final TextStyle labelStyle;
/// The text style for parent [TreeNode] text.
final TextStyle parentLabelStyle;
/// The text overflow for child [TreeNode] text.
/// If this property is null then [softWrap] is true;
final TextOverflow? labelOverflow;
/// The text overflow for parent [TreeNode] text.
/// If this property is null then [softWrap] is true;
final TextOverflow? parentLabelOverflow;
/// the speed at which expander icon animates.
final Duration expandSpeed;
const TreeViewTheme({
this.colorScheme: const ColorScheme.light(),
this.iconTheme: const IconThemeData.fallback(),
this.expanderTheme: const ExpanderThemeData.fallback(),
this.labelStyle: const TextStyle(),
this.parentLabelStyle: const TextStyle(fontWeight: FontWeight.bold),
this.labelOverflow,
this.parentLabelOverflow,
this.levelPadding: _kDefaultLevelPadding,
this.dense: true,
this.verticalSpacing,
this.horizontalSpacing,
this.iconPadding: 8,
this.expandSpeed: const Duration(milliseconds: _kExpandSpeed),
});
/// Creates a [TreeView] theme with some reasonable default values.
///
/// The [colorScheme] is [ColorScheme.light],
/// the [iconTheme] is [IconThemeData.fallback],
/// the [expanderTheme] is [ExpanderThemeData.fallback],
/// the [labelStyle] is the default [TextStyle],
/// the [parentLabelStyle] is the default [TextStyle] with bold weight,
/// and the default [levelPadding] is 20.0.
const TreeViewTheme.fallback()
: colorScheme = const ColorScheme.light(),
iconTheme = const IconThemeData.fallback(),
expanderTheme = const ExpanderThemeData.fallback(),
labelStyle = const TextStyle(),
parentLabelStyle = const TextStyle(fontWeight: FontWeight.bold),
labelOverflow = null,
parentLabelOverflow = null,
dense = true,
verticalSpacing = null,
horizontalSpacing = null,
iconPadding = 8,
levelPadding = _kDefaultLevelPadding,
expandSpeed = const Duration(milliseconds: _kExpandSpeed);
/// Creates a copy of this theme but with the given fields replaced with
/// the new values.
TreeViewTheme copyWith({
ColorScheme? colorScheme,
IconThemeData? iconTheme,
ExpanderThemeData? expanderTheme,
TextStyle? labelStyle,
TextStyle? parentLabelStyle,
TextOverflow? labelOverflow,
TextOverflow? parentLabelOverflow,
bool? dense,
double? verticalSpacing,
double? horizontalSpacing,
double? iconPadding,
double? levelPadding,
}) {
return TreeViewTheme(
colorScheme: colorScheme ?? this.colorScheme,
levelPadding: levelPadding ?? this.levelPadding,
iconPadding: iconPadding ?? this.iconPadding,
iconTheme: iconTheme ?? this.iconTheme,
expanderTheme: expanderTheme ?? this.expanderTheme,
labelStyle: labelStyle ?? this.labelStyle,
dense: dense ?? this.dense,
verticalSpacing: verticalSpacing ?? this.verticalSpacing,
horizontalSpacing: horizontalSpacing ?? this.horizontalSpacing,
parentLabelStyle: parentLabelStyle ?? this.parentLabelStyle,
labelOverflow: labelOverflow ?? this.labelOverflow,
parentLabelOverflow: parentLabelOverflow ?? this.parentLabelOverflow);
}
/// Returns a new theme that matches this [TreeView] theme but with some values
/// replaced by the non-null parameters of the given icon theme. If the given
/// [TreeViewTheme] is null, simply returns this theme.
TreeViewTheme merge(TreeViewTheme other) {
if (other == null) return this;
return copyWith(
colorScheme: other.colorScheme,
levelPadding: other.levelPadding,
iconPadding: other.iconPadding,
iconTheme: other.iconTheme,
expanderTheme: other.expanderTheme,
labelStyle: other.labelStyle,
dense: other.dense,
verticalSpacing: other.verticalSpacing,
horizontalSpacing: other.horizontalSpacing,
parentLabelStyle: other.parentLabelStyle,
labelOverflow: other.labelOverflow,
parentLabelOverflow: other.parentLabelOverflow);
}
TreeViewTheme resolve(BuildContext context) => this;
Duration get quickExpandSpeed =>
Duration(milliseconds: (expandSpeed.inMilliseconds * 1.6).toInt());
@override
int get hashCode {
return hashValues(
colorScheme,
levelPadding,
iconPadding,
iconTheme,
expanderTheme,
labelStyle,
dense,
verticalSpacing,
horizontalSpacing,
parentLabelStyle,
labelOverflow,
parentLabelOverflow);
}
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
if (other.runtimeType != runtimeType) return false;
return other is TreeViewTheme &&
other.colorScheme == colorScheme &&
other.levelPadding == levelPadding &&
other.iconPadding == iconPadding &&
other.iconTheme == iconTheme &&
other.expanderTheme == expanderTheme &&
other.labelStyle == labelStyle &&
other.dense == dense &&
other.verticalSpacing == verticalSpacing &&
other.horizontalSpacing == horizontalSpacing &&
other.parentLabelStyle == parentLabelStyle &&
other.labelOverflow == labelOverflow &&
other.parentLabelOverflow == parentLabelOverflow;
}
}

681
lib/src/utilities.dart Normal file
View file

@ -0,0 +1,681 @@
import 'dart:convert';
import 'dart:math';
import 'dart:ui';
import 'package:flutter/material.dart';
class Utilities {
static final RegExp _hexExp = RegExp(
r'^#?([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$',
caseSensitive: false,
);
static final RegExp _rgbExp = RegExp(
r'^rgb\((0|255|25[0-4]|2[0-4]\d|1\d\d|0?\d?\d)\s?,\s?(0|255|25[0-4]|2[0-4]\d|1\d\d|0?\d?\d)\s?,\s?(0|255|25[0-4]|2[0-4]\d|1\d\d|0?\d?\d)\)$',
caseSensitive: false,
);
static final RegExp _rgbaExp = RegExp(
r'^rgba\((0|255|25[0-4]|2[0-4]\d|1\d\d|0?\d?\d)\s?,\s?(0|255|25[0-4]|2[0-4]\d|1\d\d|0?\d?\d)\s?,\s?(0|255|25[0-4]|2[0-4]\d|1\d\d|0?\d?\d)\s?,\s?(0|0?\.\d|1(\.0)?)\)$',
caseSensitive: false,
);
static final RegExp _materialDesignColorExp = RegExp(
r'^((?:red|pink|purple|deepPurple|indigo|blue|lightBlue|cyan|teal|green|lightGreen|lime|yellow|amber|orange|deepOrange|brown|grey|blueGrey)(?:50|100|200|300|400|500|600|700|800|900)?|(?:red|pink|purple|deepPurple|indigo|blue|lightBlue|cyan|teal|green|lightGreen|lime|yellow|amber|orange|deepOrange)(?:Accent|Accent50|Accent100|Accent200|Accent400|Accent700)?|(?:black|white))$',
caseSensitive: false,
);
static const Color BLACK = Color.fromARGB(255, 0, 0, 0);
static const Color WHITE = Color.fromARGB(255, 255, 255, 255);
static Color getColor(String value) {
String colorValue = value;
if (_hexExp.hasMatch(colorValue)) {
final buffer = StringBuffer();
if (colorValue.length == 3 || colorValue.length == 4) {
colorValue = colorValue.replaceFirst('#', '');
List<String> pieces =
colorValue.split('').map((String piece) => '$piece$piece').toList();
colorValue = pieces.join();
}
if (colorValue.length == 6 || colorValue.length == 7) buffer.write('ff');
buffer.write(colorValue.replaceFirst('#', ''));
return Color(int.parse(buffer.toString(), radix: 16));
} else if (_rgbExp.hasMatch(value)) {
var parts = _rgbExp.allMatches(value);
int r = 0;
int g = 0;
int b = 0;
for (var part in parts) {
r = int.parse(part.group(1)!);
g = int.parse(part.group(2)!);
b = int.parse(part.group(3)!);
}
return Color.fromARGB(255, r, g, b);
} else if (_rgbaExp.hasMatch(value)) {
var parts = _rgbaExp.allMatches(value);
int r = 0;
int g = 0;
int b = 0;
double a = 1;
for (var part in parts) {
r = int.parse(part.group(1)!);
g = int.parse(part.group(2)!);
b = int.parse(part.group(3)!);
a = double.parse(part.group(4)!);
}
return Color.fromARGB((255 * a).toInt(), r, g, b);
} else if (_materialDesignColorExp.hasMatch(value)) {
switch (value) {
case 'black':
return Colors.black;
case 'white':
return Colors.white;
case 'amber':
return Colors.amber;
case 'amber100':
return Colors.amber.shade100;
case 'amber200':
return Colors.amber.shade200;
case 'amber300':
return Colors.amber.shade300;
case 'amber400':
return Colors.amber.shade400;
case 'amber500':
return Colors.amber.shade500;
case 'amber600':
return Colors.amber.shade600;
case 'amber700':
return Colors.amber.shade700;
case 'amber800':
return Colors.amber.shade800;
case 'amber900':
return Colors.amber.shade900;
case 'amberAccent':
return Colors.amberAccent;
case 'amberAccent50':
return Colors.amberAccent.shade50;
case 'amberAccent100':
return Colors.amberAccent.shade100;
case 'amberAccent200':
return Colors.amberAccent.shade200;
case 'amberAccent400':
return Colors.amberAccent.shade400;
case 'amberAccent700':
return Colors.amberAccent.shade700;
case 'blue':
return Colors.blue;
case 'blue100':
return Colors.blue.shade100;
case 'blue200':
return Colors.blue.shade200;
case 'blue300':
return Colors.blue.shade300;
case 'blue400':
return Colors.blue.shade400;
case 'blue500':
return Colors.blue.shade500;
case 'blue600':
return Colors.blue.shade600;
case 'blue700':
return Colors.blue.shade700;
case 'blue800':
return Colors.blue.shade800;
case 'blue900':
return Colors.blue.shade900;
case 'blueAccent':
return Colors.blueAccent;
case 'blueAccent50':
return Colors.blueAccent.shade50;
case 'blueAccent100':
return Colors.blueAccent.shade100;
case 'blueAccent200':
return Colors.blueAccent.shade200;
case 'blueAccent400':
return Colors.blueAccent.shade400;
case 'blueAccent700':
return Colors.blueAccent.shade700;
case 'blueGrey':
return Colors.blueGrey;
case 'blueGrey100':
return Colors.blueGrey.shade100;
case 'blueGrey200':
return Colors.blueGrey.shade200;
case 'blueGrey300':
return Colors.blueGrey.shade300;
case 'blueGrey400':
return Colors.blueGrey.shade400;
case 'blueGrey500':
return Colors.blueGrey.shade500;
case 'blueGrey600':
return Colors.blueGrey.shade600;
case 'blueGrey700':
return Colors.blueGrey.shade700;
case 'blueGrey800':
return Colors.blueGrey.shade800;
case 'blueGrey900':
return Colors.blueGrey.shade900;
case 'brown':
return Colors.brown;
case 'brown100':
return Colors.brown.shade100;
case 'brown200':
return Colors.brown.shade200;
case 'brown300':
return Colors.brown.shade300;
case 'brown400':
return Colors.brown.shade400;
case 'brown500':
return Colors.brown.shade500;
case 'brown600':
return Colors.brown.shade600;
case 'brown700':
return Colors.brown.shade700;
case 'brown800':
return Colors.brown.shade800;
case 'brown900':
return Colors.brown.shade900;
case 'cyan':
return Colors.cyan;
case 'cyan100':
return Colors.cyan.shade100;
case 'cyan200':
return Colors.cyan.shade200;
case 'cyan300':
return Colors.cyan.shade300;
case 'cyan400':
return Colors.cyan.shade400;
case 'cyan500':
return Colors.cyan.shade500;
case 'cyan600':
return Colors.cyan.shade600;
case 'cyan700':
return Colors.cyan.shade700;
case 'cyan800':
return Colors.cyan.shade800;
case 'cyan900':
return Colors.cyan.shade900;
case 'cyanAccent':
return Colors.cyanAccent;
case 'cyanAccent50':
return Colors.cyanAccent.shade50;
case 'cyanAccent100':
return Colors.cyanAccent.shade100;
case 'cyanAccent200':
return Colors.cyanAccent.shade200;
case 'cyanAccent400':
return Colors.cyanAccent.shade400;
case 'cyanAccent700':
return Colors.cyanAccent.shade700;
case 'deepOrange':
return Colors.deepOrange;
case 'deepOrange100':
return Colors.deepOrange.shade100;
case 'deepOrange200':
return Colors.deepOrange.shade200;
case 'deepOrange300':
return Colors.deepOrange.shade300;
case 'deepOrange400':
return Colors.deepOrange.shade400;
case 'deepOrange500':
return Colors.deepOrange.shade500;
case 'deepOrange600':
return Colors.deepOrange.shade600;
case 'deepOrange700':
return Colors.deepOrange.shade700;
case 'deepOrange800':
return Colors.deepOrange.shade800;
case 'deepOrange900':
return Colors.deepOrange.shade900;
case 'deepOrangeAccent':
return Colors.deepOrangeAccent;
case 'deepOrangeAccent50':
return Colors.deepOrangeAccent.shade50;
case 'deepOrangeAccent100':
return Colors.deepOrangeAccent.shade100;
case 'deepOrangeAccent200':
return Colors.deepOrangeAccent.shade200;
case 'deepOrangeAccent400':
return Colors.deepOrangeAccent.shade400;
case 'deepOrangeAccent700':
return Colors.deepOrangeAccent.shade700;
case 'deepPurple':
return Colors.deepPurple;
case 'deepPurple100':
return Colors.deepPurple.shade100;
case 'deepPurple200':
return Colors.deepPurple.shade200;
case 'deepPurple300':
return Colors.deepPurple.shade300;
case 'deepPurple400':
return Colors.deepPurple.shade400;
case 'deepPurple500':
return Colors.deepPurple.shade500;
case 'deepPurple600':
return Colors.deepPurple.shade600;
case 'deepPurple700':
return Colors.deepPurple.shade700;
case 'deepPurple800':
return Colors.deepPurple.shade800;
case 'deepPurple900':
return Colors.deepPurple.shade900;
case 'deepPurpleAccent':
return Colors.deepPurpleAccent;
case 'deepPurpleAccent50':
return Colors.deepPurpleAccent.shade50;
case 'deepPurpleAccent100':
return Colors.deepPurpleAccent.shade100;
case 'deepPurpleAccent200':
return Colors.deepPurpleAccent.shade200;
case 'deepPurpleAccent400':
return Colors.deepPurpleAccent.shade400;
case 'deepPurpleAccent700':
return Colors.deepPurpleAccent.shade700;
case 'green':
return Colors.green;
case 'green100':
return Colors.green.shade100;
case 'green200':
return Colors.green.shade200;
case 'green300':
return Colors.green.shade300;
case 'green400':
return Colors.green.shade400;
case 'green500':
return Colors.green.shade500;
case 'green600':
return Colors.green.shade600;
case 'green700':
return Colors.green.shade700;
case 'green800':
return Colors.green.shade800;
case 'green900':
return Colors.green.shade900;
case 'greenAccent':
return Colors.greenAccent;
case 'greenAccent50':
return Colors.greenAccent.shade50;
case 'greenAccent100':
return Colors.greenAccent.shade100;
case 'greenAccent200':
return Colors.greenAccent.shade200;
case 'greenAccent400':
return Colors.greenAccent.shade400;
case 'greenAccent700':
return Colors.greenAccent.shade700;
case 'grey':
return Colors.grey;
case 'grey100':
return Colors.grey.shade100;
case 'grey200':
return Colors.grey.shade200;
case 'grey300':
return Colors.grey.shade300;
case 'grey400':
return Colors.grey.shade400;
case 'grey500':
return Colors.grey.shade500;
case 'grey600':
return Colors.grey.shade600;
case 'grey700':
return Colors.grey.shade700;
case 'grey800':
return Colors.grey.shade800;
case 'grey900':
return Colors.grey.shade900;
case 'indigo':
return Colors.indigo;
case 'indigo100':
return Colors.indigo.shade100;
case 'indigo200':
return Colors.indigo.shade200;
case 'indigo300':
return Colors.indigo.shade300;
case 'indigo400':
return Colors.indigo.shade400;
case 'indigo500':
return Colors.indigo.shade500;
case 'indigo600':
return Colors.indigo.shade600;
case 'indigo700':
return Colors.indigo.shade700;
case 'indigo800':
return Colors.indigo.shade800;
case 'indigo900':
return Colors.indigo.shade900;
case 'indigoAccent':
return Colors.indigoAccent;
case 'indigoAccent50':
return Colors.indigoAccent.shade50;
case 'indigoAccent100':
return Colors.indigoAccent.shade100;
case 'indigoAccent200':
return Colors.indigoAccent.shade200;
case 'indigoAccent400':
return Colors.indigoAccent.shade400;
case 'indigoAccent700':
return Colors.indigoAccent.shade700;
case 'lightBlue':
return Colors.lightBlue;
case 'lightBlue100':
return Colors.lightBlue.shade100;
case 'lightBlue200':
return Colors.lightBlue.shade200;
case 'lightBlue300':
return Colors.lightBlue.shade300;
case 'lightBlue400':
return Colors.lightBlue.shade400;
case 'lightBlue500':
return Colors.lightBlue.shade500;
case 'lightBlue600':
return Colors.lightBlue.shade600;
case 'lightBlue700':
return Colors.lightBlue.shade700;
case 'lightBlue800':
return Colors.lightBlue.shade800;
case 'lightBlue900':
return Colors.lightBlue.shade900;
case 'lightBlueAccent':
return Colors.lightBlueAccent;
case 'lightBlueAccent50':
return Colors.lightBlueAccent.shade50;
case 'lightBlueAccent100':
return Colors.lightBlueAccent.shade100;
case 'lightBlueAccent200':
return Colors.lightBlueAccent.shade200;
case 'lightBlueAccent400':
return Colors.lightBlueAccent.shade400;
case 'lightBlueAccent700':
return Colors.lightBlueAccent.shade700;
case 'lightGreen':
return Colors.lightGreen;
case 'lightGreen100':
return Colors.lightGreen.shade100;
case 'lightGreen200':
return Colors.lightGreen.shade200;
case 'lightGreen300':
return Colors.lightGreen.shade300;
case 'lightGreen400':
return Colors.lightGreen.shade400;
case 'lightGreen500':
return Colors.lightGreen.shade500;
case 'lightGreen600':
return Colors.lightGreen.shade600;
case 'lightGreen700':
return Colors.lightGreen.shade700;
case 'lightGreen800':
return Colors.lightGreen.shade800;
case 'lightGreen900':
return Colors.lightGreen.shade900;
case 'lightGreenAccent':
return Colors.lightGreenAccent;
case 'lightGreenAccent50':
return Colors.lightGreenAccent.shade50;
case 'lightGreenAccent100':
return Colors.lightGreenAccent.shade100;
case 'lightGreenAccent200':
return Colors.lightGreenAccent.shade200;
case 'lightGreenAccent400':
return Colors.lightGreenAccent.shade400;
case 'lightGreenAccent700':
return Colors.lightGreenAccent.shade700;
case 'lime':
return Colors.lime;
case 'lime100':
return Colors.lime.shade100;
case 'lime200':
return Colors.lime.shade200;
case 'lime300':
return Colors.lime.shade300;
case 'lime400':
return Colors.lime.shade400;
case 'lime500':
return Colors.lime.shade500;
case 'lime600':
return Colors.lime.shade600;
case 'lime700':
return Colors.lime.shade700;
case 'lime800':
return Colors.lime.shade800;
case 'lime900':
return Colors.lime.shade900;
case 'limeAccent':
return Colors.limeAccent;
case 'limeAccent50':
return Colors.limeAccent.shade50;
case 'limeAccent100':
return Colors.limeAccent.shade100;
case 'limeAccent200':
return Colors.limeAccent.shade200;
case 'limeAccent400':
return Colors.limeAccent.shade400;
case 'limeAccent700':
return Colors.limeAccent.shade700;
case 'orange':
return Colors.orange;
case 'orange100':
return Colors.orange.shade100;
case 'orange200':
return Colors.orange.shade200;
case 'orange300':
return Colors.orange.shade300;
case 'orange400':
return Colors.orange.shade400;
case 'orange500':
return Colors.orange.shade500;
case 'orange600':
return Colors.orange.shade600;
case 'orange700':
return Colors.orange.shade700;
case 'orange800':
return Colors.orange.shade800;
case 'orange900':
return Colors.orange.shade900;
case 'orangeAccent':
return Colors.orangeAccent;
case 'orangeAccent50':
return Colors.orangeAccent.shade50;
case 'orangeAccent100':
return Colors.orangeAccent.shade100;
case 'orangeAccent200':
return Colors.orangeAccent.shade200;
case 'orangeAccent400':
return Colors.orangeAccent.shade400;
case 'orangeAccent700':
return Colors.orangeAccent.shade700;
case 'pink':
return Colors.pink;
case 'pink100':
return Colors.pink.shade100;
case 'pink200':
return Colors.pink.shade200;
case 'pink300':
return Colors.pink.shade300;
case 'pink400':
return Colors.pink.shade400;
case 'pink500':
return Colors.pink.shade500;
case 'pink600':
return Colors.pink.shade600;
case 'pink700':
return Colors.pink.shade700;
case 'pink800':
return Colors.pink.shade800;
case 'pink900':
return Colors.pink.shade900;
case 'pinkAccent':
return Colors.pinkAccent;
case 'pinkAccent50':
return Colors.pinkAccent.shade50;
case 'pinkAccent100':
return Colors.pinkAccent.shade100;
case 'pinkAccent200':
return Colors.pinkAccent.shade200;
case 'pinkAccent400':
return Colors.pinkAccent.shade400;
case 'pinkAccent700':
return Colors.pinkAccent.shade700;
case 'purple':
return Colors.purple;
case 'purple100':
return Colors.purple.shade100;
case 'purple200':
return Colors.purple.shade200;
case 'purple300':
return Colors.purple.shade300;
case 'purple400':
return Colors.purple.shade400;
case 'purple500':
return Colors.purple.shade500;
case 'purple600':
return Colors.purple.shade600;
case 'purple700':
return Colors.purple.shade700;
case 'purple800':
return Colors.purple.shade800;
case 'purple900':
return Colors.purple.shade900;
case 'purpleAccent':
return Colors.purpleAccent;
case 'purpleAccent50':
return Colors.purpleAccent.shade50;
case 'purpleAccent100':
return Colors.purpleAccent.shade100;
case 'purpleAccent200':
return Colors.purpleAccent.shade200;
case 'purpleAccent400':
return Colors.purpleAccent.shade400;
case 'purpleAccent700':
return Colors.purpleAccent.shade700;
case 'red':
return Colors.red;
case 'red100':
return Colors.red.shade100;
case 'red200':
return Colors.red.shade200;
case 'red300':
return Colors.red.shade300;
case 'red400':
return Colors.red.shade400;
case 'red500':
return Colors.red.shade500;
case 'red600':
return Colors.red.shade600;
case 'red700':
return Colors.red.shade700;
case 'red800':
return Colors.red.shade800;
case 'red900':
return Colors.red.shade900;
case 'redAccent':
return Colors.redAccent;
case 'redAccent50':
return Colors.redAccent.shade50;
case 'redAccent100':
return Colors.redAccent.shade100;
case 'redAccent200':
return Colors.redAccent.shade200;
case 'redAccent400':
return Colors.redAccent.shade400;
case 'redAccent700':
return Colors.redAccent.shade700;
case 'teal':
return Colors.teal;
case 'teal100':
return Colors.teal.shade100;
case 'teal200':
return Colors.teal.shade200;
case 'teal300':
return Colors.teal.shade300;
case 'teal400':
return Colors.teal.shade400;
case 'teal500':
return Colors.teal.shade500;
case 'teal600':
return Colors.teal.shade600;
case 'teal700':
return Colors.teal.shade700;
case 'teal800':
return Colors.teal.shade800;
case 'teal900':
return Colors.teal.shade900;
case 'tealAccent':
return Colors.tealAccent;
case 'tealAccent50':
return Colors.tealAccent.shade50;
case 'tealAccent100':
return Colors.tealAccent.shade100;
case 'tealAccent200':
return Colors.tealAccent.shade200;
case 'tealAccent400':
return Colors.tealAccent.shade400;
case 'tealAccent700':
return Colors.tealAccent.shade700;
case 'yellow':
return Colors.yellow;
case 'yellow100':
return Colors.yellow.shade100;
case 'yellow200':
return Colors.yellow.shade200;
case 'yellow300':
return Colors.yellow.shade300;
case 'yellow400':
return Colors.yellow.shade400;
case 'yellow500':
return Colors.yellow.shade500;
case 'yellow600':
return Colors.yellow.shade600;
case 'yellow700':
return Colors.yellow.shade700;
case 'yellow800':
return Colors.yellow.shade800;
case 'yellow900':
return Colors.yellow.shade900;
case 'yellowAccent':
return Colors.yellowAccent;
case 'yellowAccent50':
return Colors.yellowAccent.shade50;
case 'yellowAccent100':
return Colors.yellowAccent.shade100;
case 'yellowAccent200':
return Colors.yellowAccent.shade200;
case 'yellowAccent400':
return Colors.yellowAccent.shade400;
case 'yellowAccent700':
return Colors.yellowAccent.shade700;
}
} else {
return Color.fromARGB(255, 0, 0, 0);
}
return Color.fromARGB(255, 0, 0, 0);
}
static String toRGBA(Color color) {
return 'rgba(${color.red},${color.green},${color.blue},${color.alpha / 255})';
}
static Color textColor(Color color) {
if (color.computeLuminance() > 0.6) {
return BLACK;
} else {
return WHITE;
}
}
static String generateRandom([int length = 16]) {
final Random _random = Random.secure();
var values = List<int>.generate(length, (i) => _random.nextInt(256));
return base64Url.encode(values).substring(0, length);
}
static bool truthful(value) {
if (value == null) {
return false;
}
if (value == true ||
value == 'true' ||
value == 1 ||
value == '1' ||
value.toString().toLowerCase() == 'yes') {
return true;
}
return false;
}
}