import 'dart:math' as math; import 'package:data_table_2/data_table_2.dart'; import 'package:flutter/material.dart'; export 'package:data_table_2/data_table_2.dart' show DataColumn2; const _kDataTableHorizontalMargin = 48.0; const kDefaultColumnSpacing = 56.0; const _kMinRowsPerPage = 5; typedef ColumnsBuilder = List Function(void Function(int, bool)); typedef DataRowBuilder = DataRow? Function( T, int, bool, void Function(bool?)?); class FlutterFlowDataTableController extends DataTableSource { FlutterFlowDataTableController({ List? initialData, int? numRows, PaginatorController? paginatorController, bool selectable = false, }) { data = initialData?.toList() ?? []; numRows = numRows; this.paginatorController = paginatorController ?? PaginatorController(); _selectable = selectable; } DataRowBuilder? _dataRowBuilder; late PaginatorController paginatorController; List data = []; int? _numRows; List get selectedData => selectedRows.where((i) => i < data.length).map(data.elementAt).toList(); bool _selectable = false; final Set selectedRows = {}; int rowsPerPage = defaultRowsPerPage; int? sortColumnIndex; bool sortAscending = true; void init({ DataRowBuilder? dataRowBuilder, bool? selectable, List? initialData, int? initialNumRows, }) { _dataRowBuilder = dataRowBuilder ?? _dataRowBuilder; _selectable = selectable ?? _selectable; data = initialData?.toList() ?? data; _numRows = initialNumRows; } void updateData({ List? data, int? numRows, bool notify = true, }) { this.data = data?.toList() ?? this.data; _numRows = numRows ?? _numRows; if (notify) { notifyListeners(); } } void updateSort({ required int columnIndex, required bool ascending, Function(int, bool)? onSortChanged, }) { sortColumnIndex = columnIndex; sortAscending = ascending; if (onSortChanged != null) { onSortChanged(columnIndex, ascending); } notifyListeners(); } @override DataRow? getRow(int index) { final row = data.elementAtOrNull(index); return _dataRowBuilder != null && row != null ? _dataRowBuilder!( row, index, selectedRows.contains(index), _selectable ? (selected) { if (selected == null) { return; } selected ? selectedRows.add(index) : selectedRows.remove(index); notifyListeners(); } : null, ) : null; } @override bool get isRowCountApproximate => false; @override int get rowCount => _numRows ?? data.length; @override int get selectedRowCount => selectedRows.length; } class FlutterFlowDataTable extends StatefulWidget { const FlutterFlowDataTable({ super.key, required this.controller, required this.data, this.numRows, required this.columnsBuilder, required this.dataRowBuilder, this.emptyBuilder, this.onPageChanged, this.onSortChanged, this.onRowsPerPageChanged, this.paginated = true, this.selectable = false, this.hidePaginator = false, this.showFirstLastButtons = false, this.width, this.height, this.minWidth, this.headingRowHeight = 56, this.dataRowHeight = kMinInteractiveDimension, this.columnSpacing = kDefaultColumnSpacing, this.headingRowColor, this.sortIconColor, this.borderRadius, this.addHorizontalDivider = true, this.addTopAndBottomDivider = false, this.hideDefaultHorizontalDivider = false, this.addVerticalDivider = false, this.horizontalDividerColor, this.horizontalDividerThickness, this.verticalDividerColor, this.verticalDividerThickness, this.checkboxUnselectedFillColor, this.checkboxSelectedFillColor, this.checkboxUnselectedBorderColor, this.checkboxSelectedBorderColor, this.checkboxCheckColor, }); final FlutterFlowDataTableController controller; final List data; final int? numRows; final ColumnsBuilder columnsBuilder; final DataRowBuilder dataRowBuilder; final Widget? Function()? emptyBuilder; // Callback functions final Function(int)? onPageChanged; final Function(int, bool)? onSortChanged; final Function(int)? onRowsPerPageChanged; // Functionality options final bool paginated; final bool selectable; final bool showFirstLastButtons; final bool hidePaginator; // Size and shape options final double? width; final double? height; final double? minWidth; final double headingRowHeight; final double dataRowHeight; final double columnSpacing; // Table style options final Color? headingRowColor; final Color? sortIconColor; final BorderRadius? borderRadius; final bool addHorizontalDivider; final bool addTopAndBottomDivider; final bool hideDefaultHorizontalDivider; final Color? horizontalDividerColor; final double? horizontalDividerThickness; final bool addVerticalDivider; final Color? verticalDividerColor; final double? verticalDividerThickness; // Checkbox style options final Color? checkboxUnselectedFillColor; final Color? checkboxSelectedFillColor; final Color? checkboxUnselectedBorderColor; final Color? checkboxSelectedBorderColor; final Color? checkboxCheckColor; @override State> createState() => _FlutterFlowDataTableState(); } class _FlutterFlowDataTableState extends State> { FlutterFlowDataTableController get controller => widget.controller; int get rowCount => controller.rowCount; int get initialRowsPerPage => rowCount > _kMinRowsPerPage ? defaultRowsPerPage : _kMinRowsPerPage; @override void initState() { super.initState(); dataTableShowLogs = false; // Disables noisy DataTable2 debug statements. controller.init( dataRowBuilder: widget.dataRowBuilder, selectable: widget.selectable, initialData: widget.data, initialNumRows: widget.numRows, ); // ignore: cascade_invocations controller.addListener(() => setState(() {})); } @override void didUpdateWidget(FlutterFlowDataTable oldWidget) { super.didUpdateWidget(oldWidget); controller.updateData( data: widget.data, numRows: widget.numRows, notify: true, ); } @override Widget build(BuildContext context) { final columns = widget.columnsBuilder( (index, ascending) { controller.updateSort( columnIndex: index, ascending: ascending, onSortChanged: widget.onSortChanged, ); setState(() {}); }, ); final checkboxThemeData = CheckboxThemeData( checkColor: MaterialStateProperty.all( widget.checkboxCheckColor ?? Colors.black54, ), fillColor: MaterialStateProperty.resolveWith( (states) => states.contains(MaterialState.selected) ? widget.checkboxSelectedFillColor ?? Colors.white.withOpacity(0.01) : widget.checkboxUnselectedFillColor ?? Colors.white.withOpacity(0.01), ), side: MaterialStateBorderSide.resolveWith( (states) => BorderSide( width: 2.0, color: states.contains(MaterialState.selected) ? widget.checkboxSelectedBorderColor ?? Colors.black54 : widget.checkboxUnselectedBorderColor ?? Colors.black54, ), ), overlayColor: MaterialStateProperty.all(Colors.transparent), ); final horizontalBorder = widget.addHorizontalDivider ? BorderSide( color: widget.horizontalDividerColor ?? Colors.transparent, width: widget.horizontalDividerThickness ?? 1.0, ) : BorderSide.none; return ClipRRect( borderRadius: widget.borderRadius ?? BorderRadius.zero, child: SizedBox( width: widget.width, height: widget.height, child: Theme( data: Theme.of(context).copyWith( iconTheme: widget.sortIconColor != null ? IconThemeData(color: widget.sortIconColor) : null, ), child: PaginatedDataTable2( source: controller, controller: widget.paginated ? controller.paginatorController : null, rowsPerPage: widget.paginated ? initialRowsPerPage : rowCount, availableRowsPerPage: const [5, 10, 25, 50, 100], onPageChanged: widget.onPageChanged != null ? (index) => widget.onPageChanged!(index) : null, columnSpacing: widget.columnSpacing, onRowsPerPageChanged: widget.paginated ? (value) { controller.rowsPerPage = value ?? initialRowsPerPage; if (widget.onRowsPerPageChanged != null) { widget.onRowsPerPageChanged!(controller.rowsPerPage); } } : null, columns: columns, empty: widget.emptyBuilder != null ? widget.emptyBuilder!() : null, sortColumnIndex: controller.sortColumnIndex, sortAscending: controller.sortAscending, showCheckboxColumn: widget.selectable, datarowCheckboxTheme: checkboxThemeData, headingCheckboxTheme: checkboxThemeData, hidePaginator: !widget.paginated || widget.hidePaginator, wrapInCard: false, renderEmptyRowsInTheEnd: false, border: TableBorder( horizontalInside: horizontalBorder, verticalInside: widget.addVerticalDivider ? BorderSide( color: widget.verticalDividerColor ?? Colors.transparent, width: widget.verticalDividerThickness ?? 1.0, ) : BorderSide.none, bottom: widget.addTopAndBottomDivider ? horizontalBorder : BorderSide.none, ), dividerThickness: widget.hideDefaultHorizontalDivider ? 0.0 : null, headingRowColor: MaterialStateProperty.all(widget.headingRowColor), headingRowHeight: widget.headingRowHeight, dataRowHeight: widget.dataRowHeight, showFirstLastButtons: widget.showFirstLastButtons, minWidth: math.max(widget.minWidth ?? 0, _getColumnsWidth(columns)), ), ), ), ); } // Return the total fixed width of all columns that have a specified width, // plus one to make the data table scrollable if there is insufficient space. double _getColumnsWidth(List columns) => columns.where((c) => c is DataColumn2 && c.fixedWidth != null).fold( ((widget.selectable ? 2 : 1) * _kDataTableHorizontalMargin) + 1, (sum, col) => sum + (col as DataColumn2).fixedWidth!, ); }