You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			173 lines
		
	
	
		
			6.7 KiB
		
	
	
	
		
			Dart
		
	
			
		
		
	
	
			173 lines
		
	
	
		
			6.7 KiB
		
	
	
	
		
			Dart
		
	
| import 'package:flutter/material.dart';
 | |
| import 'package:test_sa/extensions/context_extension.dart';
 | |
| import 'package:test_sa/extensions/int_extensions.dart';
 | |
| import 'package:test_sa/extensions/text_extensions.dart';
 | |
| import 'package:test_sa/extensions/widget_extensions.dart';
 | |
| import 'package:test_sa/modules/cm_module/views/components/action_button/footer_action_button.dart';
 | |
| import 'package:test_sa/new_views/app_style/app_color.dart';
 | |
| import 'package:test_sa/new_views/common_widgets/app_filled_button.dart';
 | |
| 
 | |
| typedef SelectionBuilderString = String Function(dynamic);
 | |
| 
 | |
| class MultipleSelectionBottomSheet<T> extends StatefulWidget {
 | |
|   final List<T>? items;
 | |
|   final List<T> selectedItem; // Now nullable
 | |
|   final String title;
 | |
|   final SelectionBuilderString builderString;
 | |
|   final bool showCancel;
 | |
|   final Function(List<T>) onSelect;
 | |
| 
 | |
|   const MultipleSelectionBottomSheet({Key? key, this.items = const [], this.selectedItem = const [], this.title = "", required this.builderString, this.showCancel = false, required this.onSelect})
 | |
|       : super(key: key);
 | |
| 
 | |
|   @override
 | |
|   _MultipleSelectionBottomSheetState createState() => _MultipleSelectionBottomSheetState<T>();
 | |
| }
 | |
| 
 | |
| class _MultipleSelectionBottomSheetState<T> extends State<MultipleSelectionBottomSheet<T>> {
 | |
|   late List<T> _selectedValue; // Now nullable
 | |
| 
 | |
|   String query = "";
 | |
| 
 | |
|   List<T>? get filteredList => widget.items?.where((element) => widget.builderString(element).toLowerCase().contains(query.toLowerCase())).toList();
 | |
| 
 | |
|   @override
 | |
|   void initState() {
 | |
|     _selectedValue = widget.selectedItem;
 | |
|     super.initState();
 | |
|   }
 | |
| 
 | |
|   FocusNode searchFocusNode = FocusNode();
 | |
| 
 | |
|   @override
 | |
|   void dispose() {
 | |
|     searchFocusNode.dispose();
 | |
|     super.dispose();
 | |
|   }
 | |
| 
 | |
|   @override
 | |
|   Widget build(BuildContext context) {
 | |
|     return Container(
 | |
|       height: MediaQuery.of(context).size.height * .7,
 | |
|       color: Theme.of(context).scaffoldBackgroundColor,
 | |
|       child: Column(
 | |
|         crossAxisAlignment: CrossAxisAlignment.start,
 | |
|         children: [
 | |
|           Row(
 | |
|             children: [
 | |
|               widget.title.heading5(context).expanded,
 | |
|               if (widget.showCancel)
 | |
|                 AnimatedOpacity(
 | |
|                   opacity: _selectedValue != null ? 1 : 0,
 | |
|                   duration: const Duration(milliseconds: 250),
 | |
|                   child: Container(
 | |
|                     height: 30,
 | |
|                     decoration: BoxDecoration(color: const Color(0xffF63939).withOpacity(.75), borderRadius: BorderRadius.circular(30)),
 | |
|                     padding: const EdgeInsets.only(left: 8, right: 12, top: 4, bottom: 4),
 | |
|                     alignment: Alignment.center,
 | |
|                     child: Row(
 | |
|                       mainAxisSize: MainAxisSize.min,
 | |
|                       children: [
 | |
|                         const Icon(Icons.clear, color: Colors.white, size: 16),
 | |
|                         4.width,
 | |
|                         const Text(
 | |
|                           "Clear",
 | |
|                           style: TextStyle(fontSize: 14, color: Colors.white),
 | |
|                         )
 | |
|                       ],
 | |
|                     ),
 | |
|                   ).onPress(_selectedValue.isNotEmpty
 | |
|                       ? () {
 | |
|                           Navigator.pop(context);
 | |
|                           widget.onSelect([]);
 | |
|                         }
 | |
|                       : null),
 | |
|                 ),
 | |
|             ],
 | |
|           ).paddingOnly(top: 16, start: 16, end: 16, bottom: 0),
 | |
|           TextField(
 | |
|             onChanged: (queryString) {
 | |
|               query = queryString;
 | |
|               setState(() {});
 | |
|             },
 | |
|             style: const TextStyle(fontSize: 14),
 | |
|             focusNode: searchFocusNode,
 | |
|             decoration: InputDecoration(
 | |
|               hintText: 'Search by name',
 | |
|               labelText: 'Search',
 | |
|               labelStyle: TextStyle(color: AppColor.textColor(context)),
 | |
|               filled: true,
 | |
|               fillColor: AppColor.fieldBgColor(context),
 | |
|               hintStyle: const TextStyle(fontSize: 14),
 | |
|               focusedBorder: OutlineInputBorder(
 | |
|                 borderSide: BorderSide(color: AppColor.blueStatus(context), width: 2.0),
 | |
|                 borderRadius: const BorderRadius.all(Radius.circular(12.0)),
 | |
|               ),
 | |
|               enabledBorder: OutlineInputBorder(
 | |
|                 borderSide: BorderSide(color: AppColor.blueStatus(context), width: 1.0),
 | |
|                 borderRadius: const BorderRadius.all(Radius.circular(12.0)),
 | |
|               ),
 | |
|               contentPadding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0),
 | |
|             ),
 | |
|           ).paddingOnly(top: 16, start: 16, end: 16, bottom: 16),
 | |
|           Expanded(
 | |
|             // Wrap ListView with Expanded
 | |
|             child: ListView.builder(
 | |
|               itemCount: filteredList?.length,
 | |
|               padding: EdgeInsets.zero,
 | |
|               itemBuilder: (cxt, index) => Theme(
 | |
|                 data: Theme.of(context).copyWith(
 | |
|                   radioTheme: RadioThemeData(
 | |
|                     fillColor: MaterialStateColor.resolveWith((states) {
 | |
|                       if (states.contains(MaterialState.selected)) {
 | |
|                         return AppColor.textColor(context); // Active color
 | |
|                       }
 | |
|                       return Colors.grey; // Inactive color
 | |
|                     }),
 | |
|                   ),
 | |
|                 ),
 | |
|                 child: CheckboxListTile(
 | |
|                   value: checkItContains(filteredList![index]),
 | |
|                   dense: true,
 | |
|                   // groupValue: _selectedValue,
 | |
|                   activeColor: AppColor.textColor(context),
 | |
|                   contentPadding: const EdgeInsets.only(left: 16, right: 16),
 | |
|                   onChanged: (value) {
 | |
|                     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,
 | |
|                   ),
 | |
|                 ),
 | |
|               ),
 | |
|             ),
 | |
|           ),
 | |
|           8.height,
 | |
|           if (_selectedValue.isNotEmpty)
 | |
|             FooterActionButton.footerContainer(
 | |
|                 context: context,
 | |
|                 child: AppFilledButton(
 | |
|                   label: context.translation.select,
 | |
|                   maxWidth: true,
 | |
|                   onPressed: () {
 | |
|                     Navigator.pop(context);
 | |
|                     widget.onSelect(_selectedValue);
 | |
|                   },
 | |
|                 )),
 | |
|         ],
 | |
|       ),
 | |
|     );
 | |
|   }
 | |
| 
 | |
|   bool? checkItContains(T) {
 | |
|     return widget.items?.contains(T);
 | |
|   }
 | |
| }
 |