@@ -127,9 +127,6 @@ class _ThemedTable2State<T> extends State<ThemedTable2<T>> {
127127 /// [_style] represents the standard text style for the cells
128128 TextStyle ? get _style => Theme .of (context).textTheme.bodyMedium;
129129
130- /// [_selected] is the index of the selected column for sorting
131- List <int > _selected = [];
132-
133130 /// [_sortIconSize] is the size of the sort icon
134131 double get _sortIconSize => 16 ;
135132
@@ -151,8 +148,7 @@ class _ThemedTable2State<T> extends State<ThemedTable2<T>> {
151148 /// [isReversed] indicates whether the current sort order is descending (true) or ascending (false).
152149 bool isReversed = false ;
153150
154- /// [_shouldShowActions] indicates whether action buttons should be shown based on selection state.
155- bool _shouldShowActions = false ;
151+ late ValueNotifier <List <T >> _selectedItems;
156152
157153 @override
158154 void initState () {
@@ -168,7 +164,9 @@ class _ThemedTable2State<T> extends State<ThemedTable2<T>> {
168164 _contentController = _verticalScrollControllerGroup.addAndGet ();
169165 _actionsController = _verticalScrollControllerGroup.addAndGet ();
170166
171- _filterAndSort ();
167+ _selectedItems = widget.multiselectValue ?? ValueNotifier <List <T >>([]);
168+
169+ _filterAndSort ('INIT_STATE' );
172170 }
173171
174172 @override
@@ -180,8 +178,8 @@ class _ThemedTable2State<T> extends State<ThemedTable2<T>> {
180178 oldWidget.actionsCount != widget.actionsCount ||
181179 oldWidget.canSearch != widget.canSearch ||
182180 kDebugMode) {
183- _filterAndSort ();
184- setState (() {});
181+ _filterAndSort ('DID_UPDATE' );
182+ WidgetsBinding .instance. addPostFrameCallback ((_) => setState (() {}) );
185183 }
186184 }
187185
@@ -283,17 +281,19 @@ class _ThemedTable2State<T> extends State<ThemedTable2<T>> {
283281 if (widget.hasMultiselect) ...[
284282 SizedBox (
285283 width: 50 ,
286- child: Checkbox (
287- value: _selected.length == widget.items.length && widget.items.isNotEmpty,
288- onChanged: (val) {
289- if (val == true ) {
290- _selected = List .generate (widget.items.length, (index) => index);
291- } else {
292- _selected = [];
293- }
294-
295- _shouldShowActions = _selected.isNotEmpty;
296- setState (() {});
284+ child: ValueListenableBuilder (
285+ valueListenable: _selectedItems,
286+ builder: (context, value, child) {
287+ return Checkbox (
288+ value: value.length == widget.items.length && widget.items.isNotEmpty,
289+ onChanged: (val) {
290+ if (val == true ) {
291+ _selectedItems.value = List <T >.from (widget.items);
292+ } else {
293+ _selectedItems.value = [];
294+ }
295+ },
296+ );
297297 },
298298 ),
299299 ),
@@ -327,7 +327,7 @@ class _ThemedTable2State<T> extends State<ThemedTable2<T>> {
327327 isReversed = false ;
328328 }
329329
330- _filterAndSort ();
330+ _filterAndSort ('SORT' );
331331 },
332332 child: Container (
333333 width: sizes[index]! - (index < widget.columns.length - 1 ? 1 : 0 ),
@@ -419,20 +419,25 @@ class _ThemedTable2State<T> extends State<ThemedTable2<T>> {
419419 itemExtent: 50 ,
420420 controller: _multiselectController,
421421 itemBuilder: (context, index) {
422+ final item = _filteredData[index];
422423 return Container (
423424 padding: _padding,
424425 color: index % 2 == 0 ? null : _stripColor,
425- child: Checkbox (
426- value: _selected.contains (index),
427- onChanged: (val) {
428- if (val == true ) {
429- if (! _selected.contains (index)) _selected.add (index);
430- } else {
431- if (_selected.contains (index)) _selected.remove (index);
432- }
433-
434- _shouldShowActions = _selected.isNotEmpty;
435- setState (() {});
426+ child: ValueListenableBuilder (
427+ valueListenable: _selectedItems,
428+ builder: (context, value, child) {
429+ return Checkbox (
430+ value: value.contains (item),
431+ onChanged: (val) {
432+ if (val == true ) {
433+ if (! value.contains (item)) _selectedItems.value = [...value, item];
434+ } else {
435+ if (value.contains (item)) {
436+ _selectedItems.value = value.where ((i) => i != item).toList ();
437+ }
438+ }
439+ },
440+ );
436441 },
437442 ),
438443 );
@@ -555,72 +560,75 @@ class _ThemedTable2State<T> extends State<ThemedTable2<T>> {
555560
556561 // /Content
557562 // Actions
558- if (_shouldShowActions) ...[
559- Container (
560- width: double .infinity,
561- margin: const EdgeInsets .all (5 ),
562- padding: const EdgeInsets .all (10 ),
563- decoration: BoxDecoration (
564- color: Theme .of (context).inputDecorationTheme.fillColor,
565- borderRadius: BorderRadius .circular (8 ),
566- ),
567- child: Column (
568- children: [
569- Text (
570- widget.multiSelectionTitleText,
571- style: Theme .of (context).textTheme.bodyLarge? .copyWith (fontWeight: FontWeight .bold),
572- maxLines: 1 ,
573- ),
574- Text (
575- widget.multiSelectionContentText,
576- maxLines: 1 ,
577- ),
578- const SizedBox (height: 10 ),
579- Center (
580- child: SingleChildScrollView (
581- child: Row (
582- spacing: 5 ,
583- mainAxisAlignment: MainAxisAlignment .center,
584- crossAxisAlignment: CrossAxisAlignment .center,
585- children: [
586- ThemedButton (
587- labelText: widget.multiSelectionCancelLabelText,
588- color: Colors .orange,
589- icon: LayrzIcons .solarOutlineEraser,
590- onTap: () {
591- _selected = [];
592- _shouldShowActions = false ;
593- setState (() {});
594- },
595- ),
596- ...widget.multiselectActions.map ((action) {
597- return ThemedButton (
598- labelText: action.labelText,
599- icon: action.icon,
600- color: action.color,
601- onTap: action.onTap,
602- isLoading: action.isLoading,
603- isCooldown: action.isCooldown,
604- );
605- }),
606- ],
563+ ValueListenableBuilder (
564+ valueListenable: _selectedItems,
565+ builder: (context, value, child) {
566+ if (value.isEmpty) return const SizedBox .shrink ();
567+
568+ return Container (
569+ width: double .infinity,
570+ margin: const EdgeInsets .all (5 ),
571+ padding: const EdgeInsets .all (10 ),
572+ decoration: BoxDecoration (
573+ color: Theme .of (context).inputDecorationTheme.fillColor,
574+ borderRadius: BorderRadius .circular (8 ),
575+ ),
576+ child: Column (
577+ children: [
578+ Text (
579+ widget.multiSelectionTitleText,
580+ style: Theme .of (context).textTheme.bodyLarge? .copyWith (fontWeight: FontWeight .bold),
581+ maxLines: 1 ,
582+ ),
583+ Text (
584+ widget.multiSelectionContentText,
585+ maxLines: 1 ,
586+ ),
587+ const SizedBox (height: 10 ),
588+ Center (
589+ child: SingleChildScrollView (
590+ child: Row (
591+ spacing: 5 ,
592+ mainAxisAlignment: MainAxisAlignment .center,
593+ crossAxisAlignment: CrossAxisAlignment .center,
594+ children: [
595+ ThemedButton (
596+ labelText: widget.multiSelectionCancelLabelText,
597+ color: Colors .orange,
598+ icon: LayrzIcons .solarOutlineEraser,
599+ onTap: () => _selectedItems.value = [],
600+ ),
601+ ...widget.multiselectActions.map ((action) {
602+ return ThemedButton (
603+ labelText: action.labelText,
604+ icon: action.icon,
605+ color: action.color,
606+ onTap: action.onTap,
607+ isLoading: action.isLoading,
608+ isCooldown: action.isCooldown,
609+ );
610+ }),
611+ ],
612+ ),
607613 ),
608614 ),
609- ) ,
610- ] ,
611- ),
612- ) ,
613- ] ,
615+ ] ,
616+ ) ,
617+ );
618+ } ,
619+ ) ,
614620 // /Actions
615621 ],
616622 );
617623 },
618624 );
619625 }
620626
621- void _filterAndSort () async {
627+ void _filterAndSort (String source ) async {
622628 _filteredData = widget.items;
623- debugPrint ("layrz_theme/ThemedTable2: Precomputing data..." );
629+ if (widget.items.isEmpty) return ;
630+
631+ debugPrint ("layrz_theme/ThemedTable2: Precomputing data from $source ..." );
624632 _itemsStrings = {};
625633 for (final item in widget.items) {
626634 int rowHashCode = item.hashCode;
@@ -658,7 +666,7 @@ class _ThemedTable2State<T> extends State<ThemedTable2<T>> {
658666 if (_debounce? .isActive ?? false ) _debounce! .cancel ();
659667 _debounce = Timer (const Duration (milliseconds: 300 ), () {
660668 _search = value;
661- _filterAndSort ();
669+ _filterAndSort ('SEARCH' );
662670 });
663671 WidgetsBinding .instance.addPostFrameCallback ((_) {
664672 if (mounted) setState (() {});
0 commit comments