|
|
|
|
@ -27,10 +27,11 @@ class MultipleSelectionFullScreenDialog<T extends Base> extends StatefulWidget {
|
|
|
|
|
_SelectionBottomSheetState createState() => _SelectionBottomSheetState<T>();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class _SelectionBottomSheetState<T extends Base> extends State<MultipleSelectionFullScreenDialog<T>> {
|
|
|
|
|
class _SelectionBottomSheetState<T extends Base> extends State<MultipleSelectionFullScreenDialog<T>> with SingleTickerProviderStateMixin {
|
|
|
|
|
late List<T> _selectedValue; // Now nullable
|
|
|
|
|
|
|
|
|
|
String query = "";
|
|
|
|
|
late AnimationController _animationController;
|
|
|
|
|
|
|
|
|
|
List<T>? get filteredList => widget.items?.where((element) => widget.builderString(element).toLowerCase().contains(query.toLowerCase())).toList();
|
|
|
|
|
|
|
|
|
|
@ -40,12 +41,18 @@ class _SelectionBottomSheetState<T extends Base> extends State<MultipleSelection
|
|
|
|
|
// This ensures temporary selections don't affect parent until confirmed
|
|
|
|
|
_selectedValue = List<T>.from(widget.selectedItem);
|
|
|
|
|
super.initState();
|
|
|
|
|
_animationController = AnimationController(
|
|
|
|
|
duration: const Duration(milliseconds: 1200),
|
|
|
|
|
vsync: this,
|
|
|
|
|
);
|
|
|
|
|
_animationController.forward();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FocusNode searchFocusNode = FocusNode();
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
void dispose() {
|
|
|
|
|
_animationController.dispose();
|
|
|
|
|
searchFocusNode.dispose();
|
|
|
|
|
super.dispose();
|
|
|
|
|
}
|
|
|
|
|
@ -132,34 +139,67 @@ class _SelectionBottomSheetState<T extends Base> extends State<MultipleSelection
|
|
|
|
|
itemCount: filteredList?.length,
|
|
|
|
|
padding: EdgeInsets.zero,
|
|
|
|
|
itemBuilder: (cxt, index) {
|
|
|
|
|
// Calculate staggered animation delay
|
|
|
|
|
final delay = index * 0.08;
|
|
|
|
|
final animation = Tween<double>(begin: 0.0, end: 1.0).animate(
|
|
|
|
|
CurvedAnimation(
|
|
|
|
|
parent: _animationController,
|
|
|
|
|
curve: Interval(
|
|
|
|
|
delay.clamp(0.0, 0.8),
|
|
|
|
|
(delay + 0.3).clamp(0.0, 1.0),
|
|
|
|
|
curve: Curves.easeOut,
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
final slideAnimation = Tween<Offset>(
|
|
|
|
|
begin: const Offset(0.0, 0.3),
|
|
|
|
|
end: Offset.zero,
|
|
|
|
|
).animate(
|
|
|
|
|
CurvedAnimation(
|
|
|
|
|
parent: _animationController,
|
|
|
|
|
curve: Interval(
|
|
|
|
|
delay.clamp(0.0, 0.8),
|
|
|
|
|
(delay + 0.3).clamp(0.0, 1.0),
|
|
|
|
|
curve: Curves.easeOutCubic,
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
bool isDisabledItem = widget.disableItem != null && widget.disableItem?.identifier == filteredList![index].identifier;
|
|
|
|
|
return CheckboxListTile(
|
|
|
|
|
value: checkItContains(filteredList![index]),
|
|
|
|
|
dense: true,
|
|
|
|
|
controlAffinity: ListTileControlAffinity.leading,
|
|
|
|
|
activeColor: AppColor.primary10,
|
|
|
|
|
checkColor: AppColor.white10,
|
|
|
|
|
// fillColor: WidgetStateProperty.all(AppColor.background(context)),
|
|
|
|
|
contentPadding: const EdgeInsets.only(left: 16, right: 16),
|
|
|
|
|
// activeColor: AppColor.blueStatus(context),
|
|
|
|
|
side: BorderSide(color: AppColor.icon2Color(context).withValues(alpha: .3), width: 1.0),
|
|
|
|
|
onChanged: (value) {
|
|
|
|
|
// if (checkItContains(filteredList![index]) ?? false) {
|
|
|
|
|
// _selectedValue.remove(filteredList![index]);
|
|
|
|
|
// } else {
|
|
|
|
|
// _selectedValue.add(filteredList![index]);
|
|
|
|
|
// }
|
|
|
|
|
if (value == true) {
|
|
|
|
|
_selectedValue.add(filteredList![index]);
|
|
|
|
|
} else if (value == false) {
|
|
|
|
|
_selectedValue.remove(filteredList![index]);
|
|
|
|
|
}
|
|
|
|
|
searchFocusNode.unfocus();
|
|
|
|
|
setState(() {});
|
|
|
|
|
},
|
|
|
|
|
title: Text(
|
|
|
|
|
widget.builderString(filteredList![index]).cleanupWhitespace.capitalizeFirstOfEach ?? "",
|
|
|
|
|
style: Theme.of(context).textTheme.bodyLarge,
|
|
|
|
|
return FadeTransition(
|
|
|
|
|
opacity: animation,
|
|
|
|
|
child: SlideTransition(
|
|
|
|
|
position: slideAnimation,
|
|
|
|
|
child: CheckboxListTile(
|
|
|
|
|
value: checkItContains(filteredList![index]),
|
|
|
|
|
dense: true,
|
|
|
|
|
controlAffinity: ListTileControlAffinity.leading,
|
|
|
|
|
activeColor: AppColor.primary10,
|
|
|
|
|
checkColor: AppColor.white10,
|
|
|
|
|
// fillColor: WidgetStateProperty.all(AppColor.background(context)),
|
|
|
|
|
contentPadding: const EdgeInsets.only(left: 16, right: 16),
|
|
|
|
|
// activeColor: AppColor.blueStatus(context),
|
|
|
|
|
side: BorderSide(color: AppColor.icon2Color(context).withValues(alpha: .3), width: 1.0),
|
|
|
|
|
onChanged: (value) {
|
|
|
|
|
// if (checkItContains(filteredList![index]) ?? false) {
|
|
|
|
|
// _selectedValue.remove(filteredList![index]);
|
|
|
|
|
// } else {
|
|
|
|
|
// _selectedValue.add(filteredList![index]);
|
|
|
|
|
// }
|
|
|
|
|
if (value == true) {
|
|
|
|
|
_selectedValue.add(filteredList![index]);
|
|
|
|
|
} else if (value == false) {
|
|
|
|
|
_selectedValue.remove(filteredList![index]);
|
|
|
|
|
}
|
|
|
|
|
searchFocusNode.unfocus();
|
|
|
|
|
setState(() {});
|
|
|
|
|
},
|
|
|
|
|
title: Text(
|
|
|
|
|
widget.builderString(filteredList![index]).cleanupWhitespace.capitalizeFirstOfEach ?? "",
|
|
|
|
|
style: Theme.of(context).textTheme.bodyLarge,
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
}),
|
|
|
|
|
|