123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446 |
- import 'package:flutter/material.dart';
- import 'package:vitalapp/components/scroll_list.dart';
- import 'table_column.dart';
- const _cellPadding = EdgeInsets.symmetric(
- horizontal: 14,
- vertical: 12,
- );
- const _cellTextStyle = TextStyle(fontSize: 14);
- const _headerBgColor = Color.fromRGBO(233, 237, 240, 1);
- class VitalTable<T> extends StatefulWidget {
- VitalTable({
- Key? key,
- required this.columns,
- required this.source,
- this.loading = false,
- this.autoHeight = true,
- this.showSelect = false,
- this.selecteds,
- this.onAllRowsSelected,
- this.onRowSelected,
- this.onRowTap,
- this.headerDecoration,
- this.rowDecoration,
- this.selectedDecoration,
- this.headerTextStyle,
- this.rowTextStyle,
- this.selectedTextStyle,
- this.noDataHintText,
- this.currectSelected,
- this.checkCellWidth = 90,
- this.cellPadding = _cellPadding,
- }) : assert(() {
- if (showSelect == true && selecteds == null) {
- throw FlutterError(
- "Property `selecteds` must not be null while `selecteds` is not null.");
- }
- return true;
- }()),
- super(key: key);
- final double checkCellWidth;
- final EdgeInsets? cellPadding;
-
- final String? noDataHintText;
-
- final List<TableColumn<T>> columns;
-
- final List<T>? source;
-
- final bool loading;
-
- final bool autoHeight;
-
- final bool showSelect;
-
- final List<int>? selecteds;
-
-
-
- final void Function(bool value, List<int> selectedIndexs)? onAllRowsSelected;
-
-
-
-
- final void Function(bool value, int index, List<int> selectedIndexs)?
- onRowSelected;
-
-
-
-
- final void Function(int index)? onRowTap;
-
- final BoxDecoration? headerDecoration;
-
- final BoxDecoration? rowDecoration;
-
- final BoxDecoration? selectedDecoration;
-
- final TextStyle? headerTextStyle;
-
- final TextStyle? rowTextStyle;
-
- final TextStyle? selectedTextStyle;
-
- int? currectSelected = -1;
- @override
- State<StatefulWidget> createState() => _VitalTableState<T>();
- }
- class _VitalTableState<T> extends State<VitalTable<T>> {
- final ScrollController _scrollController = ScrollController();
- List<int> _selectedIdxs = [];
- List<T> _source = [];
- late int _currectSelected = widget.currectSelected ?? -1;
- @override
- void didUpdateWidget(VitalTable<T> oldWidget) {
- _loadData();
- if (widget.currectSelected != oldWidget.currectSelected) {
- _currectSelected = widget.currectSelected ?? -1;
- setState(() {});
- }
- super.didUpdateWidget(oldWidget);
- }
- @override
- void initState() {
- _loadData();
- super.initState();
- }
- void _loadData() {
- _selectedIdxs = [if (widget.selecteds != null) ...widget.selecteds!];
- _source = widget.source ?? [];
- }
- @override
- Widget build(BuildContext context) {
- return Column(
- mainAxisAlignment: MainAxisAlignment.start,
- children: [
-
- if (widget.columns.isNotEmpty) buildHeader(),
-
- if (widget.loading)
- const SizedBox(
- height: 5,
- child: LinearProgressIndicator(),
- ),
-
- if (widget.autoHeight)
- Column(children: createRows())
- else
- Expanded(
- child: AlwaysScrollListView(
- scrollController: _scrollController,
- child: ListView(
- padding: const EdgeInsets.only(right: 10),
- controller: _scrollController,
- children: createRows(),
- ),
- ),
- ),
- const SizedBox(height: 20),
- ],
- );
- }
- static Alignment headerAlignSwitch(TextAlign? textAlign) {
- switch (textAlign) {
- case TextAlign.center:
- return Alignment.center;
- case TextAlign.left:
- return Alignment.centerLeft;
- case TextAlign.right:
- return Alignment.centerRight;
- default:
- return Alignment.center;
- }
- }
- Widget buildHeader() {
- final decoration = widget.headerDecoration ??
- const BoxDecoration(
- color: _headerBgColor,
-
-
-
- );
- final cells = <Widget>[];
- if (widget.showSelect) {
- final checked = _selectedIdxs.isNotEmpty &&
- _source.isNotEmpty &&
- _selectedIdxs.length == _source.length;
- cells.add(
- buildCheckCell(
- value: checked,
- onChanged: (value) {
- final checked = value == true;
- setState(() {
- _selectedIdxs = checked
- ? List.generate(_source.length, (index) => index)
- : [];
- });
- widget.onAllRowsSelected?.call(checked, _selectedIdxs);
- },
- ),
- );
- }
- cells.addAll(
- widget.columns.map(
- (column) {
- final child = column.headerRender != null
- ? column.headerRender!()
- : Container(
- padding: widget.cellPadding,
- alignment: headerAlignSwitch(column.textAlign),
- child: Wrap(
- crossAxisAlignment: WrapCrossAlignment.center,
- children: [
- Text(
- column.headerText!,
- textAlign: column.textAlign,
- style: widget.headerTextStyle ??
- const TextStyle(
- fontSize: 14,
- fontWeight: FontWeight.bold,
- ),
- )
- ],
- ),
- );
- final cell = buildCellBox(column, child);
- return Expanded(flex: column.actualFlex, child: cell);
- },
- ),
- );
- return Container(
- decoration: decoration,
- child: Row(
- mainAxisSize: MainAxisSize.min,
- children: cells,
- ),
- );
- }
- List<Widget> createRows() {
- if (_source.isEmpty) {
- if (widget.loading) return const [];
- var noDataHintText = widget.noDataHintText;
- return [
- Container(
- height: 50,
- alignment: Alignment.center,
- child: Text(
- noDataHintText ?? "No data found",
- style: const TextStyle(fontSize: 20),
- ),
- )
- ];
- }
- final rowDecoration = widget.rowDecoration ??
- const BoxDecoration(
-
-
-
- );
- final selectedDecoration = widget.selectedDecoration ?? rowDecoration;
- final rows = <Widget>[];
- for (var i = 0, len = _source.length; i < len; i++) {
- final rowData = _source[i];
- rows.add(buildRow(rowData, i, rowDecoration, selectedDecoration));
- }
- return rows;
- }
- Widget buildRow(
- T rowData,
- int index,
- BoxDecoration rowDecoration,
- BoxDecoration selectedDecoration,
- ) {
- var isSelected = false;
- var decoration = rowDecoration;
- final textStyle = widget.rowTextStyle ?? _cellTextStyle;
- final selectedTextStyle = widget.selectedTextStyle ?? textStyle;
- final cells = <Widget>[];
- if (widget.showSelect) {
- isSelected = _selectedIdxs.contains(index);
- decoration = selectedDecoration;
- cells.add(
- buildCheckCell(
- value: _selectedIdxs.contains(index),
- onChanged: (value) {
- final checked = value == true;
- setState(() {
- if (checked) {
- if (!_selectedIdxs.contains(index)) {
- _selectedIdxs.add(index);
- }
- } else {
- if (_selectedIdxs.contains(index)) {
- _selectedIdxs.remove(index);
- }
- }
- });
- widget.onRowSelected?.call(checked, index, _selectedIdxs);
- },
- ),
- );
- }
- cells.addAll(
- widget.columns.map(
- (column) {
- final child = column.render != null
- ? column.render!.call(rowData, index)
- : Container(
- padding: _cellPadding,
- alignment: headerAlignSwitch(column.textAlign),
- child: Wrap(
- crossAxisAlignment: WrapCrossAlignment.center,
- children: [
- Tooltip(
- message: column.textFormatter!.call(rowData, index),
- child: Text(
- _breakWord(
- column.textFormatter!.call(rowData, index)),
- textAlign: column.textAlign,
- maxLines: 1,
- overflow: TextOverflow.ellipsis,
- style: isSelected
- ? selectedTextStyle.copyWith(
- fontSize: column.textFontSize,
- )
- : textStyle.copyWith(
- fontSize: column.textFontSize,
- ),
- ),
- )
- ],
- ),
-
-
-
-
-
-
-
- );
- final cell = buildCellBox(column, child);
- return Expanded(flex: column.actualFlex, child: cell);
- },
- ),
- );
- return Material(
- key: ValueKey("${index}_${rowData.hashCode}"),
- child: Ink(
- color: _currectSelected == index
- ? Theme.of(context).secondaryHeaderColor
- : index % 2 == 0
- ? Theme.of(context).scaffoldBackgroundColor
- : decoration.color ?? Colors.white,
- child: InkWell(
- mouseCursor: SystemMouseCursors.basic,
- onTap: () {
- setState(() {});
- _currectSelected = index;
- widget.onRowTap?.call(index);
- },
- hoverColor: Theme.of(context).secondaryHeaderColor,
- child: Container(
- decoration: decoration,
- child: Row(
- mainAxisSize: MainAxisSize.min,
- children: cells,
- ),
- ),
- ),
- ),
- );
- }
- Widget buildCellBox(TableColumn<T> column, Widget child) {
- if (!column.hasWidthLimit) return child;
- return Container(
- alignment: Alignment.center,
- width: column.width,
- constraints: BoxConstraints(
- maxWidth: column.maxWidth ?? double.infinity,
- minWidth: column.minWidth ?? 0.0,
- ),
- child: child,
- );
- }
- Widget buildCheckCell({
- required bool value,
- required ValueChanged<bool> onChanged,
- }) {
- return Container(
- width: widget.checkCellWidth,
- alignment: Alignment.center,
- child: Checkbox(
- activeColor: Theme.of(context).primaryColor,
- shape: RoundedRectangleBorder(
- borderRadius: BorderRadius.circular(4),
- ),
- side: const BorderSide(
- color: Colors.grey,
- width: 1,
- ),
- value: value,
- onChanged: (val) {
- onChanged(val == true);
- },
- ),
- );
- }
- }
- String _breakWord(String word) {
- if (word.isEmpty) return word;
- String breakWord = ' ';
- for (var element in word.runes) {
- breakWord += String.fromCharCode(element);
- breakWord += '\u200B';
- }
- return breakWord;
- }
|