From 77b2b57ac0b6567aac577f57d723b4e19b8046e1 Mon Sep 17 00:00:00 2001 From: Unknown Date: Thu, 16 May 2019 22:48:46 +1000 Subject: [PATCH 1/5] Ability to add `Any` option --- lib/multiselect_dialog.dart | 44 ++++++++++++++++++++++++---------- lib/multiselect_formfield.dart | 40 ++++++++++++++++++++++++------- 2 files changed, 63 insertions(+), 21 deletions(-) diff --git a/lib/multiselect_dialog.dart b/lib/multiselect_dialog.dart index e371d27..f8e0aa5 100644 --- a/lib/multiselect_dialog.dart +++ b/lib/multiselect_dialog.dart @@ -1,9 +1,10 @@ import 'package:flutter/material.dart'; -class MultiSelectDialogItem { +class MultiSelectDialogItem { + static const ANY = 'any'; const MultiSelectDialogItem(this.value, this.label); - final V value; + final dynamic value; final String label; } @@ -12,8 +13,8 @@ class MultiSelectDialog extends StatefulWidget { {Key key, this.items, this.initialSelectedValues, this.title, this.okButtonLabel, this.cancelButtonLabel}) : super(key: key); - final List> items; - final List initialSelectedValues; + final List items; + final List initialSelectedValues; final String title; final String okButtonLabel; final String cancelButtonLabel; @@ -23,7 +24,7 @@ class MultiSelectDialog extends StatefulWidget { } class _MultiSelectDialogState extends State> { - final _selectedValues = List(); + final _selectedValues = List(); void initState() { super.initState(); @@ -32,13 +33,32 @@ class _MultiSelectDialogState extends State> { } } - void _onItemCheckedChange(V itemValue, bool checked) { + void _onItemCheckedChange(dynamic itemValue, bool checked) { + if (itemValue == MultiSelectDialogItem.ANY) { + this._setAnyOption(selected: true, reset: true); + } else { + setState(() { + if (checked) { + _selectedValues.add(itemValue); + } else { + _selectedValues.remove(itemValue); + } + // Set any options status + if (_selectedValues.length == 0) { + this._setAnyOption(selected: true); + } else { + this._setAnyOption(selected: false); + } + }); + } + } + + void _setAnyOption({selected = true, reset = false}) { setState(() { - if (checked) { - _selectedValues.add(itemValue); - } else { - _selectedValues.remove(itemValue); - } + if (reset) _selectedValues.clear(); + selected + ? _selectedValues.add(MultiSelectDialogItem.ANY) + : _selectedValues.remove(MultiSelectDialogItem.ANY); }); } @@ -76,7 +96,7 @@ class _MultiSelectDialogState extends State> { ); } - Widget _buildItem(MultiSelectDialogItem item) { + Widget _buildItem(MultiSelectDialogItem item) { final checked = _selectedValues.contains(item.value); return CheckboxListTile( value: checked, diff --git a/lib/multiselect_formfield.dart b/lib/multiselect_formfield.dart index a25bccf..7d8749d 100644 --- a/lib/multiselect_formfield.dart +++ b/lib/multiselect_formfield.dart @@ -19,6 +19,8 @@ class MultiSelectFormField extends FormField { final Widget trailing; final String okButtonLabel; final String cancelButtonLabel; + final bool addAnyOption; + final String anyLabel; MultiSelectFormField( {FormFieldSetter onSaved, @@ -39,22 +41,39 @@ class MultiSelectFormField extends FormField { this.close, this.okButtonLabel = 'OK', this.cancelButtonLabel = 'CANCEL', - this.trailing}) + this.trailing, + this.addAnyOption = false, + this.anyLabel = 'Any', : super( onSaved: onSaved, validator: validator, initialValue: initialValue, autovalidate: autovalidate, builder: (FormFieldState state) { + List _dataSource = List.from(dataSource ?? []); + if (addAnyOption) { + _dataSource.insert(0, { + valueField: MultiSelectDialogItem.ANY, + textField: anyLabel + }); + } + List _buildSelectedOptions(dynamic values, state) { List selectedOptions = []; if (values != null) { values.forEach((item) { - var existingItem = dataSource.singleWhere((itm) => itm[valueField] == item, orElse: () => null); - selectedOptions.add(Chip( - label: Text(existingItem[textField], overflow: TextOverflow.ellipsis), - )); + if (item != MultiSelectDialogItem.ANY) { + var existingItem = _dataSource.singleWhere((itm) => itm[valueField] == item, orElse: () => null); + selectedOptions.add(Chip( + label: Text(existingItem[textField], overflow: TextOverflow.ellipsis), + onDeleted: () { + final List value = state.value; + value.remove(existingItem[valueField]); + state.didChange(value); + }, + )); + } }); } @@ -67,9 +86,12 @@ class MultiSelectFormField extends FormField { if (initialSelected == null) { initialSelected = List(); } + if (addAnyOption && initialSelected.isEmpty) { + initialSelected.add(MultiSelectDialogItem.ANY); + } - final items = List>(); - dataSource.forEach((item) { + final items = List(); + _dataSource.forEach((item) { items.add(MultiSelectDialogItem(item[valueField], item[textField])); }); @@ -124,12 +146,12 @@ class MultiSelectFormField extends FormField { Icon( Icons.arrow_drop_down, color: Colors.black87, - size: 25.0, + size: 25.0, ), ], ), ), - value != null && value.length > 0 + value != null && value.length > 0 && value.indexOf(MultiSelectDialogItem.ANY) == -1 ? Wrap( spacing: 8.0, runSpacing: 0.0, From 2a515657322696917fa119907896c83921d32778 Mon Sep 17 00:00:00 2001 From: Unknown Date: Thu, 16 May 2019 22:54:57 +1000 Subject: [PATCH 2/5] Can specify custom `InputDecoration` --- lib/multiselect_formfield.dart | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/multiselect_formfield.dart b/lib/multiselect_formfield.dart index 7d8749d..a672497 100644 --- a/lib/multiselect_formfield.dart +++ b/lib/multiselect_formfield.dart @@ -21,6 +21,7 @@ class MultiSelectFormField extends FormField { final String cancelButtonLabel; final bool addAnyOption; final String anyLabel; + final InputDecoration decorator; MultiSelectFormField( {FormFieldSetter onSaved, @@ -44,6 +45,7 @@ class MultiSelectFormField extends FormField { this.trailing, this.addAnyOption = false, this.anyLabel = 'Any', + this.decorator = const InputDecoration(filled: true), : super( onSaved: onSaved, validator: validator, @@ -114,10 +116,9 @@ class MultiSelectFormField extends FormField { } }, child: InputDecorator( - decoration: InputDecoration( - filled: true, + decoration: decorator.copyWith( errorText: state.hasError ? state.errorText : null, - errorMaxLines: 4, + errorMaxLines: 4 ), isEmpty: state.value == null || state.value == '', child: Column( From a440cc2db6b844fe56331a43ca8608f0451eef8f Mon Sep 17 00:00:00 2001 From: Unknown Date: Thu, 16 May 2019 22:57:15 +1000 Subject: [PATCH 3/5] Ability to remove options without opening the dialog --- lib/multiselect_formfield.dart | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/lib/multiselect_formfield.dart b/lib/multiselect_formfield.dart index a672497..270dd51 100644 --- a/lib/multiselect_formfield.dart +++ b/lib/multiselect_formfield.dart @@ -22,6 +22,7 @@ class MultiSelectFormField extends FormField { final bool addAnyOption; final String anyLabel; final InputDecoration decorator; + final bool optionsDeletable; MultiSelectFormField( {FormFieldSetter onSaved, @@ -46,6 +47,7 @@ class MultiSelectFormField extends FormField { this.addAnyOption = false, this.anyLabel = 'Any', this.decorator = const InputDecoration(filled: true), + this.optionsDeletable = false}) : super( onSaved: onSaved, validator: validator, @@ -69,11 +71,12 @@ class MultiSelectFormField extends FormField { var existingItem = _dataSource.singleWhere((itm) => itm[valueField] == item, orElse: () => null); selectedOptions.add(Chip( label: Text(existingItem[textField], overflow: TextOverflow.ellipsis), - onDeleted: () { - final List value = state.value; - value.remove(existingItem[valueField]); - state.didChange(value); - }, + onDeleted: optionsDeletable + ? () { + state.value.remove(existingItem[valueField]); + state.didChange(value); + } + : null, )); } }); From fde90acd9ffc153ab0bed7a2f2071981f3d30682 Mon Sep 17 00:00:00 2001 From: Unknown Date: Sun, 27 Sep 2020 08:39:50 +1000 Subject: [PATCH 4/5] Typing changed to List --- lib/multiselect_dialog.dart | 26 ++++++++++---------------- lib/multiselect_formfield.dart | 31 ++++++++++++++----------------- 2 files changed, 24 insertions(+), 33 deletions(-) diff --git a/lib/multiselect_dialog.dart b/lib/multiselect_dialog.dart index 0acaa21..d6363d4 100644 --- a/lib/multiselect_dialog.dart +++ b/lib/multiselect_dialog.dart @@ -10,7 +10,7 @@ class MultiSelectDialogItem { class MultiSelectDialog extends StatefulWidget { final List items; - final List initialSelectedValues; + final List initialSelectedValues; final Widget title; final String okButtonLabel; final String cancelButtonLabel; @@ -18,6 +18,7 @@ class MultiSelectDialog extends StatefulWidget { final ShapeBorder dialogShapeBorder; final Color checkBoxCheckColor; final Color checkBoxActiveColor; + final bool addAnyOption; MultiSelectDialog( {Key key, @@ -29,7 +30,8 @@ class MultiSelectDialog extends StatefulWidget { this.labelStyle = const TextStyle(), this.dialogShapeBorder, this.checkBoxActiveColor, - this.checkBoxCheckColor}) + this.checkBoxCheckColor, + this.addAnyOption}) : super(key: key); @override @@ -37,7 +39,7 @@ class MultiSelectDialog extends StatefulWidget { } class _MultiSelectDialogState extends State> { - final _selectedValues = List(); + final List _selectedValues = List(); void initState() { super.initState(); @@ -48,7 +50,7 @@ class _MultiSelectDialogState extends State> { void _onItemCheckedChange(dynamic itemValue, bool checked) { if (itemValue == MultiSelectDialogItem.ANY) { - this._setAnyOption(selected: true, reset: true); + this._resetOptions(); } else { setState(() { if (checked) { @@ -56,22 +58,13 @@ class _MultiSelectDialogState extends State> { } else { _selectedValues.remove(itemValue); } - // Set any options status - if (_selectedValues.length == 0) { - this._setAnyOption(selected: true); - } else { - this._setAnyOption(selected: false); - } }); } } - void _setAnyOption({selected = true, reset = false}) { + void _resetOptions({selected = true, reset = false}) { setState(() { - if (reset) _selectedValues.clear(); - selected - ? _selectedValues.add(MultiSelectDialogItem.ANY) - : _selectedValues.remove(MultiSelectDialogItem.ANY); + _selectedValues.clear(); }); } @@ -111,7 +104,8 @@ class _MultiSelectDialogState extends State> { } Widget _buildItem(MultiSelectDialogItem item) { - final checked = _selectedValues.contains(item.value); + bool checked = _selectedValues.contains(item.value); + if (item.value == MultiSelectDialogItem.ANY && _selectedValues.isEmpty) checked = true; return CheckboxListTile( value: checked, checkColor: widget.checkBoxCheckColor, diff --git a/lib/multiselect_formfield.dart b/lib/multiselect_formfield.dart index 35e5c8f..baadf05 100644 --- a/lib/multiselect_formfield.dart +++ b/lib/multiselect_formfield.dart @@ -3,12 +3,12 @@ library multiselect_formfield; import 'package:flutter/material.dart'; import 'package:multiselect_formfield/multiselect_dialog.dart'; -class MultiSelectFormField extends FormField { +class MultiSelectFormField extends FormField> { final Widget title; final Widget hintWidget; final bool required; final String errorText; - final dynamic value; + final List value; final List dataSource; final String textField; final String valueField; @@ -33,9 +33,9 @@ class MultiSelectFormField extends FormField { final Color checkBoxActiveColor; MultiSelectFormField( - {FormFieldSetter onSaved, - FormFieldValidator validator, - dynamic initialValue, + {FormFieldSetter> onSaved, + FormFieldValidator> validator, + List initialValue, bool autovalidate = false, this.value, this.title = const Text('Title'), @@ -71,9 +71,9 @@ class MultiSelectFormField extends FormField { validator: validator, initialValue: initialValue, autovalidate: autovalidate, - builder: (FormFieldState state) { + builder: (FormFieldState> state) { List _dataSource = List.from(dataSource ?? []); - dynamic _value = state.value ?? initialValue; + List _value = state.value ?? initialValue; if (addAnyOption) { _dataSource.insert(0, { @@ -82,7 +82,7 @@ class MultiSelectFormField extends FormField { }); } - List _buildSelectedOptions(dynamic values, state) { + List _buildSelectedOptions(List values, state) { List selectedOptions = []; if (values != null) { @@ -107,12 +107,9 @@ class MultiSelectFormField extends FormField { return InkWell( onTap: () async { - List initialSelected = state.value; + List initialSelected = state.value; if (initialSelected == null) { - initialSelected = List(); - } - if (addAnyOption && initialSelected.isEmpty) { - initialSelected.add(MultiSelectDialogItem.ANY); + initialSelected = List(); } final items = List(); @@ -123,7 +120,7 @@ class MultiSelectFormField extends FormField { List selectedValues = await showDialog( context: state.context, builder: (BuildContext context) { - return MultiSelectDialog( + return MultiSelectDialog( title: title, okButtonLabel: okButtonLabel, cancelButtonLabel: cancelButtonLabel, @@ -133,10 +130,10 @@ class MultiSelectFormField extends FormField { dialogShapeBorder: dialogShapeBorder, checkBoxActiveColor: checkBoxActiveColor, checkBoxCheckColor: checkBoxCheckColor, + addAnyOption: addAnyOption ); }, ); - if (selectedValues != null) { state.didChange(selectedValues); state.save(); @@ -149,7 +146,7 @@ class MultiSelectFormField extends FormField { fillColor: fillColor ?? Theme.of(state.context).canvasColor, border: border ?? UnderlineInputBorder(), ), - isEmpty: _value == null || _value == '', + isEmpty: _value == null, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -181,7 +178,7 @@ class MultiSelectFormField extends FormField { ], ), ), - _value != null && _value.length > 0 && _value.indexOf(MultiSelectDialogItem.ANY) == -1 + _value != null && _value.length > 0 ? Wrap( spacing: 8.0, runSpacing: 0.0, From 37200e7bf34ffa945037486b7198ecade6fab64b Mon Sep 17 00:00:00 2001 From: Unknown Date: Sun, 27 Sep 2020 09:07:58 +1000 Subject: [PATCH 5/5] Example updated with "Any" option implementation --- example/lib/main.dart | 113 ++++++++++++++++++++++++++++++------------ 1 file changed, 82 insertions(+), 31 deletions(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index 90ef32b..16d0966 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -19,14 +19,48 @@ class MyHomePage extends StatefulWidget { class _MyHomePageState extends State { List _myActivities; + List _myActivitiesWithAny; String _myActivitiesResult; + String _myActivitiesWithAnyResult; + List dataSource = [ + { + "display": "Running", + "value": "Running", + }, + { + "display": "Climbing", + "value": "Climbing", + }, + { + "display": "Walking", + "value": "Walking", + }, + { + "display": "Swimming", + "value": "Swimming", + }, + { + "display": "Soccer Practice", + "value": "Soccer Practice", + }, + { + "display": "Baseball Practice", + "value": "Baseball Practice", + }, + { + "display": "Football Practice", + "value": "Football Practice", + }, + ]; final formKey = new GlobalKey(); @override void initState() { super.initState(); _myActivities = []; + _myActivitiesWithAny = ["Running", "Swimming"]; _myActivitiesResult = ''; + _myActivitiesWithAnyResult = ''; } _saveForm() { @@ -35,6 +69,7 @@ class _MyHomePageState extends State { form.save(); setState(() { _myActivitiesResult = _myActivities.toString(); + _myActivitiesWithAnyResult = _myActivitiesWithAny.toString(); }); } } @@ -72,42 +107,14 @@ class _MyHomePageState extends State { } return null; }, - dataSource: [ - { - "display": "Running", - "value": "Running", - }, - { - "display": "Climbing", - "value": "Climbing", - }, - { - "display": "Walking", - "value": "Walking", - }, - { - "display": "Swimming", - "value": "Swimming", - }, - { - "display": "Soccer Practice", - "value": "Soccer Practice", - }, - { - "display": "Baseball Practice", - "value": "Baseball Practice", - }, - { - "display": "Football Practice", - "value": "Football Practice", - }, - ], + dataSource: dataSource, textField: 'display', valueField: 'value', okButtonLabel: 'OK', cancelButtonLabel: 'CANCEL', hintWidget: Text('Please choose one or more'), initialValue: _myActivities, + value: _myActivities, onSaved: (value) { if (value == null) return; setState(() { @@ -116,6 +123,45 @@ class _MyHomePageState extends State { }, ), ), + Container( + padding: EdgeInsets.all(16), + child: MultiSelectFormField( + autovalidate: false, + chipBackGroundColor: Colors.red, + chipLabelStyle: TextStyle(fontWeight: FontWeight.bold), + dialogTextStyle: TextStyle(fontWeight: FontWeight.bold), + checkBoxActiveColor: Colors.red, + checkBoxCheckColor: Colors.green, + dialogShapeBorder: RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(12.0))), + title: Text( + "My workouts with Any", + style: TextStyle(fontSize: 16), + ), + validator: (value) { + if (value == null || value.length == 0) { + return 'Please select one or more options'; + } + return null; + }, + dataSource: dataSource, + textField: 'display', + valueField: 'value', + okButtonLabel: 'OK', + cancelButtonLabel: 'CANCEL', + hintWidget: Text('Please choose one or more'), + initialValue: _myActivitiesWithAny, + value: _myActivitiesWithAny, + addAnyOption: true, + optionsDeletable: true, + onSaved: (value) { + if (value == null) return; + setState(() { + _myActivitiesWithAny = value; + }); + }, + ), + ), Container( padding: EdgeInsets.all(8), child: RaisedButton( @@ -125,7 +171,12 @@ class _MyHomePageState extends State { ), Container( padding: EdgeInsets.all(16), - child: Text(_myActivitiesResult), + child: Column( + children: [ + Text(_myActivitiesResult), + Text(_myActivitiesWithAnyResult), + ], + ), ) ], ),