adding packages
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class DataSource<T> extends DataTableSource {
|
||||
DataSource(this._items);
|
||||
final List<T> _items;
|
||||
|
||||
@override
|
||||
int get selectedRowCount => selected.length;
|
||||
final Set<int> selected = {};
|
||||
|
||||
String _search = '';
|
||||
String get search => this._search;
|
||||
void onSearch(String val) {
|
||||
_search = val;
|
||||
this.notifyListeners();
|
||||
}
|
||||
|
||||
@override
|
||||
DataRow getRow(int index) {
|
||||
final item = items[index];
|
||||
List<DataCell> cells = <DataCell>[];
|
||||
if (item is Map) {
|
||||
cells = item.values.map((e) => DataCell(Text(e.toString()))).toList();
|
||||
} else {
|
||||
cells = [DataCell(Text(item.toString()))];
|
||||
}
|
||||
return DataRow(
|
||||
selected: selected.contains(index),
|
||||
cells: cells,
|
||||
onSelectChanged: (val) {
|
||||
if (val) {
|
||||
selected.add(index);
|
||||
} else {
|
||||
selected.remove(index);
|
||||
}
|
||||
this.notifyListeners();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void clearSelection([int skipIndex]) {
|
||||
final _past = [...selected];
|
||||
for (final idx in _past) {
|
||||
if (idx == skipIndex) continue;
|
||||
this.getRow(idx).onSelectChanged(false);
|
||||
this.selected.remove(idx);
|
||||
}
|
||||
this.notifyListeners();
|
||||
}
|
||||
|
||||
@override
|
||||
bool get isRowCountApproximate => false;
|
||||
|
||||
@override
|
||||
int get rowCount => items.length;
|
||||
|
||||
List<T> get items => this._items ?? [];
|
||||
}
|
||||
@@ -0,0 +1,289 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'source.dart';
|
||||
|
||||
class TaggedDataView<T> extends StatefulWidget {
|
||||
const TaggedDataView({
|
||||
Key key,
|
||||
@required this.dataSource,
|
||||
this.tagViewWidth = 220,
|
||||
this.listViewWidth = 280,
|
||||
this.emptyBuilder,
|
||||
this.detailBuilder,
|
||||
this.actions,
|
||||
}) : super(key: key);
|
||||
|
||||
final TaggedDataTableSource<T> dataSource;
|
||||
final double listViewWidth, tagViewWidth;
|
||||
final Widget Function(BuildContext context) emptyBuilder;
|
||||
final Widget Function(BuildContext context, T item, int index) detailBuilder;
|
||||
final List<Widget> actions;
|
||||
|
||||
@override
|
||||
_TaggedDataViewState<T> createState() => _TaggedDataViewState<T>();
|
||||
}
|
||||
|
||||
class _TaggedDataViewState<T> extends State<TaggedDataView<T>> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
widget.dataSource.addListener(_handleSourceChange);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
widget.dataSource.removeListener(_handleSourceChange);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _handleSourceChange() {
|
||||
if (mounted) setState(() {});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final source = widget.dataSource;
|
||||
final tags = widget.dataSource.allTags;
|
||||
final folders = tags.where((e) => e.contains('/')).toList();
|
||||
final other = tags
|
||||
.where((e) => !folders.map((e) => e.split('/').first).contains(e))
|
||||
.toList();
|
||||
final allTags = [...folders, ...other].toSet().toList();
|
||||
allTags.sort();
|
||||
final emptyBuilder = () {
|
||||
if (widget?.emptyBuilder != null) {
|
||||
return widget.emptyBuilder(context);
|
||||
}
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
centerTitle: false,
|
||||
title: Text('Details'),
|
||||
),
|
||||
body: Center(
|
||||
child: Text('No Item Selected'),
|
||||
),
|
||||
);
|
||||
};
|
||||
final detailBuilder = (int index) {
|
||||
final T item = widget.dataSource.items[index];
|
||||
if (widget?.detailBuilder != null) {
|
||||
return widget.detailBuilder(context, item, index);
|
||||
}
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
centerTitle: false,
|
||||
title: Text('Details'),
|
||||
),
|
||||
body: Center(
|
||||
child: Text(item.toString()),
|
||||
),
|
||||
);
|
||||
};
|
||||
final tagsBuilder = (Function(BuildContext context) onTap) {
|
||||
return Scrollbar(
|
||||
child: ListView(
|
||||
children: [
|
||||
ListTile(
|
||||
selected: widget.dataSource.selectedTag == null,
|
||||
onTap: () {
|
||||
widget.dataSource.selectTag(null);
|
||||
onTap(context);
|
||||
},
|
||||
leading: Icon(Icons.list),
|
||||
title: Text('all'),
|
||||
),
|
||||
for (var i = 0; i < allTags.length; i++)
|
||||
buildTag(allTags[i], folders.contains(allTags[i]), onTap),
|
||||
],
|
||||
),
|
||||
);
|
||||
};
|
||||
final listBuilder = (Function(int index) onTap) {
|
||||
return Column(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 8,
|
||||
vertical: 4,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextField(
|
||||
decoration: InputDecoration(
|
||||
border: InputBorder.none,
|
||||
hintText: 'Search',
|
||||
prefixIcon: Icon(Icons.search),
|
||||
),
|
||||
onChanged: (val) {
|
||||
widget.dataSource.onSearch(val);
|
||||
if (mounted) setState(() {});
|
||||
},
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Scrollbar(
|
||||
child: ListView.builder(
|
||||
itemCount: source.rowsForTag.keys.length,
|
||||
itemBuilder: (context, index) {
|
||||
final key = source.rowsForTag.keys.toList()[index];
|
||||
final row = source.rowsForTag[key];
|
||||
return ListTile(
|
||||
title: row.cells[0].child,
|
||||
subtitle: row.cells[1].child,
|
||||
selected: row.selected,
|
||||
onTap: () => onTap(key),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
};
|
||||
return LayoutBuilder(
|
||||
builder: (context, dimens) {
|
||||
if (dimens.maxWidth >= 720) {
|
||||
return Row(
|
||||
children: [
|
||||
Container(
|
||||
width: widget.tagViewWidth + widget.listViewWidth,
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
centerTitle: false,
|
||||
title: Text(widget.dataSource.selectedTag ?? 'all'),
|
||||
actions: widget.actions ?? [],
|
||||
),
|
||||
body: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: widget.tagViewWidth,
|
||||
child: tagsBuilder((context) => {}),
|
||||
),
|
||||
Container(
|
||||
width: widget.listViewWidth,
|
||||
child: listBuilder((index) {
|
||||
final row = widget.dataSource.getRow(index);
|
||||
row.onSelectChanged(!row.selected);
|
||||
widget.dataSource.clearSelection(index);
|
||||
}),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
VerticalDivider(width: 0),
|
||||
Expanded(
|
||||
child: widget.dataSource.selectedRowCount == 0
|
||||
? emptyBuilder()
|
||||
: detailBuilder(widget.dataSource.selected.last),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
centerTitle: false,
|
||||
title: Text(widget.dataSource.selectedTag ?? 'all'),
|
||||
actions: widget.actions ?? [],
|
||||
),
|
||||
drawer: Drawer(
|
||||
child: tagsBuilder((context) {
|
||||
Navigator.of(context).maybePop();
|
||||
}),
|
||||
),
|
||||
body: listBuilder((index) {
|
||||
Navigator.of(context).push(MaterialPageRoute(
|
||||
builder: (_) => detailBuilder(index),
|
||||
));
|
||||
}),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildTag(
|
||||
String tag,
|
||||
bool folder,
|
||||
Function(BuildContext context) onTap,
|
||||
) {
|
||||
if (folder) {
|
||||
return TagFolder<T>(
|
||||
dataSource: widget.dataSource,
|
||||
tag: tag,
|
||||
);
|
||||
}
|
||||
return Builder(
|
||||
builder: (context) {
|
||||
return ListTile(
|
||||
selected: widget.dataSource.selectedTag == tag,
|
||||
onTap: () {
|
||||
widget.dataSource.selectTag(tag);
|
||||
onTap(context);
|
||||
},
|
||||
leading: widget.dataSource.getIconForTag(tag),
|
||||
title: Text(tag),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class TagFolder<T> extends StatefulWidget {
|
||||
const TagFolder({
|
||||
Key key,
|
||||
@required this.tag,
|
||||
@required this.dataSource,
|
||||
}) : super(key: key);
|
||||
final String tag;
|
||||
final TaggedDataTableSource<T> dataSource;
|
||||
|
||||
@override
|
||||
_TagFolderState createState() => _TagFolderState();
|
||||
}
|
||||
|
||||
class _TagFolderState extends State<TagFolder> {
|
||||
bool _expanded = false;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final name = widget.tag.split('/').first.trim();
|
||||
return Column(
|
||||
children: [
|
||||
ListTile(
|
||||
selected: widget.dataSource.selectedTag == name,
|
||||
onTap: () => widget.dataSource.selectTag(name),
|
||||
leading: widget.dataSource.getIconForTag(name),
|
||||
title: Text(name),
|
||||
trailing: IconButton(
|
||||
icon: Icon(_expanded
|
||||
? Icons.keyboard_arrow_up
|
||||
: Icons.keyboard_arrow_down),
|
||||
onPressed: () {
|
||||
if (mounted)
|
||||
setState(() {
|
||||
_expanded = !_expanded;
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
if (_expanded)
|
||||
...widget.dataSource.allTags
|
||||
.where((e) => e.contains('$name/'))
|
||||
.map((e) => Container(
|
||||
margin: const EdgeInsets.only(left: 20),
|
||||
child: ListTile(
|
||||
selected: widget.dataSource.selectedTag == widget.tag,
|
||||
onTap: () => widget.dataSource.selectTag(widget.tag),
|
||||
leading:
|
||||
widget.dataSource.getIconForTag(e.split('/').last),
|
||||
title: Text(e.split('/').last),
|
||||
),
|
||||
))
|
||||
.toList()
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../data_source.dart';
|
||||
|
||||
abstract class TaggedDataTableSource<T> extends DataSource<T> {
|
||||
TaggedDataTableSource(List<T> items) : super(items);
|
||||
|
||||
Icon getIconForTag(String tag) {
|
||||
final iconData = () {
|
||||
switch (tag) {
|
||||
case 'settings':
|
||||
case 'system':
|
||||
return Icons.settings;
|
||||
case 'info':
|
||||
case 'about':
|
||||
case 'details':
|
||||
return Icons.info_outline;
|
||||
case 'bookmark':
|
||||
case 'bookmarks':
|
||||
return Icons.bookmark;
|
||||
case 'account':
|
||||
return Icons.person;
|
||||
case 'dev':
|
||||
case 'code':
|
||||
return Icons.code;
|
||||
case 'favorite':
|
||||
case 'favorites':
|
||||
return Icons.favorite;
|
||||
default:
|
||||
}
|
||||
return Icons.info;
|
||||
};
|
||||
if (iconData() == null) return null;
|
||||
return Icon(iconData());
|
||||
}
|
||||
|
||||
List<String> getTagsForRow(int index) {
|
||||
final item = this.items[index];
|
||||
if (item is Map && item.containsKey('tags')) {
|
||||
return item['tags'];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
String _selected;
|
||||
String get selectedTag => _selected;
|
||||
void selectTag(String tag) {
|
||||
_selected = tag;
|
||||
this.notifyListeners();
|
||||
}
|
||||
|
||||
List<String> get allTags {
|
||||
final tags = <String>{};
|
||||
for (var i = 0; i < rowCount; i++) {
|
||||
final _tags = getTagsForRow(i);
|
||||
tags.addAll(_tags);
|
||||
}
|
||||
final _tags = tags.toList();
|
||||
_tags.sort();
|
||||
return _tags;
|
||||
}
|
||||
|
||||
Map<int, DataRow> get rowsForTag {
|
||||
final _results = <int, DataRow>{};
|
||||
for (var i = 0; i < rowCount; i++) {
|
||||
final tags = getTagsForRow(i);
|
||||
if (_selected == null) {
|
||||
_results[i] = getRow(i);
|
||||
} else {
|
||||
for (final tag in tags) {
|
||||
if (tag.contains(_selected)) {
|
||||
_results[i] = getRow(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
final search = this.search.toLowerCase();
|
||||
if (search.isEmpty) return _results;
|
||||
_results.clear();
|
||||
for (var i = 0; i < rowCount; i++) {
|
||||
final item = this.items[i];
|
||||
if (item.toString().toLowerCase().contains(search)) {
|
||||
_results[i] = getRow(i);
|
||||
}
|
||||
}
|
||||
return _results;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user