import 'package:flutter/material.dart'; /// Searchable multi-select dropdown with chips and 'Select All' option class SearchableMultiSelectDropdown extends StatefulWidget { const SearchableMultiSelectDropdown({ Key? key, required this.label, required this.items, required this.selectedIds, required this.getId, required this.getLabel, required this.onChanged, }) : super(key: key); final String label; final List items; final List selectedIds; final String Function(T) getId; final String Function(T) getLabel; final ValueChanged> onChanged; @override State> createState() => SearchableMultiSelectDropdownState(); } class SearchableMultiSelectDropdownState extends State> { late List _selectedIds; String _search = ''; bool _selectAll = false; @override void initState() { super.initState(); _selectedIds = List.from(widget.selectedIds); _selectAll = _selectedIds.length == widget.items.length && widget.items.isNotEmpty; } void _openDropdown() async { await showDialog( context: context, builder: (context) { return StatefulBuilder( builder: (context, setState) { final filtered = widget.items .where( (item) => widget .getLabel(item) .toLowerCase() .contains(_search.toLowerCase()), ) .toList(); return AlertDialog( title: Text('Select ${widget.label}'), content: Column( mainAxisSize: MainAxisSize.min, children: [ TextField( decoration: const InputDecoration(hintText: 'Search...'), onChanged: (v) => setState(() => _search = v), ), CheckboxListTile( value: _selectAll, title: const Text('Select All'), onChanged: (checked) { setState(() { _selectAll = checked ?? false; if (_selectAll) { _selectedIds = widget.items .map(widget.getId) .toList(); } else { _selectedIds.clear(); } }); }, ), SizedBox( height: 200, child: ListView( children: [ for (final item in filtered) CheckboxListTile( value: _selectedIds.contains(widget.getId(item)), title: Text(widget.getLabel(item)), onChanged: (checked) { setState(() { final id = widget.getId(item); if (checked == true) { _selectedIds.add(id); } else { _selectedIds.remove(id); } _selectAll = _selectedIds.length == widget.items.length && widget.items.isNotEmpty; }); }, ), ], ), ), ], ), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(), child: const Text('Cancel'), ), ElevatedButton( onPressed: () => Navigator.of(context).pop(_selectedIds), child: const Text('Done'), ), ], ); }, ); }, ).then((result) { if (result is List) { setState(() { _selectedIds = result; _selectAll = _selectedIds.length == widget.items.length && widget.items.isNotEmpty; }); widget.onChanged(_selectedIds); } }); } @override Widget build(BuildContext context) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ InputDecorator( decoration: InputDecoration(labelText: widget.label), child: Wrap( spacing: 8, runSpacing: 4, children: [ ..._selectedIds.map((id) { T item; try { item = widget.items.firstWhere((e) => widget.getId(e) == id); } catch (_) { return const SizedBox.shrink(); } return Chip( label: Text(widget.getLabel(item)), onDeleted: () { setState(() { _selectedIds.remove(id); _selectAll = _selectedIds.length == widget.items.length && widget.items.isNotEmpty; }); widget.onChanged(_selectedIds); }, ); }), ActionChip( label: Text('Select'), avatar: const Icon(Icons.arrow_drop_down), onPressed: _openDropdown, ), ], ), ), ], ); } }