diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..f8c5fc9 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/README.md b/README.md index 71100cc..038a5d1 100644 --- a/README.md +++ b/README.md @@ -58,11 +58,11 @@ Search choices Widget with a single choice that opens a dialog or a menu to let ```dart factory SearchableDropdown.single({ - Key key, - @required List> items, - @required Function onChanged, - T value, - TextStyle style, + Key? key, + required List> items, + required Function onChanged, + T? value, + TextStyle? style, dynamic searchHint, dynamic hint, dynamic disabledHint, @@ -73,23 +73,23 @@ factory SearchableDropdown.single({ dynamic closeButton = "Close", bool displayClearIcon = true, Icon clearIcon = const Icon(Icons.clear), - Color iconEnabledColor, - Color iconDisabledColor, + Color? iconEnabledColor, + Color? iconDisabledColor, double iconSize = 24.0, bool isExpanded = false, bool isCaseSensitiveSearch = false, - Function searchFn, - Function onClear, - Function selectedValueWidgetFn, + Function? searchFn, + Function? onClear, + Function? selectedValueWidgetFn, TextInputType keyboardType = TextInputType.text, - Function validator, + Function? validator, bool assertUniqueValue = true, - Function displayItem, + Function? displayItem, bool dialogBox = true, - BoxConstraints menuConstraints, - bool readOnly: false, - Color menuBackgroundColor, -} + BoxConstraints? menuConstraints, + bool readOnly = false, + Color? menuBackgroundColor, + } ) ``` @@ -133,8 +133,8 @@ Search choices Widget with a multiple choice that opens a dialog or a menu to le SearchableDropdown.multiple( { Key key, - @required List> items, - @required Function onChanged, + required List> items, + required Function onChanged, List selectedItems: const [], TextStyle style, dynamic searchHint, diff --git a/example/pubspec.lock b/example/pubspec.lock index 2a1fc0d..5919806 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -7,35 +7,42 @@ packages: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.4.1" + version: "2.8.1" boolean_selector: dependency: transitive description: name: boolean_selector url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.1.0" + characters: + dependency: transitive + description: + name: characters + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" charcode: dependency: transitive description: name: charcode url: "https://pub.dartlang.org" source: hosted - version: "1.1.3" + version: "1.3.1" clock: dependency: transitive description: name: clock url: "https://pub.dartlang.org" source: hosted - version: "1.0.1" + version: "1.1.0" collection: dependency: transitive description: name: collection url: "https://pub.dartlang.org" source: hosted - version: "1.14.12" + version: "1.15.0" cupertino_icons: dependency: "direct main" description: @@ -49,7 +56,7 @@ packages: name: fake_async url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.2.0" flutter: dependency: "direct main" description: flutter @@ -66,21 +73,21 @@ packages: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.6" + version: "0.12.10" meta: dependency: transitive description: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.1.8" + version: "1.7.0" path: dependency: transitive description: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.7.0" + version: "1.8.0" searchable_dropdown: dependency: "direct dev" description: @@ -99,55 +106,55 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.7.0" + version: "1.8.1" stack_trace: dependency: transitive description: name: stack_trace url: "https://pub.dartlang.org" source: hosted - version: "1.9.3" + version: "1.10.0" stream_channel: dependency: transitive description: name: stream_channel url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.1.0" string_scanner: dependency: transitive description: name: string_scanner url: "https://pub.dartlang.org" source: hosted - version: "1.0.5" + version: "1.1.0" term_glyph: dependency: transitive description: name: term_glyph url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.2.0" test_api: dependency: transitive description: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.2.15" + version: "0.4.2" typed_data: dependency: transitive description: name: typed_data url: "https://pub.dartlang.org" source: hosted - version: "1.1.6" + version: "1.3.0" vector_math: dependency: transitive description: name: vector_math url: "https://pub.dartlang.org" source: hosted - version: "2.0.8" + version: "2.1.0" sdks: - dart: ">=2.6.0 <3.0.0" + dart: ">=2.12.0 <3.0.0" diff --git a/lib/searchable_dropdown.dart b/lib/searchable_dropdown.dart index cb9a2f4..2c63786 100644 --- a/lib/searchable_dropdown.dart +++ b/lib/searchable_dropdown.dart @@ -11,13 +11,14 @@ class NotGiven { class PointerThisPlease { T value; + PointerThisPlease(this.value); } -Widget prepareWidget(dynamic object, +Widget? prepareWidget(dynamic object, {dynamic parameter = const NotGiven(), - BuildContext context, - Function stringToWidgetFunction}) { + BuildContext? context, + Function? stringToWidgetFunction}) { if (object == null) { return (null); } @@ -52,10 +53,10 @@ Widget prepareWidget(dynamic object, } class SearchableDropdown extends StatefulWidget { - final List> items; - final Function onChanged; - final T value; - final TextStyle style; + final List>? items; + final Function? onChanged; + final T? value; + final TextStyle? style; final dynamic searchHint; final dynamic hint; final dynamic disabledHint; @@ -65,24 +66,24 @@ class SearchableDropdown extends StatefulWidget { final dynamic label; final dynamic closeButton; final bool displayClearIcon; - final Icon clearIcon; - final Color iconEnabledColor; - final Color iconDisabledColor; - final double iconSize; - final bool isExpanded; + final Icon? clearIcon; + final Color? iconEnabledColor; + final Color? iconDisabledColor; + final double? iconSize; + final bool? isExpanded; final bool isCaseSensitiveSearch; - final Function searchFn; - final Function onClear; - final Function selectedValueWidgetFn; + final Function? searchFn; + final Function? onClear; + final Function? selectedValueWidgetFn; final TextInputType keyboardType; - final Function validator; + final Function? validator; final bool multipleSelection; - final List selectedItems; - final Function displayItem; - final bool dialogBox; - final BoxConstraints menuConstraints; - final bool readOnly; - final Color menuBackgroundColor; + final List? selectedItems; + final Function? displayItem; + final bool? dialogBox; + final BoxConstraints? menuConstraints; + final bool? readOnly; + final Color? menuBackgroundColor; /// Search choices Widget with a single choice that opens a dialog or a menu to let the user do the selection conveniently with a search. /// @@ -117,11 +118,11 @@ class SearchableDropdown extends StatefulWidget { /// @param readOnly [bool] whether to let the user choose the value to select or just present the selected value if any. /// @param menuBackgroundColor [Color] background color of the menu whether in dialog box or menu mode. factory SearchableDropdown.single({ - Key key, - @required List> items, - @required Function onChanged, - T value, - TextStyle style, + Key? key, + required List> items, + required Function onChanged, + T? value, + TextStyle? style, dynamic searchHint, dynamic hint, dynamic disabledHint, @@ -132,22 +133,22 @@ class SearchableDropdown extends StatefulWidget { dynamic closeButton = "Close", bool displayClearIcon = true, Icon clearIcon = const Icon(Icons.clear), - Color iconEnabledColor, - Color iconDisabledColor, + Color? iconEnabledColor, + Color? iconDisabledColor, double iconSize = 24.0, bool isExpanded = false, bool isCaseSensitiveSearch = false, - Function searchFn, - Function onClear, - Function selectedValueWidgetFn, + Function? searchFn, + Function? onClear, + Function? selectedValueWidgetFn, TextInputType keyboardType = TextInputType.text, - Function validator, + Function? validator, bool assertUniqueValue = true, - Function displayItem, + Function? displayItem, bool dialogBox = true, - BoxConstraints menuConstraints, + BoxConstraints? menuConstraints, bool readOnly = false, - Color menuBackgroundColor, + Color? menuBackgroundColor, }) { return (SearchableDropdown._( key: key, @@ -216,11 +217,11 @@ class SearchableDropdown extends StatefulWidget { /// @param readOnly [bool] whether to let the user choose the value to select or just present the selected value if any. /// @param menuBackgroundColor [Color] background color of the menu whether in dialog box or menu mode. factory SearchableDropdown.multiple({ - Key key, - @required List> items, - @required Function onChanged, + Key? key, + required List> items, + required Function onChanged, List selectedItems = const [], - TextStyle style, + TextStyle? style, dynamic searchHint, dynamic hint, dynamic disabledHint, @@ -231,60 +232,60 @@ class SearchableDropdown extends StatefulWidget { dynamic closeButton = "Close", bool displayClearIcon = true, Icon clearIcon = const Icon(Icons.clear), - Color iconEnabledColor, - Color iconDisabledColor, + Color? iconEnabledColor, + Color? iconDisabledColor, double iconSize = 24.0, bool isExpanded = false, bool isCaseSensitiveSearch = false, - Function searchFn, - Function onClear, - Function selectedValueWidgetFn, + Function? searchFn, + Function? onClear, + Function? selectedValueWidgetFn, TextInputType keyboardType = TextInputType.text, - Function validator, - Function displayItem, + Function? validator, + Function? displayItem, bool dialogBox = true, - BoxConstraints menuConstraints, + BoxConstraints? menuConstraints, bool readOnly = false, - Color menuBackgroundColor, + Color? menuBackgroundColor, }) { return (SearchableDropdown._( - key: key, + key: key!, items: items, - style: style, + style: style!, searchHint: searchHint, hint: hint, disabledHint: disabledHint, icon: icon, underline: underline, - iconEnabledColor: iconEnabledColor, - iconDisabledColor: iconDisabledColor, + iconEnabledColor: iconEnabledColor!, + iconDisabledColor: iconDisabledColor!, iconSize: iconSize, isExpanded: isExpanded, isCaseSensitiveSearch: isCaseSensitiveSearch, closeButton: closeButton, displayClearIcon: displayClearIcon, clearIcon: clearIcon, - onClear: onClear, - selectedValueWidgetFn: selectedValueWidgetFn, + onClear: onClear!, + selectedValueWidgetFn: selectedValueWidgetFn!, keyboardType: keyboardType, - validator: validator, + validator: validator!, label: label, - searchFn: searchFn, + searchFn: searchFn!, multipleSelection: true, selectedItems: selectedItems, doneButton: doneButton, onChanged: onChanged, - displayItem: displayItem, + displayItem: displayItem!, dialogBox: dialogBox, - menuConstraints: menuConstraints, + menuConstraints: menuConstraints!, readOnly: readOnly, - menuBackgroundColor: menuBackgroundColor, + menuBackgroundColor: menuBackgroundColor!, )); } SearchableDropdown._({ - Key key, - @required this.items, + Key? key, + required this.items, this.onChanged, this.value, this.style, @@ -319,13 +320,13 @@ class SearchableDropdown extends StatefulWidget { assert(iconSize != null), assert(isExpanded != null), assert(!multipleSelection || doneButton != null), - assert(menuConstraints == null || !dialogBox), + assert(menuConstraints == null || !dialogBox!), super(key: key); SearchableDropdown({ - Key key, - @required this.items, - @required this.onChanged, + Key? key, + required this.items, + required this.onChanged, this.value, this.style, this.searchHint, @@ -359,7 +360,7 @@ class SearchableDropdown extends StatefulWidget { assert(iconSize != null), assert(isExpanded != null), assert(!multipleSelection || doneButton != null), - assert(menuConstraints == null || !dialogBox), + assert(menuConstraints == null || !dialogBox!), super(key: key); @override @@ -367,46 +368,49 @@ class SearchableDropdown extends StatefulWidget { } class _SearchableDropdownState extends State> { - List selectedItems; + List? selectedItems; PointerThisPlease displayMenu = PointerThisPlease(false); - TextStyle get _textStyle => + TextStyle? get _textStyle => widget.style ?? (_enabled && !(widget.readOnly ?? false) - ? Theme.of(context).textTheme.subhead + ? Theme.of(context).textTheme.subtitle1 : Theme.of(context) .textTheme - .subhead + .subtitle1! .copyWith(color: _disabledIconColor)); + bool get _enabled => widget.items != null && - widget.items.isNotEmpty && + widget.items!.isNotEmpty && widget.onChanged != null; Color get _enabledIconColor { if (widget.iconEnabledColor != null) { - return widget.iconEnabledColor; + return widget.iconEnabledColor!; } switch (Theme.of(context).brightness) { case Brightness.light: return Colors.grey.shade700; case Brightness.dark: return Colors.white70; + default: + return Colors.grey.shade700; } - return Colors.grey.shade700; } Color get _disabledIconColor { if (widget.iconDisabledColor != null) { - return widget.iconDisabledColor; + return widget.iconDisabledColor!; } switch (Theme.of(context).brightness) { case Brightness.light: return Colors.grey.shade400; case Brightness.dark: return Colors.white10; + default: + return Colors.grey.shade400; } - return Colors.grey.shade400; } Color get _iconColor { @@ -420,23 +424,23 @@ class _SearchableDropdownState extends State> { if (widget.validator == null) { return (true); } - return (widget.validator(selectedResult) == null); + return (widget.validator!(selectedResult) == null); } bool get hasSelection { - return (selectedItems != null && selectedItems.isNotEmpty); + return (selectedItems != null && selectedItems!.isNotEmpty); } dynamic get selectedResult { return (widget.multipleSelection ? selectedItems : selectedItems?.isNotEmpty ?? false - ? widget.items[selectedItems.first]?.value + ? widget.items![selectedItems!.first].value : null); } int indexFromValue(T value) { - return (widget.items.indexWhere((item) { + return (widget.items!.indexWhere((item) { return (item.value == value); })); } @@ -454,8 +458,8 @@ class _SearchableDropdownState extends State> { if (widget.multipleSelection) { selectedItems = List.from(widget.selectedItems ?? []); } else if (widget.value != null) { - int i = indexFromValue(widget.value); - if (i != null && i != -1) { + int? i = indexFromValue(widget.value!); + if (i != -1) { selectedItems = [i]; } } @@ -463,7 +467,7 @@ class _SearchableDropdownState extends State> { } @override - void didUpdateWidget(SearchableDropdown oldWidget) { + void didUpdateWidget(SearchableDropdown oldWidget) { super.didUpdateWidget(oldWidget); _updateSelectedIndex(); } @@ -486,10 +490,10 @@ class _SearchableDropdownState extends State> { menuConstraints: widget.menuConstraints, menuBackgroundColor: widget.menuBackgroundColor, callOnPop: () { - if (!widget.dialogBox && + if (!(widget.dialogBox ?? false) && widget.onChanged != null && selectedItems != null) { - widget.onChanged(selectedResult); + widget.onChanged!(selectedResult); } setState(() {}); }, @@ -499,18 +503,18 @@ class _SearchableDropdownState extends State> { @override Widget build(BuildContext context) { final List items = - _enabled ? List.from(widget.items) : []; - int hintIndex; + _enabled ? List.from(widget.items!) : []; + int? hintIndex; if (widget.hint != null || (!_enabled && prepareWidget(widget.disabledHint) != null)) { - final Widget emplacedHint = _enabled + final Widget? emplacedHint = _enabled ? prepareWidget(widget.hint) : DropdownMenuItem( child: prepareWidget(widget.disabledHint) ?? - prepareWidget(widget.hint)); + prepareWidget(widget.hint)!); hintIndex = items.length; items.add(DefaultTextStyle( - style: _textStyle.copyWith(color: Theme.of(context).hintColor), + style: _textStyle!.copyWith(color: Theme.of(context).hintColor), child: IgnorePointer( child: emplacedHint, ignoringSemantics: false, @@ -518,10 +522,10 @@ class _SearchableDropdownState extends State> { )); } Widget innerItemsWidget; - List list = List(); + var list = []; selectedItems?.forEach((item) { list.add(widget.selectedValueWidgetFn != null - ? widget.selectedValueWidgetFn(widget.items[item].value) + ? widget.selectedValueWidgetFn!(widget.items![item].value) : items[item]); }); if (list.isEmpty && hintIndex != null) { @@ -536,12 +540,12 @@ class _SearchableDropdownState extends State> { : _kUnalignedButtonPadding; Widget clickable = InkWell( - key: Key( - "clickableResultPlaceHolder"), //this key is used for running automated tests + key: Key("clickableResultPlaceHolder"), + //this key is used for running automated tests onTap: (widget.readOnly ?? false) || !_enabled ? null : () async { - if (widget.dialogBox) { + if (widget.dialogBox!) { await showDialog( context: context, barrierDismissible: true, @@ -549,7 +553,7 @@ class _SearchableDropdownState extends State> { return (menuWidget); }); if (widget.onChanged != null && selectedItems != null) { - widget.onChanged(selectedResult); + widget.onChanged!(selectedResult); } } else { displayMenu.value = true; @@ -558,7 +562,7 @@ class _SearchableDropdownState extends State> { }, child: Row( children: [ - widget.isExpanded + (widget.isExpanded??false) ? Expanded(child: innerItemsWidget) : innerItemsWidget, IconTheme( @@ -573,18 +577,18 @@ class _SearchableDropdownState extends State> { )); Widget result = DefaultTextStyle( - style: _textStyle, + style: _textStyle!, child: Container( padding: padding.resolve(Directionality.of(context)), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisSize: MainAxisSize.min, children: [ - widget.isExpanded ? Expanded(child: clickable) : clickable, + (widget.isExpanded??false) ? Expanded(child: clickable) : clickable, !widget.displayClearIcon ? SizedBox() : InkWell( - onTap: hasSelection && _enabled && !widget.readOnly + onTap: hasSelection && _enabled && !widget.readOnly! ? () { clearSelection(); } @@ -598,7 +602,7 @@ class _SearchableDropdownState extends State> { IconTheme( data: IconThemeData( color: - hasSelection && _enabled && !widget.readOnly + hasSelection && _enabled && !widget.readOnly! ? _enabledIconColor : _disabledIconColor, size: widget.iconSize, @@ -617,7 +621,7 @@ class _SearchableDropdownState extends State> { final double bottom = 8.0; var validatorOutput; if (widget.validator != null) { - validatorOutput = widget.validator(selectedResult); + validatorOutput = widget.validator!(selectedResult); } var labelOutput = prepareWidget(widget.label, parameter: selectedResult, stringToWidgetFunction: (string) { @@ -669,37 +673,37 @@ class _SearchableDropdownState extends State> { } clearSelection() { - selectedItems.clear(); + selectedItems!.clear(); if (widget.onChanged != null) { - widget.onChanged(selectedResult); + widget.onChanged!(selectedResult); } if (widget.onClear != null) { - widget.onClear(); + widget.onClear!(); } setState(() {}); } } class DropdownDialog extends StatefulWidget { - final List> items; - final Widget hint; - final bool isCaseSensitiveSearch; + final List>? items; + final Widget? hint; + final bool? isCaseSensitiveSearch; final dynamic closeButton; - final TextInputType keyboardType; - final Function searchFn; - final bool multipleSelection; - final List selectedItems; - final Function displayItem; + final TextInputType? keyboardType; + final Function? searchFn; + final bool? multipleSelection; + final List? selectedItems; + final Function? displayItem; final dynamic doneButton; - final Function validator; - final bool dialogBox; - final PointerThisPlease displayMenu; - final BoxConstraints menuConstraints; - final Function callOnPop; - final Color menuBackgroundColor; + final Function? validator; + final bool? dialogBox; + final PointerThisPlease? displayMenu; + final BoxConstraints? menuConstraints; + final Function? callOnPop; + final Color? menuBackgroundColor; DropdownDialog({ - Key key, + Key? key, this.items, this.hint, this.isCaseSensitiveSearch = false, @@ -727,20 +731,20 @@ class _DropdownDialogState extends State { TextStyle defaultButtonStyle = new TextStyle(fontSize: 16, fontWeight: FontWeight.w500); List shownIndexes = []; - Function searchFn; + Function? searchFn; _DropdownDialogState(); dynamic get selectedResult { - return (widget.multipleSelection + return (widget.multipleSelection! ? widget.selectedItems : widget.selectedItems?.isNotEmpty ?? false - ? widget.items[widget.selectedItems.first]?.value + ? widget.items![widget.selectedItems!.first].value : null); } void _updateShownIndexes(String keyword) { - shownIndexes = searchFn(keyword, widget.items); + shownIndexes = searchFn!(keyword, widget.items); } @override @@ -749,7 +753,7 @@ class _DropdownDialogState extends State { searchFn = widget.searchFn; } else { Function matchFn; - if (widget.isCaseSensitiveSearch) { + if (widget.isCaseSensitiveSearch!) { matchFn = (item, keyword) { return (item.value.toString().contains(keyword)); }; @@ -764,7 +768,7 @@ class _DropdownDialogState extends State { searchFn = (keyword, items) { List shownIndexes = []; int i = 0; - widget.items.forEach((item) { + widget.items!.forEach((item) { if (matchFn(item, keyword) || (keyword?.isEmpty ?? true)) { shownIndexes.add(i); } @@ -786,8 +790,8 @@ class _DropdownDialogState extends State { child: new Card( color: widget.menuBackgroundColor, margin: EdgeInsets.symmetric( - vertical: widget.dialogBox ? 10 : 5, - horizontal: widget.dialogBox ? 10 : 4), + vertical: (widget.dialogBox ?? false) ? 10 : 5, + horizontal: (widget.dialogBox ?? false) ? 10 : 4), child: new Container( constraints: widget.menuConstraints, padding: EdgeInsets.symmetric(vertical: 15, horizontal: 15), @@ -811,13 +815,13 @@ class _DropdownDialogState extends State { if (widget.validator == null) { return (true); } - return (widget.validator(selectedResult) == null); + return (widget.validator!(selectedResult) == null); } Widget titleBar() { var validatorOutput; if (widget.validator != null) { - validatorOutput = widget.validator(selectedResult); + validatorOutput = widget.validator!(selectedResult); } Widget validatorOutputWidget = valid @@ -829,12 +833,12 @@ class _DropdownDialogState extends State { ) : validatorOutput; - Widget doneButtonWidget = - widget.multipleSelection || widget.doneButton != null + Widget? doneButtonWidget = + (widget.multipleSelection ?? false) || widget.doneButton != null ? prepareWidget(widget.doneButton, parameter: selectedResult, context: context, stringToWidgetFunction: (string) { - return (FlatButton.icon( + return (TextButton.icon( onPressed: !valid ? null : () { @@ -851,15 +855,18 @@ class _DropdownDialogState extends State { child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - prepareWidget(widget.hint), + prepareWidget(widget.hint)!, Column( - children: [doneButtonWidget, validatorOutputWidget], + children: [ + doneButtonWidget!, + validatorOutputWidget + ], ), ]), ) : new Container( child: Column( - children: [doneButtonWidget, validatorOutputWidget], + children: [doneButtonWidget!, validatorOutputWidget], ), ); } @@ -925,12 +932,12 @@ class _DropdownDialogState extends State { } pop() { - if (widget.dialogBox) { + if (widget.dialogBox!) { Navigator.pop(context); } else { - widget.displayMenu.value = false; + widget.displayMenu!.value = false; if (widget.callOnPop != null) { - widget.callOnPop(); + widget.callOnPop!(); } } } @@ -940,20 +947,20 @@ class _DropdownDialogState extends State { child: Scrollbar( child: new ListView.builder( itemBuilder: (context, index) { - DropdownMenuItem item = widget.items[shownIndexes[index]]; + DropdownMenuItem item = widget.items![shownIndexes[index]]; return new InkWell( onTap: () { - if (widget.multipleSelection) { + if (widget.multipleSelection!) { setState(() { - if (widget.selectedItems.contains(shownIndexes[index])) { - widget.selectedItems.remove(shownIndexes[index]); + if (widget.selectedItems!.contains(shownIndexes[index])) { + widget.selectedItems!.remove(shownIndexes[index]); } else { - widget.selectedItems.add(shownIndexes[index]); + widget.selectedItems!.add(shownIndexes[index]); } }); } else { - widget.selectedItems.clear(); - widget.selectedItems.add(shownIndexes[index]); + widget.selectedItems!.clear(); + widget.selectedItems!.add(shownIndexes[index]); if (widget.doneButton == null) { pop(); } else { @@ -961,11 +968,11 @@ class _DropdownDialogState extends State { } } }, - child: widget.multipleSelection + child: widget.multipleSelection! ? widget.displayItem == null ? (Row(children: [ Icon( - widget.selectedItems.contains(shownIndexes[index]) + widget.selectedItems!.contains(shownIndexes[index]) ? Icons.check_box : Icons.check_box_outline_blank, ), @@ -974,11 +981,11 @@ class _DropdownDialogState extends State { ), Flexible(child: item), ])) - : widget.displayItem(item, - widget.selectedItems.contains(shownIndexes[index])) + : widget.displayItem!(item, + widget.selectedItems!.contains(shownIndexes[index])) : widget.displayItem == null ? item - : widget.displayItem(item, item.value == selectedResult), + : widget.displayItem!(item, item.value == selectedResult), ); }, itemCount: shownIndexes.length, diff --git a/pubspec.lock b/pubspec.lock index 67a7df1..8077d23 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,62 +1,55 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: - archive: - dependency: transitive - description: - name: archive - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.11" - args: - dependency: transitive - description: - name: args - url: "https://pub.dartlang.org" - source: hosted - version: "1.5.2" async: dependency: transitive description: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.4.0" + version: "2.8.1" boolean_selector: dependency: transitive description: name: boolean_selector url: "https://pub.dartlang.org" source: hosted - version: "1.0.5" + version: "2.1.0" + characters: + dependency: transitive + description: + name: characters + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" charcode: dependency: transitive description: name: charcode url: "https://pub.dartlang.org" source: hosted - version: "1.1.2" - collection: + version: "1.3.1" + clock: dependency: transitive description: - name: collection + name: clock url: "https://pub.dartlang.org" source: hosted - version: "1.14.11" - convert: + version: "1.1.0" + collection: dependency: transitive description: - name: convert + name: collection url: "https://pub.dartlang.org" source: hosted - version: "2.1.1" - crypto: + version: "1.15.0" + fake_async: dependency: transitive description: - name: crypto + name: fake_async url: "https://pub.dartlang.org" source: hosted - version: "2.1.3" + version: "1.2.0" flutter: dependency: "direct main" description: flutter @@ -67,55 +60,27 @@ packages: description: flutter source: sdk version: "0.0.0" - image: - dependency: transitive - description: - name: image - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.4" matcher: dependency: transitive description: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.6" + version: "0.12.10" meta: dependency: transitive description: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.1.8" + version: "1.7.0" path: dependency: transitive description: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.6.4" - pedantic: - dependency: transitive - description: - name: pedantic - url: "https://pub.dartlang.org" - source: hosted - version: "1.8.0+1" - petitparser: - dependency: transitive - description: - name: petitparser - url: "https://pub.dartlang.org" - source: hosted - version: "2.4.0" - quiver: - dependency: transitive - description: - name: quiver - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.5" + version: "1.8.0" sky_engine: dependency: transitive description: flutter @@ -127,62 +92,55 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.5.5" + version: "1.8.1" stack_trace: dependency: transitive description: name: stack_trace url: "https://pub.dartlang.org" source: hosted - version: "1.9.3" + version: "1.10.0" stream_channel: dependency: transitive description: name: stream_channel url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.1.0" string_scanner: dependency: transitive description: name: string_scanner url: "https://pub.dartlang.org" source: hosted - version: "1.0.5" + version: "1.1.0" term_glyph: dependency: transitive description: name: term_glyph url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.2.0" test_api: dependency: transitive description: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.2.11" + version: "0.4.2" typed_data: dependency: transitive description: name: typed_data url: "https://pub.dartlang.org" source: hosted - version: "1.1.6" + version: "1.3.0" vector_math: dependency: transitive description: name: vector_math url: "https://pub.dartlang.org" source: hosted - version: "2.0.8" - xml: - dependency: transitive - description: - name: xml - url: "https://pub.dartlang.org" - source: hosted - version: "3.5.0" + version: "2.1.0" sdks: - dart: ">=2.4.0 <3.0.0" + dart: ">=2.12.0 <3.0.0" diff --git a/pubspec.yaml b/pubspec.yaml index 3565d75..f20af5b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,12 +1,12 @@ name: searchable_dropdown description: Widget to let the user search through a keyword string typed on a customizable keyboard in a single or multiple choices list presented as a dropdown in a dialog box or a menu. -version: 1.1.4 +version: 2.0.0 homepage: https://github.com/icemanbsi/searchable_dropdown repository: https://github.com/icemanbsi/searchable_dropdown issue_tracker: https://github.com/icemanbsi/searchable_dropdown/issues environment: - sdk: ">=2.1.0 <3.0.0" + sdk: ">=2.12.0 <3.0.0" dependencies: flutter: diff --git a/test/searchable_dropdown_test.dart b/test/searchable_dropdown_test.dart index 0b96a8d..2a3fb51 100644 --- a/test/searchable_dropdown_test.dart +++ b/test/searchable_dropdown_test.dart @@ -26,7 +26,7 @@ class ExampleNumber { }; String get numberString { - return (map.containsKey(number) ? map[number] : "unknown"); + return (map.containsKey(number) ? map[number]! : "unknown"); } ExampleNumber(this.number); @@ -46,7 +46,7 @@ void main() { testWidgets( 'single dialog open dialog, search keyword, select single value, clear', (WidgetTester tester) async { - String selectedValue; + String? selectedValue; String searchKeyword = "4"; List items = []; for (int i = 0; i < 20; i++) { @@ -60,16 +60,16 @@ void main() { await tester.pumpWidget(MaterialApp( home: Scaffold( appBar: AppBar( - title: const Text('Search Choices plugin test'), + title: Text('Search Choices plugin test'), ), body: Center( child: SearchableDropdown.single( items: items, value: selectedValue, - hint: new Text('Select One'), - searchHint: new Text( + hint: Text('Select One'), + searchHint: Text( 'Search and select one', - style: new TextStyle(fontSize: 20), + style: TextStyle(fontSize: 20), ), onChanged: (value) { // setState(() { @@ -91,22 +91,28 @@ void main() { await tester.tap(nothingSelectedFinder); await tester.pump(); final listViewFinder = find.byType(ListView); - expect(listViewFinder, findsNWidgets(1), - reason: "List of items displayed"); - ListView listView = tester.element(listViewFinder).widget; + if (listViewFinder.runtimeType == ListView) { + expect(listViewFinder, findsNWidgets(1), + reason: "List of items displayed"); + } + ListView listView = (tester.element(listViewFinder).widget as ListView); final textFieldFinder = find.byType(TextField); expect(textFieldFinder, findsNWidgets(1), reason: "Search field displayed"); - expect(listView.semanticChildCount, items.length, - reason: "List of items is complete"); + if (listView.runtimeType == ListView) { + expect(listView.semanticChildCount, items.length, + reason: "List of items is complete"); + } await tester.enterText(textFieldFinder, searchKeyword); await tester.pump(); - listView = tester.element(listViewFinder).widget; + listView = tester.element(listViewFinder).widget as ListView; int expectedNbResults = items.where((it) { return (it.value.toString().contains(searchKeyword)); }).length; - expect(listView.semanticChildCount, expectedNbResults, - reason: "Search filter number of items displayed"); + if (listView.runtimeType == ListView) { + expect(listView.semanticChildCount, expectedNbResults, + reason: "Search filter number of items displayed"); + } String expectedValue = items.firstWhere((it) { return (it.value.toString().contains(searchKeyword)); }).value; @@ -142,7 +148,7 @@ void main() { testWidgets( 'single menu open menu, search keyword, select single value, clear', (WidgetTester tester) async { - String selectedValue; + String? selectedValue; String searchKeyword = "4"; List items = []; for (int i = 0; i < 20; i++) { @@ -191,20 +197,24 @@ void main() { final listViewFinder = find.byType(ListView); expect(listViewFinder, findsNWidgets(1), reason: "List of items displayed"); - ListView listView = tester.element(listViewFinder).widget; + ListView listView = tester.element(listViewFinder).widget as ListView; final textFieldFinder = find.byType(TextField); expect(textFieldFinder, findsNWidgets(1), reason: "Search field displayed"); - expect(listView.semanticChildCount, items.length, - reason: "List of items is complete"); + if (listView.runtimeType == ListView) { + expect(listView.semanticChildCount, items.length, + reason: "List of items is complete"); + } await tester.enterText(textFieldFinder, searchKeyword); await tester.pump(); - listView = tester.element(listViewFinder).widget; + listView = tester.element(listViewFinder).widget as ListView; int expectedNbResults = items.where((it) { return (it.value.toString().contains(searchKeyword)); }).length; - expect(listView.semanticChildCount, expectedNbResults, - reason: "Search filter number of items displayed"); + if (listView.runtimeType == ListView) { + expect(listView.semanticChildCount, expectedNbResults, + reason: "Search filter number of items displayed"); + } String expectedValue = items.firstWhere((it) { return (it.value.toString().contains(searchKeyword)); }).value; @@ -240,7 +250,7 @@ void main() { testWidgets( 'single object dialog open dialog, search keyword, select single value, clear', (WidgetTester tester) async { - ExampleNumber selectedNumber; + ExampleNumber? selectedNumber; String searchKeyword = "4"; List items = ExampleNumber.list.map((exNum) { return (DropdownMenuItem( @@ -283,20 +293,24 @@ void main() { final listViewFinder = find.byType(ListView); expect(listViewFinder, findsNWidgets(1), reason: "List of items displayed"); - ListView listView = tester.element(listViewFinder).widget; + ListView listView = tester.element(listViewFinder).widget as ListView; final textFieldFinder = find.byType(TextField); expect(textFieldFinder, findsNWidgets(1), reason: "Search field displayed"); - expect(listView.semanticChildCount, items.length, - reason: "List of items is complete"); + if (listView.runtimeType == ListView) { + expect(listView.semanticChildCount, items.length, + reason: "List of items is complete"); + } await tester.enterText(textFieldFinder, searchKeyword); await tester.pump(); - listView = tester.element(listViewFinder).widget; + listView = tester.element(listViewFinder).widget as ListView; int expectedNbResults = items.where((it) { return (it.value.toString().contains(searchKeyword)); }).length; - expect(listView.semanticChildCount, expectedNbResults, - reason: "Search filter number of items displayed"); + if (listView.runtimeType == ListView) { + expect(listView.semanticChildCount, expectedNbResults, + reason: "Search filter number of items displayed"); + } ExampleNumber expectedValue = items.firstWhere((it) { return (it.value.toString().contains(searchKeyword)); }).value; @@ -331,7 +345,7 @@ void main() { 'single dialog text no overflow because expanded', (WidgetTester tester) async { String searchKeyword = "at"; - String selectedValue; + String? selectedValue; List items = [ DropdownMenuItem( child: Text( @@ -374,20 +388,24 @@ void main() { final listViewFinder = find.byType(ListView); expect(listViewFinder, findsNWidgets(1), reason: "List of items displayed"); - ListView listView = tester.element(listViewFinder).widget; + ListView listView = tester.element(listViewFinder).widget as ListView; final textFieldFinder = find.byType(TextField); expect(textFieldFinder, findsNWidgets(1), reason: "Search field displayed"); - expect(listView.semanticChildCount, items.length, - reason: "List of items is complete"); + if (listView.runtimeType == ListView) { + expect(listView.semanticChildCount, items.length, + reason: "List of items is complete"); + } await tester.enterText(textFieldFinder, searchKeyword); await tester.pump(); - listView = tester.element(listViewFinder).widget; + listView = tester.element(listViewFinder).widget as ListView; int expectedNbResults = items.where((it) { return (it.value.toString().contains(searchKeyword)); }).length; - expect(listView.semanticChildCount, expectedNbResults, - reason: "Search filter number of items displayed"); + if (listView.runtimeType == ListView) { + expect(listView.semanticChildCount, expectedNbResults, + reason: "Search filter number of items displayed"); + } String expectedValue = items.firstWhere((it) { return (it.value.toString().contains(searchKeyword)); }).value; @@ -423,7 +441,7 @@ void main() { testWidgets( 'multi dialog open dialog, search keyword, select multiple values, clear', (WidgetTester tester) async { - List selectedItems = []; + List? selectedItems = []; String searchKeyword = "4"; List items = []; for (int i = 0; i < 20; i++) { @@ -470,7 +488,7 @@ void main() { final listViewFinder = find.byType(ListView); expect(listViewFinder, findsNWidgets(1), reason: "List of items displayed"); - ListView listView = tester.element(listViewFinder).widget; + ListView listView = tester.element(listViewFinder).widget as ListView; final textFieldFinder = find.byType(TextField); expect(textFieldFinder, findsNWidgets(1), reason: "Search field displayed"); @@ -478,12 +496,14 @@ void main() { reason: "List of items is complete"); await tester.enterText(textFieldFinder, searchKeyword); await tester.pump(); - listView = tester.element(listViewFinder).widget; + listView = tester.element(listViewFinder).widget as ListView; int expectedNbResults = items.where((it) { return (it.value.toString().contains(searchKeyword)); }).length; - expect(listView.semanticChildCount, expectedNbResults, - reason: "Search filter number of items displayed"); + if (listView.runtimeType == ListView) { + expect(listView.semanticChildCount, expectedNbResults, + reason: "Search filter number of items displayed"); + } List expectedList = []; for (int i = 0; i < items.length; i++) { var item = items[i]; @@ -522,7 +542,7 @@ void main() { await tester.tap(clearButtonFinder); await tester.pump(); expect(nothingSelectedFinder, findsNWidgets(1), reason: "No selection"); - expect(selectedItems?.length ?? 0, 0, reason: "selectedValue cleared"); + expect(selectedItems ?? 0, 0, reason: "selectedValue cleared"); }, skip: false, );