Skip to content

Commit afdcd4e

Browse files
committed
Multiselection behavior changed to be more efficient
1 parent 582ab9d commit afdcd4e

4 files changed

Lines changed: 102 additions & 90 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Changelog
22

3+
## 7.4.0-prerelease6
4+
5+
- Changed behavior of multiselection on `ThemedTable2`, now will use internally also the `ValueNotifier` to handle the selected items.
6+
37
## 7.4.0-prerelease5
48

59
- Added external `ValueNotifier` support to `ThemedMapToolbar` to listen to zoom changes externally.

example/pubspec.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -366,7 +366,7 @@ packages:
366366
path: ".."
367367
relative: true
368368
source: path
369-
version: "7.4.0-prerelease4"
369+
version: "7.4.0-prerelease6"
370370
leak_tracker:
371371
dependency: transitive
372372
description:

lib/src/table2/src/table.dart

Lines changed: 96 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -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(() {});

pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name: layrz_theme
22
description: Layrz standard styling library for Flutter. Widget library following the Material Design 3 guidelines, with a focus on reliavility and functionality.
3-
version: "7.4.0-prerelease4"
3+
version: "7.4.0-prerelease6"
44
homepage: https://theme.layrz.com
55
repository: https://github.com/goldenm-software/layrz_theme
66

0 commit comments

Comments
 (0)