From 67fdc8a632186b657bc7ec35dcb6dfa194ad6e71 Mon Sep 17 00:00:00 2001 From: Jan-Niclas de Vries Date: Tue, 25 May 2021 15:07:03 +0200 Subject: [PATCH 01/10] push hacky fix --- example/lib/main.dart | 5 ++- example/pubspec.lock | 6 +-- lib/multiselect_formfield.dart | 75 ++++++++++++++++++---------------- pubspec.lock | 6 +-- 4 files changed, 49 insertions(+), 43 deletions(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index 59a7c21..a11dd84 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -54,9 +54,10 @@ class _MyHomePageState extends State { Container( padding: EdgeInsets.all(16), child: MultiSelectFormField( - autovalidate: false, + autovalidate: AutovalidateMode.disabled, chipBackGroundColor: Colors.blue, - chipLabelStyle: TextStyle(fontWeight: FontWeight.bold, color: Colors.white), + chipLabelStyle: TextStyle( + fontWeight: FontWeight.bold, color: Colors.white), dialogTextStyle: TextStyle(fontWeight: FontWeight.bold), checkBoxActiveColor: Colors.blue, checkBoxCheckColor: Colors.white, diff --git a/example/pubspec.lock b/example/pubspec.lock index 1202879..fafaf8e 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -7,7 +7,7 @@ packages: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.5.0" + version: "2.6.1" boolean_selector: dependency: transitive description: @@ -141,7 +141,7 @@ packages: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.2.19" + version: "0.3.0" typed_data: dependency: transitive description: @@ -157,4 +157,4 @@ packages: source: hosted version: "2.1.0" sdks: - dart: ">=2.12.0-0.0 <3.0.0" + dart: ">=2.12.0 <3.0.0" diff --git a/lib/multiselect_formfield.dart b/lib/multiselect_formfield.dart index 7f2c823..a5e8882 100644 --- a/lib/multiselect_formfield.dart +++ b/lib/multiselect_formfield.dart @@ -32,7 +32,7 @@ class MultiSelectFormField extends FormField { FormFieldSetter? onSaved, FormFieldValidator? validator, dynamic initialValue, - bool autovalidate = false, + AutovalidateMode? autovalidate = AutovalidateMode.disabled, this.title = const Text('Title'), this.hintWidget = const Text('Tap to select one or more'), this.required = false, @@ -62,15 +62,19 @@ class MultiSelectFormField extends FormField { onSaved: onSaved, validator: validator, initialValue: initialValue, - autovalidate: autovalidate, + autovalidateMode: autovalidate, builder: (FormFieldState state) { List _buildSelectedOptions(state) { List selectedOptions = []; if (state.value != null) { state.value.forEach((item) { - var existingItem = dataSource!.singleWhere(((itm) => itm[valueField] == item), - orElse: () => null); + var existingItem; + try { + existingItem = dataSource!.singleWhere( + ((itm) => itm[valueField] == item), + ); + } catch (e) {} selectedOptions.add(Chip( labelStyle: chipLabelStyle, backgroundColor: chipBackGroundColor, @@ -86,41 +90,42 @@ class MultiSelectFormField extends FormField { } return InkWell( + onTap: !enabled + ? null + : () async { + List? initialSelected = state.value; + if (initialSelected == null) { + initialSelected = []; + } - onTap: !enabled ? null :() async { - List? initialSelected = state.value; - if (initialSelected == null) { - initialSelected = []; - } - - final items = >[]; + final items = >[]; dataSource!.forEach((item) { - items.add( - MultiSelectDialogItem(item[valueField], item[textField])); - }); + items.add(MultiSelectDialogItem( + item[valueField], item[textField])); + }); - List? selectedValues = await showDialog( - context: state.context, - builder: (BuildContext context) { - return MultiSelectDialog( - title: title, - okButtonLabel: okButtonLabel, - cancelButtonLabel: cancelButtonLabel, - items: items, - initialSelectedValues: initialSelected, - labelStyle: dialogTextStyle, - dialogShapeBorder: dialogShapeBorder, - checkBoxActiveColor: checkBoxActiveColor, - checkBoxCheckColor: checkBoxCheckColor, - ); - }, - ); + List? selectedValues = await showDialog( + context: state.context, + builder: (BuildContext context) { + return MultiSelectDialog( + title: title, + okButtonLabel: okButtonLabel, + cancelButtonLabel: cancelButtonLabel, + items: items, + initialSelectedValues: initialSelected, + labelStyle: dialogTextStyle, + dialogShapeBorder: dialogShapeBorder, + checkBoxActiveColor: checkBoxActiveColor, + checkBoxCheckColor: checkBoxCheckColor, + ); + }, + ); - if (selectedValues != null) { - state.didChange(selectedValues); - state.save(); - } - }, + if (selectedValues != null) { + state.didChange(selectedValues); + state.save(); + } + }, child: InputDecorator( decoration: InputDecoration( filled: true, diff --git a/pubspec.lock b/pubspec.lock index 9e492de..ad6d1df 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -7,7 +7,7 @@ packages: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.5.0" + version: "2.6.1" boolean_selector: dependency: transitive description: @@ -127,7 +127,7 @@ packages: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.2.19" + version: "0.3.0" typed_data: dependency: transitive description: @@ -143,4 +143,4 @@ packages: source: hosted version: "2.1.0" sdks: - dart: ">=2.12.0-0.0 <3.0.0" + dart: ">=2.12.0 <3.0.0" From faf6843fe1cbb34f1351d0b69b673b01e707c642 Mon Sep 17 00:00:00 2001 From: Jan-Niclas de Vries Date: Wed, 14 Jul 2021 08:33:00 +0200 Subject: [PATCH 02/10] make generic --- example/lib/main.dart | 41 ++---- lib/multiselect_formfield.dart | 187 +------------------------- lib/{ => src}/multiselect_dialog.dart | 4 +- lib/src/multiselect_formfield.dart | 187 ++++++++++++++++++++++++++ lib/src/pair.dart | 11 ++ 5 files changed, 212 insertions(+), 218 deletions(-) rename lib/{ => src}/multiselect_dialog.dart (96%) create mode 100644 lib/src/multiselect_formfield.dart create mode 100644 lib/src/pair.dart diff --git a/example/lib/main.dart b/example/lib/main.dart index a11dd84..070026b 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -18,7 +18,7 @@ class MyHomePage extends StatefulWidget { } class _MyHomePageState extends State { - List? _myActivities; + List? _myActivities; late String _myActivitiesResult; final formKey = new GlobalKey(); @@ -53,7 +53,7 @@ class _MyHomePageState extends State { children: [ Container( padding: EdgeInsets.all(16), - child: MultiSelectFormField( + child: MultiSelectFormField( autovalidate: AutovalidateMode.disabled, chipBackGroundColor: Colors.blue, chipLabelStyle: TextStyle( @@ -74,34 +74,13 @@ 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", - }, + Pair.from("Running"), + Pair.from("Climbing"), + Pair.from("Walking"), + Pair.from("Swimming"), + Pair.from("Soccer Practice"), + Pair.from("Baseball Practice"), + Pair.from("Football Practice"), ], textField: 'display', valueField: 'value', @@ -112,7 +91,7 @@ class _MyHomePageState extends State { onSaved: (value) { if (value == null) return; setState(() { - _myActivities = value; + _myActivities = value.toList(); }); }, ), diff --git a/lib/multiselect_formfield.dart b/lib/multiselect_formfield.dart index a5e8882..2fec0c1 100644 --- a/lib/multiselect_formfield.dart +++ b/lib/multiselect_formfield.dart @@ -1,185 +1,2 @@ -library multiselect_formfield; - -import 'package:flutter/material.dart'; -import 'package:multiselect_formfield/multiselect_dialog.dart'; - -class MultiSelectFormField extends FormField { - final Widget title; - final Widget hintWidget; - final bool required; - final String errorText; - final List? dataSource; - final String? textField; - final String? valueField; - final Function? change; - final Function? open; - final Function? close; - final Widget? leading; - final Widget? trailing; - final String okButtonLabel; - final String cancelButtonLabel; - final Color? fillColor; - final InputBorder? border; - final TextStyle? chipLabelStyle; - final Color? chipBackGroundColor; - final TextStyle dialogTextStyle; - final ShapeBorder dialogShapeBorder; - final Color? checkBoxCheckColor; - final Color? checkBoxActiveColor; - final bool enabled; - - MultiSelectFormField({ - FormFieldSetter? onSaved, - FormFieldValidator? validator, - dynamic initialValue, - AutovalidateMode? autovalidate = AutovalidateMode.disabled, - this.title = const Text('Title'), - this.hintWidget = const Text('Tap to select one or more'), - this.required = false, - this.errorText = 'Please select one or more options', - this.leading, - this.dataSource, - this.textField, - this.valueField, - this.change, - this.open, - this.close, - this.okButtonLabel = 'OK', - this.cancelButtonLabel = 'CANCEL', - this.fillColor, - this.border, - this.trailing, - this.chipLabelStyle, - this.enabled = true, - this.chipBackGroundColor, - this.dialogTextStyle = const TextStyle(), - this.dialogShapeBorder = const RoundedRectangleBorder( - borderRadius: BorderRadius.all(Radius.circular(0.0)), - ), - this.checkBoxActiveColor, - this.checkBoxCheckColor, - }) : super( - onSaved: onSaved, - validator: validator, - initialValue: initialValue, - autovalidateMode: autovalidate, - builder: (FormFieldState state) { - List _buildSelectedOptions(state) { - List selectedOptions = []; - - if (state.value != null) { - state.value.forEach((item) { - var existingItem; - try { - existingItem = dataSource!.singleWhere( - ((itm) => itm[valueField] == item), - ); - } catch (e) {} - selectedOptions.add(Chip( - labelStyle: chipLabelStyle, - backgroundColor: chipBackGroundColor, - label: Text( - existingItem[textField], - overflow: TextOverflow.ellipsis, - ), - )); - }); - } - - return selectedOptions; - } - - return InkWell( - onTap: !enabled - ? null - : () async { - List? initialSelected = state.value; - if (initialSelected == null) { - initialSelected = []; - } - - final items = >[]; - dataSource!.forEach((item) { - items.add(MultiSelectDialogItem( - item[valueField], item[textField])); - }); - - List? selectedValues = await showDialog( - context: state.context, - builder: (BuildContext context) { - return MultiSelectDialog( - title: title, - okButtonLabel: okButtonLabel, - cancelButtonLabel: cancelButtonLabel, - items: items, - initialSelectedValues: initialSelected, - labelStyle: dialogTextStyle, - dialogShapeBorder: dialogShapeBorder, - checkBoxActiveColor: checkBoxActiveColor, - checkBoxCheckColor: checkBoxCheckColor, - ); - }, - ); - - if (selectedValues != null) { - state.didChange(selectedValues); - state.save(); - } - }, - child: InputDecorator( - decoration: InputDecoration( - filled: true, - errorText: state.hasError ? state.errorText : null, - errorMaxLines: 4, - fillColor: fillColor ?? Theme.of(state.context).canvasColor, - border: border ?? UnderlineInputBorder(), - ), - isEmpty: state.value == null || state.value == '', - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: EdgeInsets.fromLTRB(0, 2, 0, 0), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded( - child: title, - ), - required - ? Padding( - padding: EdgeInsets.only(top: 5, right: 5), - child: Text( - ' *', - style: TextStyle( - color: Colors.red.shade700, - fontSize: 17.0, - ), - ), - ) - : Container(), - Icon( - Icons.arrow_drop_down, - color: Colors.black87, - size: 25.0, - ), - ], - ), - ), - state.value != null && state.value.length > 0 - ? Wrap( - spacing: 8.0, - runSpacing: 0.0, - children: _buildSelectedOptions(state), - ) - : new Container( - padding: EdgeInsets.only(top: 4), - child: hintWidget, - ) - ], - ), - ), - ); - }, - ); -} +export 'package:multiselect_formfield/src/multiselect_formfield.dart'; +export 'package:multiselect_formfield/src/pair.dart'; diff --git a/lib/multiselect_dialog.dart b/lib/src/multiselect_dialog.dart similarity index 96% rename from lib/multiselect_dialog.dart rename to lib/src/multiselect_dialog.dart index 347cdea..89b0533 100644 --- a/lib/multiselect_dialog.dart +++ b/lib/src/multiselect_dialog.dart @@ -8,8 +8,8 @@ class MultiSelectDialogItem { } class MultiSelectDialog extends StatefulWidget { - final List>? items; - final List? initialSelectedValues; + final Iterable>? items; + final Iterable? initialSelectedValues; final Widget? title; final String? okButtonLabel; final String? cancelButtonLabel; diff --git a/lib/src/multiselect_formfield.dart b/lib/src/multiselect_formfield.dart new file mode 100644 index 0000000..4de4d68 --- /dev/null +++ b/lib/src/multiselect_formfield.dart @@ -0,0 +1,187 @@ +library multiselect_formfield; + +import 'package:flutter/material.dart'; +import 'package:multiselect_formfield/src/multiselect_dialog.dart'; +import 'package:multiselect_formfield/src/pair.dart'; + +class MultiSelectFormField extends FormField> { + final Widget title; + final Widget hintWidget; + final bool required; + final String errorText; + final Iterable>? dataSource; + final String? textField; + final T? valueField; + final Function? change; + final Function? open; + final Function? close; + final Widget? leading; + final Widget? trailing; + final String okButtonLabel; + final String cancelButtonLabel; + final Color? fillColor; + final InputBorder? border; + final TextStyle? chipLabelStyle; + final Color? chipBackGroundColor; + final TextStyle dialogTextStyle; + final ShapeBorder dialogShapeBorder; + final Color? checkBoxCheckColor; + final Color? checkBoxActiveColor; + final bool enabled; + + MultiSelectFormField({ + FormFieldSetter>? onSaved, + FormFieldValidator>? validator, + Iterable? initialValue, + AutovalidateMode? autovalidate = AutovalidateMode.disabled, + this.title = const Text('Title'), + this.hintWidget = const Text('Tap to select one or more'), + this.required = false, + this.errorText = 'Please select one or more options', + this.leading, + this.dataSource, + this.textField, + this.valueField, + this.change, + this.open, + this.close, + this.okButtonLabel = 'OK', + this.cancelButtonLabel = 'CANCEL', + this.fillColor, + this.border, + this.trailing, + this.chipLabelStyle, + this.enabled = true, + this.chipBackGroundColor, + this.dialogTextStyle = const TextStyle(), + this.dialogShapeBorder = const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(0.0)), + ), + this.checkBoxActiveColor, + this.checkBoxCheckColor, + }) : super( + onSaved: onSaved, + validator: validator, + initialValue: initialValue, + autovalidateMode: autovalidate, + builder: (FormFieldState> state) { + List _buildSelectedOptions(state) { + List selectedOptions = []; + + if (state.value != null) { + state.value.forEach((item) { + Pair? existingItem; + try { + existingItem = dataSource!.singleWhere( + ((itm) => itm.value == item), + ); + } catch (e) {} + selectedOptions.add(Chip( + labelStyle: chipLabelStyle, + backgroundColor: chipBackGroundColor, + label: Text( + existingItem?.label ?? "", + overflow: TextOverflow.ellipsis, + ), + )); + }); + } + + return selectedOptions; + } + + return InkWell( + onTap: !enabled + ? null + : () async { + Iterable? initialSelected = state.value; + if (initialSelected == null) { + initialSelected = List.empty(growable: true); + } + + final items = >[]; + dataSource!.forEach((item) { + items + .add(MultiSelectDialogItem(item.value, item.label)); + }); + + Iterable? selectedValues = + await showDialog?>( + context: state.context, + builder: (BuildContext context) { + return MultiSelectDialog( + title: title, + okButtonLabel: okButtonLabel, + cancelButtonLabel: cancelButtonLabel, + items: items, + initialSelectedValues: initialSelected, + labelStyle: dialogTextStyle, + dialogShapeBorder: dialogShapeBorder, + checkBoxActiveColor: checkBoxActiveColor, + checkBoxCheckColor: checkBoxCheckColor, + ); + }, + ); + + if (selectedValues != null) { + state.didChange(selectedValues); + state.save(); + } + }, + child: InputDecorator( + decoration: InputDecoration( + filled: true, + errorText: state.hasError ? state.errorText : null, + errorMaxLines: 4, + fillColor: fillColor ?? Theme.of(state.context).canvasColor, + border: border ?? UnderlineInputBorder(), + ), + isEmpty: state.value == null || state.value == '', + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsets.fromLTRB(0, 2, 0, 0), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: title, + ), + required + ? Padding( + padding: EdgeInsets.only(top: 5, right: 5), + child: Text( + ' *', + style: TextStyle( + color: Colors.red.shade700, + fontSize: 17.0, + ), + ), + ) + : Container(), + Icon( + Icons.arrow_drop_down, + color: Colors.black87, + size: 25.0, + ), + ], + ), + ), + state.value != null && state.value!.length > 0 + ? Wrap( + spacing: 8.0, + runSpacing: 0.0, + children: _buildSelectedOptions(state), + ) + : new Container( + padding: EdgeInsets.only(top: 4), + child: hintWidget, + ) + ], + ), + ), + ); + }, + ); +} diff --git a/lib/src/pair.dart b/lib/src/pair.dart new file mode 100644 index 0000000..c654c0d --- /dev/null +++ b/lib/src/pair.dart @@ -0,0 +1,11 @@ +class Pair { + final String label; + final T value; + + Pair({ + required this.label, + required this.value, + }); + + static Pair from(String label) => Pair(label: label, value: label); +} From a38ece82abb5bf21402438d4c7f21f92854e17e7 Mon Sep 17 00:00:00 2001 From: Jan-Niclas de Vries Date: Wed, 14 Jul 2021 08:33:26 +0200 Subject: [PATCH 03/10] do not publish example --- example/pubspec.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 32dbe1f..58233fb 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -1,6 +1,7 @@ name: example description: Multi select form field example. version: 1.0.0+1 +publish_to: none environment: sdk: ">=2.12.0-0 <3.0.0" From 2b42fdb216ffff79f667e18de8261547e42b3a55 Mon Sep 17 00:00:00 2001 From: Jan-Niclas de Vries Date: Wed, 14 Jul 2021 09:39:14 +0200 Subject: [PATCH 04/10] extend example --- example/lib/main.dart | 57 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 52 insertions(+), 5 deletions(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index 070026b..702d97c 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -19,22 +19,27 @@ class MyHomePage extends StatefulWidget { class _MyHomePageState extends State { List? _myActivities; + List? _myInts; late String _myActivitiesResult; + late String _myIntsResult; final formKey = new GlobalKey(); @override void initState() { super.initState(); _myActivities = []; + _myInts = []; _myActivitiesResult = ''; + _myIntsResult = ''; } _saveForm() { - var form = formKey.currentState!; + final form = formKey.currentState!; if (form.validate()) { form.save(); setState(() { _myActivitiesResult = _myActivities.toString(); + _myIntsResult = _myInts.toString(); }); } } @@ -55,7 +60,7 @@ class _MyHomePageState extends State { padding: EdgeInsets.all(16), child: MultiSelectFormField( autovalidate: AutovalidateMode.disabled, - chipBackGroundColor: Colors.blue, + chipBackgroundColor: Colors.blue, chipLabelStyle: TextStyle( fontWeight: FontWeight.bold, color: Colors.white), dialogTextStyle: TextStyle(fontWeight: FontWeight.bold), @@ -82,8 +87,6 @@ class _MyHomePageState extends State { Pair.from("Baseball Practice"), Pair.from("Football Practice"), ], - textField: 'display', - valueField: 'value', okButtonLabel: 'OK', cancelButtonLabel: 'CANCEL', hintWidget: Text('Please choose one or more'), @@ -96,6 +99,50 @@ class _MyHomePageState extends State { }, ), ), + Container( + padding: EdgeInsets.all(16), + child: Text(_myActivitiesResult), + ), + Container( + padding: EdgeInsets.all(16), + child: MultiSelectFormField( + autovalidate: AutovalidateMode.disabled, + chipBackgroundColor: Colors.blue, + chipLabelStyle: TextStyle( + fontWeight: FontWeight.bold, color: Colors.white), + dialogTextStyle: TextStyle(fontWeight: FontWeight.bold), + checkBoxActiveColor: Colors.blue, + checkBoxCheckColor: Colors.white, + dialogShapeBorder: RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(12.0))), + title: Text( + "My Numbers", + style: TextStyle(fontSize: 16), + ), + validator: (value) { + if (value == null || value.length == 0) { + return 'Please select one or more options'; + } + return null; + }, + dataSource: [ + Pair(label: "Zero", value: 0), + Pair(label: "One", value: 1), + Pair(label: "Two", value: 2), + Pair(label: "Three", value: 3), + ], + okButtonLabel: 'OK', + cancelButtonLabel: 'CANCEL', + hintWidget: Text('Please choose one or more'), + initialValue: _myInts, + onSaved: (value) { + if (value == null) return; + setState(() { + _myInts = value.toList(); + }); + }, + ), + ), Container( padding: EdgeInsets.all(8), child: ElevatedButton( @@ -105,7 +152,7 @@ class _MyHomePageState extends State { ), Container( padding: EdgeInsets.all(16), - child: Text(_myActivitiesResult), + child: Text(_myIntsResult), ) ], ), From 62c801662e88012327544f3a057a18cecaee10e0 Mon Sep 17 00:00:00 2001 From: Jan-Niclas de Vries Date: Wed, 14 Jul 2021 09:43:25 +0200 Subject: [PATCH 05/10] push alternative --- lib/src/multiselect_formfield.dart | 293 +++++++++++++++++++++++------ 1 file changed, 238 insertions(+), 55 deletions(-) diff --git a/lib/src/multiselect_formfield.dart b/lib/src/multiselect_formfield.dart index 4de4d68..61494bb 100644 --- a/lib/src/multiselect_formfield.dart +++ b/lib/src/multiselect_formfield.dart @@ -4,55 +4,41 @@ import 'package:flutter/material.dart'; import 'package:multiselect_formfield/src/multiselect_dialog.dart'; import 'package:multiselect_formfield/src/pair.dart'; -class MultiSelectFormField extends FormField> { +class MultiSelectFormField2 extends FormField> { final Widget title; final Widget hintWidget; - final bool required; + final bool needed; final String errorText; - final Iterable>? dataSource; - final String? textField; - final T? valueField; - final Function? change; - final Function? open; - final Function? close; - final Widget? leading; - final Widget? trailing; + final Iterable> dataSource; final String okButtonLabel; final String cancelButtonLabel; final Color? fillColor; final InputBorder? border; final TextStyle? chipLabelStyle; - final Color? chipBackGroundColor; + final Color? chipBackgroundColor; final TextStyle dialogTextStyle; final ShapeBorder dialogShapeBorder; final Color? checkBoxCheckColor; final Color? checkBoxActiveColor; final bool enabled; - MultiSelectFormField({ + MultiSelectFormField2({ FormFieldSetter>? onSaved, FormFieldValidator>? validator, Iterable? initialValue, AutovalidateMode? autovalidate = AutovalidateMode.disabled, + required this.dataSource, this.title = const Text('Title'), this.hintWidget = const Text('Tap to select one or more'), - this.required = false, + this.needed = false, this.errorText = 'Please select one or more options', - this.leading, - this.dataSource, - this.textField, - this.valueField, - this.change, - this.open, - this.close, this.okButtonLabel = 'OK', this.cancelButtonLabel = 'CANCEL', this.fillColor, this.border, - this.trailing, this.chipLabelStyle, this.enabled = true, - this.chipBackGroundColor, + this.chipBackgroundColor, this.dialogTextStyle = const TextStyle(), this.dialogShapeBorder = const RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(0.0)), @@ -65,31 +51,6 @@ class MultiSelectFormField extends FormField> { initialValue: initialValue, autovalidateMode: autovalidate, builder: (FormFieldState> state) { - List _buildSelectedOptions(state) { - List selectedOptions = []; - - if (state.value != null) { - state.value.forEach((item) { - Pair? existingItem; - try { - existingItem = dataSource!.singleWhere( - ((itm) => itm.value == item), - ); - } catch (e) {} - selectedOptions.add(Chip( - labelStyle: chipLabelStyle, - backgroundColor: chipBackGroundColor, - label: Text( - existingItem?.label ?? "", - overflow: TextOverflow.ellipsis, - ), - )); - }); - } - - return selectedOptions; - } - return InkWell( onTap: !enabled ? null @@ -100,10 +61,12 @@ class MultiSelectFormField extends FormField> { } final items = >[]; - dataSource!.forEach((item) { - items - .add(MultiSelectDialogItem(item.value, item.label)); - }); + dataSource.forEach( + (item) { + items.add( + MultiSelectDialogItem(item.value, item.label)); + }, + ); Iterable? selectedValues = await showDialog?>( @@ -136,7 +99,7 @@ class MultiSelectFormField extends FormField> { fillColor: fillColor ?? Theme.of(state.context).canvasColor, border: border ?? UnderlineInputBorder(), ), - isEmpty: state.value == null || state.value == '', + isEmpty: state.value?.isEmpty ?? false, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -148,7 +111,7 @@ class MultiSelectFormField extends FormField> { Expanded( child: title, ), - required + needed ? Padding( padding: EdgeInsets.only(top: 5, right: 5), child: Text( @@ -168,11 +131,12 @@ class MultiSelectFormField extends FormField> { ], ), ), - state.value != null && state.value!.length > 0 + state.value?.isNotEmpty ?? false ? Wrap( spacing: 8.0, runSpacing: 0.0, - children: _buildSelectedOptions(state), + children: _buildSelectedOptions(state, dataSource, + chipLabelStyle, chipBackgroundColor), ) : new Container( padding: EdgeInsets.only(top: 4), @@ -184,4 +148,223 @@ class MultiSelectFormField extends FormField> { ); }, ); + + static List _buildSelectedOptions( + FormFieldState> state, + Iterable>? dataSource, + TextStyle? chipLabelStyle, + Color? chipBackgroundColor, + ) { + List selectedOptions = []; + + if (state.value != null) { + state.value?.forEach( + (value) { + Pair? existingItem = dataSource?.cast?>().singleWhere( + ((itm) => itm?.value == value), + orElse: () => null, + ); + if (existingItem != null) { + selectedOptions.add( + Chip( + labelStyle: chipLabelStyle, + backgroundColor: chipBackgroundColor, + label: Text( + existingItem.label, + overflow: TextOverflow.ellipsis, + ), + ), + ); + } + }, + ); + } + + return selectedOptions; + } +} + +class MultiSelectFormField extends StatelessWidget { + final FormFieldSetter>? onSaved; + final FormFieldValidator>? validator; + final Iterable? initialValue; + final AutovalidateMode? autovalidate; + final Widget title; + final Widget hintWidget; + final bool needed; + final String errorText; + final Iterable> dataSource; + final String okButtonLabel; + final String cancelButtonLabel; + final Color? fillColor; + final InputBorder? border; + final TextStyle? chipLabelStyle; + final Color? chipBackgroundColor; + final TextStyle dialogTextStyle; + final ShapeBorder dialogShapeBorder; + final Color? checkBoxCheckColor; + final Color? checkBoxActiveColor; + final bool enabled; + + MultiSelectFormField({ + Key? key, + required this.dataSource, + this.onSaved, + this.validator, + this.initialValue, + this.autovalidate = AutovalidateMode.disabled, + this.title = const Text('Title'), + this.hintWidget = const Text('Tap to select one or more'), + this.needed = false, + this.errorText = 'Please select one or more options', + this.okButtonLabel = 'OK', + this.cancelButtonLabel = 'CANCEL', + this.fillColor, + this.border, + this.chipLabelStyle, + this.enabled = true, + this.chipBackgroundColor, + this.dialogTextStyle = const TextStyle(), + this.dialogShapeBorder = const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(0.0)), + ), + this.checkBoxActiveColor, + this.checkBoxCheckColor, + }) : super(key: key); + + List _buildSelectedOptions( + FormFieldState> state, + ) { + List selectedOptions = []; + + if (state.value != null) { + state.value?.forEach( + (value) { + Pair? existingItem = dataSource.cast?>().singleWhere( + ((itm) => itm?.value == value), + orElse: () => null, + ); + if (existingItem != null) { + selectedOptions.add( + Chip( + labelStyle: chipLabelStyle, + backgroundColor: chipBackgroundColor, + label: Text( + existingItem.label, + overflow: TextOverflow.ellipsis, + ), + ), + ); + } + }, + ); + } + + return selectedOptions; + } + + @override + Widget build(BuildContext context) { + return FormField>( + onSaved: onSaved, + validator: validator, + initialValue: initialValue, + autovalidateMode: autovalidate, + builder: (FormFieldState> state) { + return InkWell( + onTap: !enabled + ? null + : () async { + Iterable? initialSelected = state.value; + if (initialSelected == null) { + initialSelected = List.empty(growable: true); + } + + final items = >[]; + dataSource.forEach( + (item) { + items.add(MultiSelectDialogItem(item.value, item.label)); + }, + ); + + Iterable? selectedValues = await showDialog?>( + context: state.context, + builder: (BuildContext context) { + return MultiSelectDialog( + title: title, + okButtonLabel: okButtonLabel, + cancelButtonLabel: cancelButtonLabel, + items: items, + initialSelectedValues: initialSelected, + labelStyle: dialogTextStyle, + dialogShapeBorder: dialogShapeBorder, + checkBoxActiveColor: checkBoxActiveColor, + checkBoxCheckColor: checkBoxCheckColor, + ); + }, + ); + + if (selectedValues != null) { + state.didChange(selectedValues); + state.save(); + } + }, + child: InputDecorator( + decoration: InputDecoration( + filled: true, + errorText: state.hasError ? state.errorText : null, + errorMaxLines: 4, + fillColor: fillColor ?? Theme.of(state.context).canvasColor, + border: border ?? UnderlineInputBorder(), + ), + isEmpty: state.value?.isEmpty ?? false, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsets.fromLTRB(0, 2, 0, 0), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: title, + ), + needed + ? Padding( + padding: EdgeInsets.only(top: 5, right: 5), + child: Text( + ' *', + style: TextStyle( + color: Colors.red.shade700, + fontSize: 17.0, + ), + ), + ) + : Container(), + Icon( + Icons.arrow_drop_down, + color: Colors.black87, + size: 25.0, + ), + ], + ), + ), + state.value?.isNotEmpty ?? false + ? Wrap( + spacing: 8.0, + runSpacing: 0.0, + children: _buildSelectedOptions(state), + ) + : new Container( + padding: EdgeInsets.only(top: 4), + child: hintWidget, + ) + ], + ), + ), + ); + }, + ); + ; + } } From db958dde2d25632b311306ffd25a12d145c07b40 Mon Sep 17 00:00:00 2001 From: Jan-Niclas de Vries Date: Wed, 14 Jul 2021 09:43:36 +0200 Subject: [PATCH 06/10] show validation error --- example/lib/main.dart | 3 +++ 1 file changed, 3 insertions(+) diff --git a/example/lib/main.dart b/example/lib/main.dart index 702d97c..7c2af3d 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -41,6 +41,9 @@ class _MyHomePageState extends State { _myActivitiesResult = _myActivities.toString(); _myIntsResult = _myInts.toString(); }); + } else { + setState(() => + _myActivitiesResult = _myIntsResult = "Error: Validation failed"); } } From 7d50c1acddb1e1b1e6a50a3b7ec950abf825522c Mon Sep 17 00:00:00 2001 From: Jan-Niclas de Vries Date: Wed, 14 Jul 2021 10:14:15 +0200 Subject: [PATCH 07/10] use alternative --- lib/src/multiselect_formfield.dart | 244 ++++------------------------- 1 file changed, 27 insertions(+), 217 deletions(-) diff --git a/lib/src/multiselect_formfield.dart b/lib/src/multiselect_formfield.dart index 61494bb..0236343 100644 --- a/lib/src/multiselect_formfield.dart +++ b/lib/src/multiselect_formfield.dart @@ -4,186 +4,6 @@ import 'package:flutter/material.dart'; import 'package:multiselect_formfield/src/multiselect_dialog.dart'; import 'package:multiselect_formfield/src/pair.dart'; -class MultiSelectFormField2 extends FormField> { - final Widget title; - final Widget hintWidget; - final bool needed; - final String errorText; - final Iterable> dataSource; - final String okButtonLabel; - final String cancelButtonLabel; - final Color? fillColor; - final InputBorder? border; - final TextStyle? chipLabelStyle; - final Color? chipBackgroundColor; - final TextStyle dialogTextStyle; - final ShapeBorder dialogShapeBorder; - final Color? checkBoxCheckColor; - final Color? checkBoxActiveColor; - final bool enabled; - - MultiSelectFormField2({ - FormFieldSetter>? onSaved, - FormFieldValidator>? validator, - Iterable? initialValue, - AutovalidateMode? autovalidate = AutovalidateMode.disabled, - required this.dataSource, - this.title = const Text('Title'), - this.hintWidget = const Text('Tap to select one or more'), - this.needed = false, - this.errorText = 'Please select one or more options', - this.okButtonLabel = 'OK', - this.cancelButtonLabel = 'CANCEL', - this.fillColor, - this.border, - this.chipLabelStyle, - this.enabled = true, - this.chipBackgroundColor, - this.dialogTextStyle = const TextStyle(), - this.dialogShapeBorder = const RoundedRectangleBorder( - borderRadius: BorderRadius.all(Radius.circular(0.0)), - ), - this.checkBoxActiveColor, - this.checkBoxCheckColor, - }) : super( - onSaved: onSaved, - validator: validator, - initialValue: initialValue, - autovalidateMode: autovalidate, - builder: (FormFieldState> state) { - return InkWell( - onTap: !enabled - ? null - : () async { - Iterable? initialSelected = state.value; - if (initialSelected == null) { - initialSelected = List.empty(growable: true); - } - - final items = >[]; - dataSource.forEach( - (item) { - items.add( - MultiSelectDialogItem(item.value, item.label)); - }, - ); - - Iterable? selectedValues = - await showDialog?>( - context: state.context, - builder: (BuildContext context) { - return MultiSelectDialog( - title: title, - okButtonLabel: okButtonLabel, - cancelButtonLabel: cancelButtonLabel, - items: items, - initialSelectedValues: initialSelected, - labelStyle: dialogTextStyle, - dialogShapeBorder: dialogShapeBorder, - checkBoxActiveColor: checkBoxActiveColor, - checkBoxCheckColor: checkBoxCheckColor, - ); - }, - ); - - if (selectedValues != null) { - state.didChange(selectedValues); - state.save(); - } - }, - child: InputDecorator( - decoration: InputDecoration( - filled: true, - errorText: state.hasError ? state.errorText : null, - errorMaxLines: 4, - fillColor: fillColor ?? Theme.of(state.context).canvasColor, - border: border ?? UnderlineInputBorder(), - ), - isEmpty: state.value?.isEmpty ?? false, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: EdgeInsets.fromLTRB(0, 2, 0, 0), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded( - child: title, - ), - needed - ? Padding( - padding: EdgeInsets.only(top: 5, right: 5), - child: Text( - ' *', - style: TextStyle( - color: Colors.red.shade700, - fontSize: 17.0, - ), - ), - ) - : Container(), - Icon( - Icons.arrow_drop_down, - color: Colors.black87, - size: 25.0, - ), - ], - ), - ), - state.value?.isNotEmpty ?? false - ? Wrap( - spacing: 8.0, - runSpacing: 0.0, - children: _buildSelectedOptions(state, dataSource, - chipLabelStyle, chipBackgroundColor), - ) - : new Container( - padding: EdgeInsets.only(top: 4), - child: hintWidget, - ) - ], - ), - ), - ); - }, - ); - - static List _buildSelectedOptions( - FormFieldState> state, - Iterable>? dataSource, - TextStyle? chipLabelStyle, - Color? chipBackgroundColor, - ) { - List selectedOptions = []; - - if (state.value != null) { - state.value?.forEach( - (value) { - Pair? existingItem = dataSource?.cast?>().singleWhere( - ((itm) => itm?.value == value), - orElse: () => null, - ); - if (existingItem != null) { - selectedOptions.add( - Chip( - labelStyle: chipLabelStyle, - backgroundColor: chipBackgroundColor, - label: Text( - existingItem.label, - overflow: TextOverflow.ellipsis, - ), - ), - ); - } - }, - ); - } - - return selectedOptions; - } -} - class MultiSelectFormField extends StatelessWidget { final FormFieldSetter>? onSaved; final FormFieldValidator>? validator; @@ -232,34 +52,30 @@ class MultiSelectFormField extends StatelessWidget { this.checkBoxCheckColor, }) : super(key: key); - List _buildSelectedOptions( - FormFieldState> state, - ) { - List selectedOptions = []; + List _buildSelectedOptions(FormFieldState> state) { + final List selectedOptions = []; + final values = state.value; - if (state.value != null) { - state.value?.forEach( - (value) { - Pair? existingItem = dataSource.cast?>().singleWhere( - ((itm) => itm?.value == value), - orElse: () => null, - ); - if (existingItem != null) { - selectedOptions.add( - Chip( - labelStyle: chipLabelStyle, - backgroundColor: chipBackgroundColor, - label: Text( - existingItem.label, - overflow: TextOverflow.ellipsis, - ), - ), + if (values != null) { + for (final value in values) { + Pair? existingItem = dataSource.cast?>().singleWhere( + ((itm) => itm?.value == value), + orElse: () => null, ); - } - }, - ); + if (existingItem != null) { + selectedOptions.add( + Chip( + labelStyle: chipLabelStyle, + backgroundColor: chipBackgroundColor, + label: Text( + existingItem.label, + overflow: TextOverflow.ellipsis, + ), + ), + ); + } + } } - return selectedOptions; } @@ -275,17 +91,12 @@ class MultiSelectFormField extends StatelessWidget { onTap: !enabled ? null : () async { - Iterable? initialSelected = state.value; - if (initialSelected == null) { - initialSelected = List.empty(growable: true); - } - + Iterable? initialSelected = + state.value ?? List.empty(growable: true); final items = >[]; - dataSource.forEach( - (item) { - items.add(MultiSelectDialogItem(item.value, item.label)); - }, - ); + for (final pair in dataSource) { + items.add(MultiSelectDialogItem(pair.value, pair.label)); + } Iterable? selectedValues = await showDialog?>( context: state.context, @@ -355,7 +166,7 @@ class MultiSelectFormField extends StatelessWidget { runSpacing: 0.0, children: _buildSelectedOptions(state), ) - : new Container( + : Container( padding: EdgeInsets.only(top: 4), child: hintWidget, ) @@ -365,6 +176,5 @@ class MultiSelectFormField extends StatelessWidget { ); }, ); - ; } } From 7ffc71575989491dd477289dec862b540c32dc34 Mon Sep 17 00:00:00 2001 From: Jan-Niclas de Vries Date: Wed, 14 Jul 2021 10:14:56 +0200 Subject: [PATCH 08/10] add lints --- analysis_options.yaml | 1 + pubspec.lock | 7 +++++++ pubspec.yaml | 1 + 3 files changed, 9 insertions(+) create mode 100644 analysis_options.yaml diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 0000000..572dd23 --- /dev/null +++ b/analysis_options.yaml @@ -0,0 +1 @@ +include: package:lints/recommended.yaml diff --git a/pubspec.lock b/pubspec.lock index ad6d1df..1d510d4 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -60,6 +60,13 @@ packages: description: flutter source: sdk version: "0.0.0" + lints: + dependency: "direct dev" + description: + name: lints + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" matcher: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 8677820..a540f1c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -14,5 +14,6 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter + lints: ^1.0.1 flutter: From 94ccb8ecc1228c90a9e29089ff315254ac9e9df5 Mon Sep 17 00:00:00 2001 From: Jan-Niclas de Vries Date: Wed, 14 Jul 2021 10:18:28 +0200 Subject: [PATCH 09/10] make adujstments --- example/lib/main.dart | 6 +++--- lib/src/multiselect_dialog.dart | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index 7c2af3d..bcbbb49 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -22,7 +22,7 @@ class _MyHomePageState extends State { List? _myInts; late String _myActivitiesResult; late String _myIntsResult; - final formKey = new GlobalKey(); + final formKey = GlobalKey(); @override void initState() { @@ -76,7 +76,7 @@ class _MyHomePageState extends State { style: TextStyle(fontSize: 16), ), validator: (value) { - if (value == null || value.length == 0) { + if (value == null || value.isEmpty) { return 'Please select one or more options'; } return null; @@ -123,7 +123,7 @@ class _MyHomePageState extends State { style: TextStyle(fontSize: 16), ), validator: (value) { - if (value == null || value.length == 0) { + if (value == null || value.isEmpty) { return 'Please select one or more options'; } return null; diff --git a/lib/src/multiselect_dialog.dart b/lib/src/multiselect_dialog.dart index 89b0533..78626bf 100644 --- a/lib/src/multiselect_dialog.dart +++ b/lib/src/multiselect_dialog.dart @@ -38,6 +38,7 @@ class MultiSelectDialog extends StatefulWidget { class _MultiSelectDialogState extends State> { final _selectedValues = []; + @override void initState() { super.initState(); if (widget.initialSelectedValues != null) { From 52431051b5429133fc358949dfff21ace08f104c Mon Sep 17 00:00:00 2001 From: Jan-Niclas de Vries Date: Wed, 14 Jul 2021 10:22:48 +0200 Subject: [PATCH 10/10] update version and readme --- README.md | 62 +++++++++++++------------------------------- example/pubspec.lock | 2 +- pubspec.yaml | 2 +- 3 files changed, 20 insertions(+), 46 deletions(-) diff --git a/README.md b/README.md index 5167ad7..59f32f7 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ A multi select form field using alert dialog to select multiple items with check | :------------------------ | :---------------------------------------------------------------------------------------------------------------------- | | **title** *Widget* | Set Title of MultiSelectTextFormField. | | **hintWidget** *Widget* | Set Hint Text of MultiSelectTextFormField. | -| **required** *bool* | Add Selection is Compulsary or not. | +| **needed** *bool* | Add Selection is Compulsary or not. | | **errorText** *String* | Error String to be Displayed | | **dataSource** *List*| List of Data as DataSource To Select. | | **textField** *String* | Key Param from List (DataSource). | @@ -57,7 +57,7 @@ A multi select form field using alert dialog to select multiple items with check ## Minimal Example ```dart -MultiSelectFormField( +MultiSelectFormField( autovalidate: false, chipBackGroundColor: Colors.red, chipLabelStyle: TextStyle(fontWeight: FontWeight.bold), @@ -71,18 +71,13 @@ MultiSelectFormField( style: TextStyle(fontSize: 16), ), dataSource: [ - { - "display": "Running", - "value": "Running", - }, - { - "display": "Climbing", - "value": "Climbing", - }, - { - "display": "Walking", - "value": "Walking", - }, + Pair.from("Running"), + Pair.from("Climbing"), + Pair.from("Walking"), + Pair.from("Swimming"), + Pair.from("Soccer Practice"), + Pair.from("Baseball Practice"), + Pair.from("Football Practice"), ], textField: 'display', valueField: 'value', @@ -156,7 +151,7 @@ class _MyHomePageState extends State { children: [ Container( padding: EdgeInsets.all(16), - child: MultiSelectFormField( + child: MultiSelectFormField( autovalidate: false, titleText: 'My workouts', validator: (value) { @@ -165,40 +160,19 @@ class _MyHomePageState extends State { } }, 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", - }, + Pair.from("Running"), + Pair.from("Climbing"), + Pair.from("Walking"), + Pair.from("Swimming"), + Pair.from("Soccer Practice"), + Pair.from("Baseball Practice"), + Pair.from("Football Practice"), ], textField: 'display', valueField: 'value', okButtonLabel: 'OK', cancelButtonLabel: 'CANCEL', - // required: true, + // needed: true, hintText: 'Please choose one or more', value: _myActivities, onSaved: (value) { diff --git a/example/pubspec.lock b/example/pubspec.lock index fafaf8e..54b1447 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -87,7 +87,7 @@ packages: path: ".." relative: true source: path - version: "0.1.6" + version: "0.1.7" path: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index a540f1c..9496f04 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: multiselect_formfield description: A multi select form field using alert dialog to select multiple items with checkboxes and showing as chips. -version: 0.1.6 +version: 0.1.7 author: Carlos E. Torres homepage: https://github.com/cetorres/multiselect_formfield