adding packages
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
# Auto detect text files and perform LF normalization
|
||||
* text=auto
|
||||
@@ -0,0 +1,28 @@
|
||||
name: github pages
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-18.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Setup Flutter
|
||||
uses: subosito/flutter-action@v1
|
||||
with:
|
||||
channel: 'stable'
|
||||
|
||||
- name: Install
|
||||
run: flutter pub get
|
||||
- name: Build
|
||||
run: cd example && flutter build web
|
||||
|
||||
- name: Deploy
|
||||
uses: peaceiris/actions-gh-pages@v3
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
publish_dir: ./example/build/web
|
||||
@@ -0,0 +1,7 @@
|
||||
.DS_Store
|
||||
.dart_tool/
|
||||
|
||||
.packages
|
||||
.pub/
|
||||
|
||||
build/
|
||||
@@ -0,0 +1,10 @@
|
||||
# This file tracks properties of this Flutter project.
|
||||
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
||||
#
|
||||
# This file should be version controlled and should not be manually edited.
|
||||
|
||||
version:
|
||||
revision: 746d5f7676e116041dd125d98ffbeeaf9251b90d
|
||||
channel: master
|
||||
|
||||
project_type: plugin
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Flutter",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"program": "example/lib/main.dart"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"cSpell.words": [
|
||||
"datatable",
|
||||
"listview"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
## 1.4.0
|
||||
|
||||
* fixed deprecated theme properties
|
||||
|
||||
## 1.3.2
|
||||
|
||||
* fixing hide select box
|
||||
* recreating example target folders
|
||||
|
||||
## 1.3.1
|
||||
|
||||
* support for no items
|
||||
* adding totalItems
|
||||
|
||||
## 1.3.0
|
||||
|
||||
* adding `showSelect'
|
||||
* adding `showSort'
|
||||
|
||||
## 1.2.0
|
||||
|
||||
* Stable Version
|
||||
* Added '.fromJson'
|
||||
* Updated Examples
|
||||
|
||||
## 1.1.1-nullsafety
|
||||
|
||||
* Updating to null safety
|
||||
|
||||
## 1.1.0-nullsafety
|
||||
|
||||
* Updating to null safety
|
||||
|
||||
## 1.0.1 - 04.06.2019
|
||||
|
||||
* Fixing Bugs on Mobile
|
||||
* Added `alwaysShowDataTable`
|
||||
|
||||
## 1.0.0 - 04.26.2019
|
||||
|
||||
* Adding Desktop to Example
|
||||
* Requiring `Dart 2.2.2`
|
||||
* Fixing Sort Fields
|
||||
* Fixing Bottom Bar
|
||||
* Fixed Mobile Item Builder
|
||||
|
||||
## 0.3.0 - 04.06.2019
|
||||
|
||||
* Making Example Desktop Aware
|
||||
|
||||
## 0.2.4
|
||||
|
||||
* Fixed Padding on Refresh
|
||||
* Added CupertinoRefreshController
|
||||
|
||||
## 0.2.3
|
||||
|
||||
* Removing SafeArea
|
||||
|
||||
## 0.2.2
|
||||
|
||||
* Adding Mobile Slivers
|
||||
* Updated Example
|
||||
|
||||
## 0.2.1
|
||||
|
||||
* Updating Toolbar on Mobile
|
||||
|
||||
## 0.2.0
|
||||
|
||||
* Updating Size Attribute to Detect if Tablet
|
||||
|
||||
## 0.1.3
|
||||
|
||||
* Adding Size Attribute to Detect if Tablet
|
||||
|
||||
## 0.1.2
|
||||
|
||||
* Fixing Mobile onSort Behavior
|
||||
* Fixing Bottom Bar Sizing
|
||||
|
||||
## 0.1.1
|
||||
|
||||
* Adding Support for Widgets when No Items Loded and Empty Items
|
||||
|
||||
## 0.1.0
|
||||
|
||||
* Updated Example
|
||||
* Made Tableview Stateless
|
||||
* Removed Need for Data Source
|
||||
|
||||
## 0.0.2
|
||||
|
||||
* Updated Example
|
||||
* Made Tableview Stateless
|
||||
* Removed Need for Data Source
|
||||
|
||||
|
||||
## 0.0.1
|
||||
|
||||
* Created Data Table
|
||||
* Created Mobile ListView
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020 Rody Davis
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,55 @@
|
||||
[](https://www.buymeacoffee.com/rodydavis)
|
||||
[](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=WSH3GVC49GNNJ)
|
||||

|
||||
[](https://github.com/rodydavis/data_tables)
|
||||
[](https://pub.dev/packages/data_tables)
|
||||
|
||||
# data_tables
|
||||
|
||||
- Full Screen Paginated Data Tables for Tablets/Desktops
|
||||
- Mobile ListView with Action Buttons for Sorting and Selecting All
|
||||
- Supports Dark Mode
|
||||
- From Json
|
||||
|
||||
Online Demo: https://rodydavis.github.io/data_tables/
|
||||
|
||||
## Getting Started
|
||||
|
||||
- You can optionally build the listview for mobile with a builder, by default it creates a ExpansionTile with the remaining columns as children
|
||||
- The tablet breakpoint can also be set.
|
||||
|
||||
`bool showMobileListView;` - When set to false it will always show a data table
|
||||
|
||||
`int sortColumnIndex;` - Current Sorted Column
|
||||
|
||||
`bool sortAscending;` - Sort Order
|
||||
|
||||
`ValueChanged<bool> onSelectAll;` - Called for Selecting and Deselecting All
|
||||
|
||||
`ValueChanged<int> onRowsPerPageChanged;` - Called when rows change on data table or last row reached on mobile.
|
||||
|
||||
`int rowsPerPage;` - Default Rows per page
|
||||
|
||||
`Widget header;` - Widget header for Desktop and Tablet Data Table
|
||||
|
||||
`List<DataColumn> columns;` - List of Columns (Must match length of DataCells in DataSource)
|
||||
|
||||
`IndexedWidgetBuilder mobileItemBuilder;` - Optional Item builder for the list view for Mobile
|
||||
|
||||
`Size tabletBreakpoint;` - Tablet breakpoint for the screen width and height
|
||||
|
||||
`List<Widget> actions, selectedActions;` - Actions that show when items are selected or not
|
||||
|
||||
`RefreshCallback onRefresh;` - If not null the list view will be wrapped in a RefreshIndicator
|
||||
|
||||
## Screenshots
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
@@ -0,0 +1,6 @@
|
||||
include: package:pedantic/analysis_options.yaml
|
||||
|
||||
analyzer:
|
||||
exclude:
|
||||
- lib/**
|
||||
- pubspec.lock
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
#!/bin/sh
|
||||
# This is a generated file; do not edit or check into version control.
|
||||
export "FLUTTER_ROOT=/usr/local/Caskroom/flutter/1.2.1/flutter"
|
||||
export "FLUTTER_APPLICATION_PATH=/Users/rodydavis/Developer/GitHub/plugins/packages/data_tables/example"
|
||||
export "COCOAPODS_PARALLEL_CODE_SIGN=true"
|
||||
export "FLUTTER_TARGET=lib/main.dart"
|
||||
export "FLUTTER_BUILD_DIR=build"
|
||||
export "FLUTTER_BUILD_NAME=1.0.0"
|
||||
export "FLUTTER_BUILD_NUMBER=1"
|
||||
export "DART_OBFUSCATION=false"
|
||||
export "TRACK_WIDGET_CREATION=false"
|
||||
export "TREE_SHAKE_ICONS=false"
|
||||
export "PACKAGE_CONFIG=.packages"
|
||||
@@ -0,0 +1,11 @@
|
||||
//
|
||||
// Generated file. Do not edit.
|
||||
//
|
||||
|
||||
// clang-format off
|
||||
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
|
||||
void fl_register_plugins(FlPluginRegistry* registry) {
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
//
|
||||
// Generated file. Do not edit.
|
||||
//
|
||||
|
||||
// clang-format off
|
||||
|
||||
#ifndef GENERATED_PLUGIN_REGISTRANT_
|
||||
#define GENERATED_PLUGIN_REGISTRANT_
|
||||
|
||||
#include <flutter_linux/flutter_linux.h>
|
||||
|
||||
// Registers Flutter plugins.
|
||||
void fl_register_plugins(FlPluginRegistry* registry);
|
||||
|
||||
#endif // GENERATED_PLUGIN_REGISTRANT_
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
// This is a generated file; do not edit or check into version control.
|
||||
FLUTTER_ROOT=/usr/local/Caskroom/flutter/1.2.1/flutter
|
||||
FLUTTER_APPLICATION_PATH=/Users/rodydavis/Developer/GitHub/plugins/packages/data_tables/example
|
||||
COCOAPODS_PARALLEL_CODE_SIGN=true
|
||||
FLUTTER_BUILD_DIR=build
|
||||
FLUTTER_BUILD_NAME=1.0.0
|
||||
FLUTTER_BUILD_NUMBER=1
|
||||
EXCLUDED_ARCHS=arm64
|
||||
DART_OBFUSCATION=false
|
||||
TRACK_WIDGET_CREATION=false
|
||||
TREE_SHAKE_ICONS=false
|
||||
PACKAGE_CONFIG=.packages
|
||||
Vendored
Executable
+13
@@ -0,0 +1,13 @@
|
||||
#!/bin/sh
|
||||
# This is a generated file; do not edit or check into version control.
|
||||
export "FLUTTER_ROOT=/usr/local/Caskroom/flutter/1.2.1/flutter"
|
||||
export "FLUTTER_APPLICATION_PATH=/Users/rodydavis/Developer/GitHub/plugins/packages/data_tables/example"
|
||||
export "COCOAPODS_PARALLEL_CODE_SIGN=true"
|
||||
export "FLUTTER_BUILD_DIR=build"
|
||||
export "FLUTTER_BUILD_NAME=1.0.0"
|
||||
export "FLUTTER_BUILD_NUMBER=1"
|
||||
export "EXCLUDED_ARCHS=arm64"
|
||||
export "DART_OBFUSCATION=false"
|
||||
export "TRACK_WIDGET_CREATION=false"
|
||||
export "TREE_SHAKE_ICONS=false"
|
||||
export "PACKAGE_CONFIG=.packages"
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
//
|
||||
// Generated file. Do not edit.
|
||||
//
|
||||
|
||||
// clang-format off
|
||||
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
|
||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
//
|
||||
// Generated file. Do not edit.
|
||||
//
|
||||
|
||||
// clang-format off
|
||||
|
||||
#ifndef GENERATED_PLUGIN_REGISTRANT_
|
||||
#define GENERATED_PLUGIN_REGISTRANT_
|
||||
|
||||
#include <flutter/plugin_registry.h>
|
||||
|
||||
// Registers Flutter plugins.
|
||||
void RegisterPlugins(flutter::PluginRegistry* registry);
|
||||
|
||||
#endif // GENERATED_PLUGIN_REGISTRANT_
|
||||
@@ -0,0 +1,208 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'ui/mobile_paged_listview.dart';
|
||||
import 'ui/stateless_datatable.dart';
|
||||
|
||||
const _kTabletBreakpoint = Size(480, 480);
|
||||
|
||||
class NativeDataTable extends StatelessWidget {
|
||||
const NativeDataTable({
|
||||
required this.columns,
|
||||
required this.rows,
|
||||
this.rowsPerPage = PaginatedDataTable.defaultRowsPerPage,
|
||||
this.header,
|
||||
this.showSelect = true,
|
||||
this.showSort = true,
|
||||
this.onRowsPerPageChanged,
|
||||
this.totalItems,
|
||||
this.onSelectAll,
|
||||
this.sortAscending,
|
||||
this.sortColumnIndex,
|
||||
this.mobileItemBuilder,
|
||||
this.tabletBreakpoint = _kTabletBreakpoint,
|
||||
this.actions,
|
||||
this.firstRowIndex = 0,
|
||||
this.selectedActions,
|
||||
this.onRefresh,
|
||||
this.mobileFetchNextRows = 100,
|
||||
this.handlePrevious,
|
||||
this.handleNext,
|
||||
this.rowCountApproximate = false,
|
||||
this.noItems,
|
||||
this.mobileIsLoading,
|
||||
this.mobileSlivers,
|
||||
this.alwaysShowDataTable = false,
|
||||
});
|
||||
|
||||
NativeDataTable.fromJson({
|
||||
required List<Map<String, dynamic>> items,
|
||||
List<String>? columnKeys,
|
||||
DataColumn Function(String key)? columnBuilder,
|
||||
DataRow Function(Map<String, dynamic> item)? rowBuilder,
|
||||
DataCell Function(String key, dynamic value)? cellBuilder,
|
||||
this.rowsPerPage = PaginatedDataTable.defaultRowsPerPage,
|
||||
this.header,
|
||||
this.showSelect = true,
|
||||
this.showSort = true,
|
||||
this.onRowsPerPageChanged,
|
||||
this.onSelectAll,
|
||||
this.sortAscending,
|
||||
this.sortColumnIndex,
|
||||
this.mobileItemBuilder,
|
||||
this.totalItems,
|
||||
this.tabletBreakpoint = _kTabletBreakpoint,
|
||||
this.actions,
|
||||
this.firstRowIndex = 0,
|
||||
this.selectedActions,
|
||||
this.onRefresh,
|
||||
this.mobileFetchNextRows = 100,
|
||||
this.handlePrevious,
|
||||
this.handleNext,
|
||||
this.rowCountApproximate = false,
|
||||
this.noItems,
|
||||
this.mobileIsLoading,
|
||||
this.mobileSlivers,
|
||||
this.alwaysShowDataTable = false,
|
||||
}) : assert(items.isNotEmpty || columnKeys != null),
|
||||
columns = (columnKeys ?? items[0].keys.toList()).map((e) {
|
||||
if (columnBuilder != null) return columnBuilder(e);
|
||||
return DataColumn(label: Text(e));
|
||||
}).toList(),
|
||||
rows = items.isEmpty
|
||||
? []
|
||||
: items.map((e) {
|
||||
if (rowBuilder != null) return rowBuilder(e);
|
||||
return DataRow(
|
||||
cells: e.entries.map((e) {
|
||||
if (cellBuilder != null) return cellBuilder(e.key, e.value);
|
||||
return DataCell(Text(e.value.toString()));
|
||||
}).toList());
|
||||
}).toList();
|
||||
|
||||
NativeDataTable.builder({
|
||||
required this.columns,
|
||||
this.rowsPerPage = PaginatedDataTable.defaultRowsPerPage,
|
||||
required int itemCount,
|
||||
required DataRowBuilder itemBuilder,
|
||||
this.totalItems,
|
||||
this.header,
|
||||
this.onRowsPerPageChanged,
|
||||
this.onSelectAll,
|
||||
this.sortAscending,
|
||||
this.showSelect = true,
|
||||
this.showSort = true,
|
||||
this.sortColumnIndex,
|
||||
this.mobileItemBuilder,
|
||||
this.tabletBreakpoint = _kTabletBreakpoint,
|
||||
this.actions,
|
||||
this.selectedActions,
|
||||
this.firstRowIndex = 0,
|
||||
this.onRefresh,
|
||||
this.mobileFetchNextRows = 100,
|
||||
this.handlePrevious,
|
||||
this.handleNext,
|
||||
this.rowCountApproximate = false,
|
||||
this.noItems,
|
||||
this.mobileIsLoading,
|
||||
this.mobileSlivers,
|
||||
this.alwaysShowDataTable = false,
|
||||
}) : rows = _buildRows(itemCount, itemBuilder);
|
||||
|
||||
final int? sortColumnIndex;
|
||||
final bool? sortAscending;
|
||||
final ValueChanged<bool?>? onSelectAll;
|
||||
final ValueChanged<int?>? onRowsPerPageChanged;
|
||||
final int? totalItems;
|
||||
final int rowsPerPage;
|
||||
final int firstRowIndex;
|
||||
|
||||
/// Visible on Tablet/Desktop
|
||||
final Widget? header;
|
||||
final List<DataColumn> columns;
|
||||
final List<DataRow> rows;
|
||||
final IndexedWidgetBuilder? mobileItemBuilder;
|
||||
final Size tabletBreakpoint;
|
||||
final List<Widget>? actions, selectedActions;
|
||||
final int mobileFetchNextRows;
|
||||
final RefreshCallback? onRefresh;
|
||||
final VoidCallback? handlePrevious, handleNext;
|
||||
|
||||
/// Set this to [true] for using this with a api
|
||||
final bool rowCountApproximate;
|
||||
final bool showSelect, showSort;
|
||||
final Widget? noItems;
|
||||
final Widget? mobileIsLoading;
|
||||
final List<Widget>? mobileSlivers;
|
||||
final bool alwaysShowDataTable;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final size = MediaQuery.of(context).size;
|
||||
final isTablet = size.width >= tabletBreakpoint.width &&
|
||||
size.height >= tabletBreakpoint.height;
|
||||
if (alwaysShowDataTable || isTablet) {
|
||||
return StatelessDataTable(
|
||||
rows: rows,
|
||||
firstRowIndex: firstRowIndex,
|
||||
totalItems: totalItems,
|
||||
header: header,
|
||||
showCheckboxColumn: showSelect,
|
||||
handleNext: handleNext,
|
||||
handlePrevious: handlePrevious,
|
||||
rowsPerPage: rowsPerPage,
|
||||
onRowsPerPageChanged: onRowsPerPageChanged,
|
||||
sortColumnIndex: sortColumnIndex,
|
||||
sortAscending: sortAscending ?? false,
|
||||
onSelectAll: showSelect ? onSelectAll : null,
|
||||
columns: columns,
|
||||
// ignore: avoid_redundant_argument_values
|
||||
shrinkWrap: false,
|
||||
rowCountApproximate: rowCountApproximate,
|
||||
actions: [
|
||||
...actions ?? [],
|
||||
Container(
|
||||
child: onRefresh == null
|
||||
? null
|
||||
: IconButton(
|
||||
icon: const Icon(Icons.refresh),
|
||||
onPressed: onRefresh,
|
||||
),
|
||||
),
|
||||
],
|
||||
selectedActions: selectedActions,
|
||||
);
|
||||
}
|
||||
|
||||
return PagedListView(
|
||||
rows: rows,
|
||||
slivers: mobileSlivers,
|
||||
columns: columns,
|
||||
showSelect: showSelect,
|
||||
showSort: showSort,
|
||||
loadNext: handleNext,
|
||||
mobileItemBuilder: mobileItemBuilder,
|
||||
actions: actions,
|
||||
selectedActions: selectedActions,
|
||||
onSelectAll: onSelectAll,
|
||||
rowsPerPage: rowsPerPage,
|
||||
sortAscending: sortAscending,
|
||||
sortColumnIndex: sortColumnIndex,
|
||||
onRefresh: onRefresh,
|
||||
isRowCountApproximate: rowCountApproximate,
|
||||
isLoading: mobileIsLoading,
|
||||
noItems: noItems,
|
||||
);
|
||||
}
|
||||
|
||||
static List<DataRow> _buildRows(int count, DataRowBuilder builder) {
|
||||
List<DataRow> _rows = [];
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
_rows.add(builder(i));
|
||||
}
|
||||
|
||||
return _rows;
|
||||
}
|
||||
}
|
||||
|
||||
typedef DataRowBuilder = DataRow Function(int index);
|
||||
@@ -0,0 +1,271 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/cupertino.dart' as cupertino;
|
||||
|
||||
class PagedListView extends StatefulWidget {
|
||||
const PagedListView({
|
||||
required this.rows,
|
||||
required this.columns,
|
||||
this.showSelect = true,
|
||||
this.showSort = true,
|
||||
this.mobileItemBuilder,
|
||||
this.selectedActions,
|
||||
this.actions,
|
||||
this.onSelectAll,
|
||||
this.rowsPerPage,
|
||||
this.loadNext,
|
||||
this.sortColumnIndex,
|
||||
this.sortAscending,
|
||||
this.onRefresh,
|
||||
this.isRowCountApproximate = false,
|
||||
this.initialScrollOffset = 0,
|
||||
this.noItems,
|
||||
this.isLoading,
|
||||
this.slivers,
|
||||
});
|
||||
|
||||
final double initialScrollOffset;
|
||||
final List<DataColumn> columns;
|
||||
final List<DataRow> rows;
|
||||
final IndexedWidgetBuilder? mobileItemBuilder;
|
||||
final bool showSelect, showSort;
|
||||
final List<Widget>? actions;
|
||||
final List<Widget>? selectedActions;
|
||||
final ValueChanged<bool?>? onSelectAll;
|
||||
final int? rowsPerPage;
|
||||
final VoidCallback? loadNext;
|
||||
final int? sortColumnIndex;
|
||||
final bool? sortAscending;
|
||||
final Widget? noItems, isLoading;
|
||||
final RefreshCallback? onRefresh;
|
||||
final bool isRowCountApproximate;
|
||||
final List<Widget>? slivers;
|
||||
|
||||
@override
|
||||
_NativePagedListViewState createState() => _NativePagedListViewState();
|
||||
}
|
||||
|
||||
class _NativePagedListViewState extends State<PagedListView> {
|
||||
ScrollController? _controller;
|
||||
PersistentBottomSheetController? _sortController;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_controller = ScrollController(
|
||||
initialScrollOffset: widget.initialScrollOffset * 40.0,
|
||||
);
|
||||
_controller!.addListener(_scrollListener);
|
||||
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant PagedListView oldWidget) {
|
||||
if (oldWidget.showSelect != widget.showSelect) if (mounted) setState(() {});
|
||||
if (oldWidget.showSort != widget.showSort) if (mounted) setState(() {});
|
||||
if (oldWidget.rows != widget.rows) if (mounted) setState(() {});
|
||||
if (oldWidget.columns != widget.columns) if (mounted) setState(() {});
|
||||
super.didUpdateWidget(oldWidget);
|
||||
}
|
||||
|
||||
void _scrollListener() {
|
||||
if (_controller!.offset >= _controller!.position.maxScrollExtent && !_controller!.position.outOfRange) {
|
||||
// Bottom of List
|
||||
widget.loadNext!();
|
||||
}
|
||||
if (_controller!.offset <= _controller!.position.minScrollExtent && !_controller!.position.outOfRange) {
|
||||
// Top of List
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: CustomScrollView(
|
||||
controller: _controller,
|
||||
slivers: <Widget>[
|
||||
...?widget.slivers,
|
||||
if (widget.onRefresh == null) SliverToBoxAdapter(child: Container()) else cupertino.CupertinoSliverRefreshControl(onRefresh: widget.onRefresh),
|
||||
if (widget.isLoading != null && widget.rows.isEmpty)
|
||||
Center(child: widget.isLoading)
|
||||
else
|
||||
widget.noItems != null && widget.rows.isEmpty
|
||||
? Center(child: widget.noItems)
|
||||
: SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
widget.mobileItemBuilder ??
|
||||
(context, index) {
|
||||
return ExpansionTile(
|
||||
leading: widget.showSelect
|
||||
? Checkbox(
|
||||
value: widget.rows[index].selected,
|
||||
onChanged: (bool? value) {
|
||||
setState(() {
|
||||
widget.rows[index].onSelectChanged!(value);
|
||||
});
|
||||
},
|
||||
)
|
||||
: null,
|
||||
title: widget.rows[index].cells.first.child,
|
||||
children: _buildMobileChildren(index),
|
||||
);
|
||||
},
|
||||
childCount: widget.rows.length,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
if (widget.showSelect || widget.showSort)
|
||||
SafeArea(
|
||||
top: false,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
top: cupertino.BorderSide(color: Colors.grey[200]!),
|
||||
)),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: rowsSelected ? selectedActions : actions,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> get actions => [
|
||||
if (widget.showSelect)
|
||||
IconButton(
|
||||
icon: const Icon(Icons.select_all),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
widget.onSelectAll!(true);
|
||||
});
|
||||
},
|
||||
),
|
||||
if (widget.showSort)
|
||||
IconButton(
|
||||
tooltip: "Sort Items",
|
||||
icon: const Icon(Icons.sort_by_alpha),
|
||||
onPressed: () {
|
||||
if (_sortController != null) {
|
||||
_sortController!.close();
|
||||
debugPrint("Close...");
|
||||
return;
|
||||
}
|
||||
|
||||
_sortController = Scaffold.of(context).showBottomSheet((context) {
|
||||
final List<DataColumn> _cols = widget.columns.where((c) => c.onSort != null).toList();
|
||||
final bool? _sortAsc = widget.sortAscending;
|
||||
final int? selectedIndex = widget.sortColumnIndex;
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).brightness == Brightness.dark ? Colors.black38 : Colors.grey[200],
|
||||
borderRadius: const BorderRadius.only(topLeft: Radius.circular(20), topRight: Radius.circular(20))),
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
for (var i = 0; i < _cols.length; i++) ...[
|
||||
ListTile(
|
||||
dense: true,
|
||||
selected: selectedIndex == i,
|
||||
title: _cols[i].label,
|
||||
subtitle: Text(widget.sortAscending! ? 'Ascending' : 'Descending'),
|
||||
leading: Radio<int>(
|
||||
groupValue: selectedIndex,
|
||||
onChanged: (value) {
|
||||
_sortController!.setState!(() {
|
||||
_cols[i].onSort!(i, _sortAsc!);
|
||||
});
|
||||
},
|
||||
value: i,
|
||||
),
|
||||
trailing: IconButton(
|
||||
icon: Icon(_sortAsc! ? Icons.arrow_upward : Icons.arrow_downward),
|
||||
onPressed: () {
|
||||
_sortController!.setState!(() {
|
||||
_cols[i].onSort!(i, !_sortAsc);
|
||||
});
|
||||
},
|
||||
),
|
||||
onTap: () {
|
||||
if (selectedIndex == i) {
|
||||
_sortController!.setState!(() {
|
||||
_cols[i].onSort!(i, !_sortAsc);
|
||||
});
|
||||
} else {
|
||||
_sortController!.setState!(() {
|
||||
_cols[i].onSort!(i, _sortAsc);
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
Container(
|
||||
padding: EdgeInsets.only(top: 10.0, bottom: 10.0),
|
||||
child: Container(
|
||||
child: TextButton(
|
||||
child: Text(
|
||||
"Close",
|
||||
style: Theme.of(context).textTheme.headline5,
|
||||
),
|
||||
onPressed: () {
|
||||
_sortController!.close();
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
_sortController!.closed.whenComplete(() {
|
||||
debugPrint("Done");
|
||||
_sortController = null;
|
||||
});
|
||||
},
|
||||
),
|
||||
Container(
|
||||
child: widget.onRefresh == null
|
||||
? null
|
||||
: IconButton(
|
||||
icon: Icon(Icons.refresh),
|
||||
onPressed: widget.onRefresh,
|
||||
),
|
||||
),
|
||||
...widget.actions ?? [],
|
||||
];
|
||||
List<Widget> get selectedActions => [
|
||||
IconButton(
|
||||
icon: Icon(Icons.clear_all),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
widget.onSelectAll!(false);
|
||||
});
|
||||
},
|
||||
),
|
||||
...widget.selectedActions ?? []
|
||||
];
|
||||
|
||||
bool get rowsSelected => _selectedRowCount != 0;
|
||||
|
||||
int get _selectedRowCount => widget.rows.where((d) => d.selected).toSet().toList().length;
|
||||
|
||||
List<Widget> _buildMobileChildren(int index) {
|
||||
List<Widget> _children = [];
|
||||
int i = 0;
|
||||
for (var _cell in widget.rows[index].cells) {
|
||||
_children.add(ListTile(
|
||||
title: widget.columns[i].label,
|
||||
subtitle: _cell.child,
|
||||
));
|
||||
i++;
|
||||
}
|
||||
return _children;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,337 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flutter/gestures.dart' show DragStartBehavior;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
class StatelessDataTable extends StatelessWidget {
|
||||
StatelessDataTable({
|
||||
Key? key,
|
||||
this.header,
|
||||
this.actions,
|
||||
required this.columns,
|
||||
required this.rows,
|
||||
this.sortColumnIndex,
|
||||
this.showCheckboxColumn = true,
|
||||
this.sortAscending = true,
|
||||
this.totalItems,
|
||||
this.onSelectAll,
|
||||
this.firstRowIndex = 0,
|
||||
this.onPageChanged,
|
||||
this.shrinkWrap = false,
|
||||
this.selectedActions,
|
||||
this.rowCountApproximate = false,
|
||||
this.rowsPerPage = defaultRowsPerPage,
|
||||
this.handlePrevious,
|
||||
this.handleNext,
|
||||
this.availableRowsPerPage = const <int>[defaultRowsPerPage, defaultRowsPerPage * 2, defaultRowsPerPage * 5, defaultRowsPerPage * 10],
|
||||
this.onRowsPerPageChanged,
|
||||
this.dragStartBehavior = DragStartBehavior.down,
|
||||
}) : assert(columns.isNotEmpty),
|
||||
assert(sortColumnIndex == null || (sortColumnIndex >= 0 && sortColumnIndex < columns.length)),
|
||||
assert(rowsPerPage > 0),
|
||||
assert(() {
|
||||
if (onRowsPerPageChanged != null) {
|
||||
assert(availableRowsPerPage.contains(rowsPerPage));
|
||||
}
|
||||
return true;
|
||||
}()),
|
||||
super(key: key);
|
||||
|
||||
final VoidCallback? handleNext, handlePrevious;
|
||||
final Widget? header;
|
||||
final bool showCheckboxColumn;
|
||||
final List<Widget>? actions, selectedActions;
|
||||
final List<DataColumn> columns;
|
||||
final List<DataRow> rows;
|
||||
final bool shrinkWrap;
|
||||
final int? sortColumnIndex;
|
||||
final int? totalItems;
|
||||
final bool sortAscending;
|
||||
final ValueSetter<bool?>? onSelectAll;
|
||||
final ValueChanged<int?>? onPageChanged;
|
||||
final int rowsPerPage;
|
||||
static const int defaultRowsPerPage = 10;
|
||||
final List<int> availableRowsPerPage;
|
||||
final ValueChanged<int?>? onRowsPerPageChanged;
|
||||
final DragStartBehavior dragStartBehavior;
|
||||
final int firstRowIndex;
|
||||
final bool rowCountApproximate;
|
||||
final Map<int, DataRow> _rows = <int, DataRow>{};
|
||||
|
||||
DataRow _getBlankRowFor(int index) => DataRow.byIndex(index: index, cells: columns.map<DataCell>((DataColumn column) => DataCell.empty).toList());
|
||||
|
||||
DataRow _getProgressIndicatorRowFor(int index) {
|
||||
bool haveProgressIndicator = false;
|
||||
final List<DataCell> cells = columns.map<DataCell>((DataColumn column) {
|
||||
if (!column.numeric) {
|
||||
haveProgressIndicator = true;
|
||||
return const DataCell(CircularProgressIndicator());
|
||||
}
|
||||
return DataCell.empty;
|
||||
}).toList();
|
||||
if (!haveProgressIndicator) {
|
||||
haveProgressIndicator = true;
|
||||
cells[0] = const DataCell(CircularProgressIndicator());
|
||||
}
|
||||
return DataRow.byIndex(index: index, cells: cells);
|
||||
}
|
||||
|
||||
List<DataRow> _getRows(int firstRowIndex, int rowsPerPage) {
|
||||
final List<DataRow> result = <DataRow>[];
|
||||
final int nextPageFirstRowIndex = firstRowIndex + rowsPerPage;
|
||||
bool haveProgressIndicator = false;
|
||||
for (int index = firstRowIndex; index < nextPageFirstRowIndex; index += 1) {
|
||||
DataRow? row;
|
||||
if (index < rows.length || rowCountApproximate) {
|
||||
try {
|
||||
row = _rows.putIfAbsent(index, () => rows[index]);
|
||||
} catch (e) {
|
||||
print("Row not found => $e");
|
||||
}
|
||||
if (row == null && !haveProgressIndicator) {
|
||||
row ??= _getProgressIndicatorRowFor(index);
|
||||
haveProgressIndicator = true;
|
||||
}
|
||||
row ??= _getBlankRowFor(index);
|
||||
result.add(row);
|
||||
}
|
||||
}
|
||||
//show no data
|
||||
if (result.isEmpty) {
|
||||
var cells = columns.map<DataCell>((DataColumn column) => DataCell.empty).toList();
|
||||
cells[cells.length ~/ 2] = const DataCell(Text('no data'));
|
||||
result.add(DataRow.byIndex(index: 0, cells: cells));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
final GlobalKey _tableKey = GlobalKey();
|
||||
|
||||
int get _selectedRowCount => rows.where((d) => d.selected).toSet().toList().length;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
assert(debugCheckHasMaterialLocalizations(context));
|
||||
final ThemeData themeData = Theme.of(context);
|
||||
final MaterialLocalizations localizations = MaterialLocalizations.of(context);
|
||||
|
||||
final List<Widget> headerWidgets = <Widget>[];
|
||||
double startPadding = 24;
|
||||
|
||||
if (_selectedRowCount == 0) {
|
||||
if (header != null) {
|
||||
headerWidgets.add(Expanded(child: header!));
|
||||
if (header is ButtonBar) {
|
||||
startPadding = 12.0;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
headerWidgets.add(Expanded(
|
||||
child: Text(localizations.selectedRowCountTitle(_selectedRowCount)),
|
||||
));
|
||||
}
|
||||
if (selectedActions != null && _selectedRowCount != 0) {
|
||||
headerWidgets.addAll(selectedActions!.map<Widget>((Widget action) {
|
||||
return Padding(
|
||||
padding: const EdgeInsetsDirectional.only(start: 24.0 - 8.0 * 2.0),
|
||||
child: action,
|
||||
);
|
||||
}).toList());
|
||||
} else if (actions != null) {
|
||||
headerWidgets.addAll(actions!.map<Widget>((Widget action) {
|
||||
return Padding(
|
||||
padding: const EdgeInsetsDirectional.only(start: 24.0 - 8.0 * 2.0),
|
||||
child: action,
|
||||
);
|
||||
}).toList());
|
||||
}
|
||||
|
||||
final TextStyle? footerTextStyle = themeData.textTheme.caption;
|
||||
final List<Widget> footerWidgets = <Widget>[];
|
||||
if (onRowsPerPageChanged != null) {
|
||||
final List<Widget> _footerChildren =
|
||||
availableRowsPerPage.where((int value) => value <= rows.length || value == rowsPerPage).map<DropdownMenuItem<int>>((int value) {
|
||||
return DropdownMenuItem<int>(value: value, child: Text('$value'));
|
||||
}).toList();
|
||||
footerWidgets.addAll(<Widget>[
|
||||
Container(width: 14),
|
||||
Text(localizations.rowsPerPageTitle),
|
||||
ConstrainedBox(
|
||||
constraints: const BoxConstraints(minWidth: 64),
|
||||
child: Align(
|
||||
alignment: AlignmentDirectional.centerEnd,
|
||||
child: DropdownButtonHideUnderline(
|
||||
child: DropdownButton<int>(
|
||||
items: _footerChildren as List<DropdownMenuItem<int>>?,
|
||||
value: rowsPerPage,
|
||||
onChanged: onRowsPerPageChanged,
|
||||
style: footerTextStyle,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
]);
|
||||
}
|
||||
footerWidgets.addAll(<Widget>[
|
||||
Container(width: 32),
|
||||
Text(localizations.pageRowsInfoTitle(firstRowIndex + 1, firstRowIndex + rowsPerPage, totalItems ?? rows.length, rowCountApproximate)),
|
||||
Container(width: 32),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.chevron_left),
|
||||
padding: EdgeInsets.zero,
|
||||
tooltip: localizations.previousPageTooltip,
|
||||
onPressed: firstRowIndex <= 0 ? null : handlePrevious),
|
||||
Container(width: 24),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.chevron_right),
|
||||
padding: EdgeInsets.zero,
|
||||
tooltip: localizations.nextPageTooltip,
|
||||
onPressed: (!rowCountApproximate && (firstRowIndex + rowsPerPage >= (totalItems ?? rows.length))) ? null : handleNext),
|
||||
Container(width: 14),
|
||||
]);
|
||||
|
||||
if (shrinkWrap) {
|
||||
return SafeArea(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: <Widget>[
|
||||
if (showCheckboxColumn)
|
||||
Semantics(
|
||||
container: true,
|
||||
child: DefaultTextStyle(
|
||||
style: _selectedRowCount > 0
|
||||
? themeData.textTheme.subtitle1!.copyWith(color: themeData.colorScheme.secondary)
|
||||
: themeData.textTheme.headline6!.copyWith(fontWeight: FontWeight.w400),
|
||||
child: IconTheme.merge(
|
||||
data: const IconThemeData(opacity: 0.54),
|
||||
child: ButtonTheme(
|
||||
child: Ink(
|
||||
height: 64,
|
||||
color: _selectedRowCount > 0 ? themeData.secondaryHeaderColor : null,
|
||||
child: Padding(
|
||||
padding: EdgeInsetsDirectional.only(start: startPadding, end: 14),
|
||||
child: Row(mainAxisAlignment: MainAxisAlignment.end, children: headerWidgets),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
dragStartBehavior: dragStartBehavior,
|
||||
child: Builder(
|
||||
builder: (BuildContext context) {
|
||||
final rows = _getRows(firstRowIndex, rowsPerPage);
|
||||
return DataTable(
|
||||
showCheckboxColumn: showCheckboxColumn,
|
||||
key: _tableKey,
|
||||
columns: columns,
|
||||
sortColumnIndex: sortColumnIndex,
|
||||
sortAscending: sortAscending,
|
||||
onSelectAll: onSelectAll,
|
||||
rows: rows,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
DefaultTextStyle(
|
||||
style: footerTextStyle!,
|
||||
child: IconTheme.merge(
|
||||
data: const IconThemeData(opacity: 0.54),
|
||||
child: Container(
|
||||
height: 56,
|
||||
child: SingleChildScrollView(
|
||||
dragStartBehavior: dragStartBehavior,
|
||||
scrollDirection: Axis.horizontal,
|
||||
reverse: true,
|
||||
child: Row(
|
||||
children: footerWidgets,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return SafeArea(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: <Widget>[
|
||||
if (showCheckboxColumn)
|
||||
Semantics(
|
||||
container: true,
|
||||
child: DefaultTextStyle(
|
||||
style: _selectedRowCount > 0
|
||||
? themeData.textTheme.subtitle1!.copyWith(color: themeData.colorScheme.secondary)
|
||||
: themeData.textTheme.headline6!.copyWith(fontWeight: FontWeight.w400),
|
||||
child: IconTheme.merge(
|
||||
data: const IconThemeData(opacity: 0.54),
|
||||
child: ButtonTheme(
|
||||
child: Ink(
|
||||
height: 64,
|
||||
color: _selectedRowCount > 0 ? themeData.secondaryHeaderColor : null,
|
||||
child: Padding(
|
||||
padding: EdgeInsetsDirectional.only(start: startPadding, end: 14),
|
||||
child: Row(mainAxisAlignment: MainAxisAlignment.end, children: headerWidgets),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
flex: 8,
|
||||
child: ScrollConfiguration(
|
||||
behavior: CustomScrollBehavior(),
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.vertical,
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: DataTable(
|
||||
key: _tableKey,
|
||||
columns: columns,
|
||||
sortColumnIndex: sortColumnIndex,
|
||||
sortAscending: sortAscending,
|
||||
onSelectAll: onSelectAll,
|
||||
rows: _getRows(firstRowIndex, rowsPerPage)),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
DefaultTextStyle(
|
||||
style: footerTextStyle!,
|
||||
child: IconTheme.merge(
|
||||
data: const IconThemeData(opacity: 0.54),
|
||||
child: SizedBox(
|
||||
height: 56,
|
||||
child: SingleChildScrollView(
|
||||
dragStartBehavior: dragStartBehavior,
|
||||
scrollDirection: Axis.horizontal,
|
||||
reverse: true,
|
||||
child: Row(
|
||||
children: footerWidgets,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CustomScrollBehavior extends MaterialScrollBehavior {
|
||||
@override
|
||||
Set<PointerDeviceKind> get dragDevices => {
|
||||
PointerDeviceKind.touch,
|
||||
PointerDeviceKind.mouse,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
name: data_tables
|
||||
description: ListView on Mobile and Stateless Data Tables on Tablets and Desktops.
|
||||
version: 1.4.0
|
||||
homepage: https://github.com/rodydavis/plugins
|
||||
repository: https://github.com/rodydavis/data_tables
|
||||
environment:
|
||||
sdk: '>=2.12.0-259.8.beta <3.0.0'
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
pedantic: 1.11.1
|
||||
|
||||
flutter:
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 427 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 446 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 278 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 257 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 287 KiB |
Binary file not shown.
Reference in New Issue
Block a user