adding packages

This commit is contained in:
2026-01-15 14:38:46 -08:00
parent ef86e3ab6a
commit 6869cf47e5
5253 changed files with 726695 additions and 34 deletions
+2
View File
@@ -0,0 +1,2 @@
# Auto detect text files and perform LF normalization
* text=auto
+28
View File
@@ -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
+7
View File
@@ -0,0 +1,7 @@
.DS_Store
.dart_tool/
.packages
.pub/
build/
+10
View File
@@ -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
View File
@@ -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"
}
]
}
+6
View File
@@ -0,0 +1,6 @@
{
"cSpell.words": [
"datatable",
"listview"
]
}
+102
View File
@@ -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
+21
View File
@@ -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.
+55
View File
@@ -0,0 +1,55 @@
[![Buy Me A Coffee](https://img.shields.io/badge/Donate-Buy%20Me%20A%20Coffee-yellow.svg)](https://www.buymeacoffee.com/rodydavis)
[![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=WSH3GVC49GNNJ)
![github pages](https://github.com/rodydavis/data_tables/workflows/github%20pages/badge.svg)
[![GitHub stars](https://img.shields.io/github/stars/rodydavis/data_tables?color=blue)](https://github.com/rodydavis/data_tables)
[![data_tables](https://img.shields.io/pub/v/data_tables.svg)](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
![](https://github.com/rodydavis/data_tables/blob/master/screenshots/1.PNG)
![](https://github.com/rodydavis/data_tables/blob/master/screenshots/2.PNG)
![](https://github.com/rodydavis/data_tables/blob/master/screenshots/3.PNG)
![](https://github.com/rodydavis/data_tables/blob/master/screenshots/4.PNG)
![](https://github.com/rodydavis/data_tables/blob/master/screenshots/5.PNG)
@@ -0,0 +1,6 @@
include: package:pedantic/analysis_options.yaml
analyzer:
exclude:
- lib/**
- pubspec.lock
@@ -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_
@@ -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
@@ -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"
@@ -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_
+208
View File
@@ -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,
};
}
+18
View File
@@ -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.