diff --git a/example/lib/main.dart b/example/lib/main.dart index a602299..63f965a 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -26,7 +26,7 @@ class ExampleNumber { }; String get numberString { - return (map.containsKey(number) ? map[number] : "unknown"); + return (map.containsKey(number) ? map[number] ?? "unknown" : "unknown"); } ExampleNumber(this.number); @@ -51,8 +51,8 @@ class MyApp extends StatefulWidget { class _MyAppState extends State { bool asTabs = false; - String selectedValue; - String preselectedValue = "dolor sit"; + String selectedValue = ''; + String preselectedValue = 'dolor sit'; ExampleNumber selectedNumber; List selectedItems = []; final List items = []; @@ -64,12 +64,7 @@ class _MyAppState extends State { @override void initState() { String wordPair = ""; - loremIpsum - .toLowerCase() - .replaceAll(",", "") - .replaceAll(".", "") - .split(" ") - .forEach((word) { + loremIpsum.toLowerCase().replaceAll(",", "").replaceAll(".", "").split(" ").forEach((word) { if (wordPair.isEmpty) { wordPair = word + " "; } else { @@ -217,7 +212,7 @@ class _MyAppState extends State { )))); }, doneButton: (selectedItemsDone, doneContext) { - return (RaisedButton( + return (ElevatedButton( onPressed: () { Navigator.pop(doneContext); setState(() {}); @@ -227,16 +222,12 @@ class _MyAppState extends State { closeButton: null, style: TextStyle(fontStyle: FontStyle.italic), searchFn: (String keyword, items) { - List ret = List(); + List ret = []; if (keyword != null && items != null && keyword.isNotEmpty) { keyword.split(" ").forEach((k) { int i = 0; items.forEach((item) { - if (k.isNotEmpty && - (item.value - .toString() - .toLowerCase() - .contains(k.toLowerCase()))) { + if (k.isNotEmpty && (item.value.toString().toLowerCase().contains(k.toLowerCase()))) { ret.add(i); } i++; @@ -253,9 +244,7 @@ class _MyAppState extends State { label: "Label for multi", underline: Container( height: 1.0, - decoration: BoxDecoration( - border: - Border(bottom: BorderSide(color: Colors.teal, width: 3.0))), + decoration: BoxDecoration(border: Border(bottom: BorderSide(color: Colors.teal, width: 3.0))), ), iconDisabledColor: Colors.brown, iconEnabledColor: Colors.indigo, @@ -278,7 +267,7 @@ class _MyAppState extends State { }); }, doneButton: (selectedItemsDone, doneContext) { - return (RaisedButton( + return (ElevatedButton( onPressed: selectedItemsDone.length != 3 ? null : () { @@ -337,16 +326,15 @@ class _MyAppState extends State { return Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - RaisedButton( + ElevatedButton( onPressed: () { setState(() { selectedItems.clear(); - selectedItems.addAll( - Iterable.generate(items.length).toList()); + selectedItems.addAll(Iterable.generate(items.length).toList()); }); }, child: Text("Select all")), - RaisedButton( + ElevatedButton( onPressed: () { setState(() { selectedItems.clear(); @@ -375,16 +363,15 @@ class _MyAppState extends State { return Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - RaisedButton( + ElevatedButton( onPressed: () { setState(() { selectedItems.clear(); - selectedItems.addAll( - Iterable.generate(items.length).toList()); + selectedItems.addAll(Iterable.generate(items.length).toList()); }); }, child: Text("Select all")), - RaisedButton( + ElevatedButton( onPressed: () { setState(() { selectedItems.clear(); @@ -417,8 +404,7 @@ class _MyAppState extends State { ), "Single dialog object": SearchableDropdown.single( items: ExampleNumber.list.map((exNum) { - return (DropdownMenuItem( - child: Text(exNum.numberString), value: exNum)); + return (DropdownMenuItem(child: Text(exNum.numberString), value: exNum)); }).toList(), value: selectedNumber, hint: "Select one number", @@ -518,7 +504,7 @@ class _MyAppState extends State { }, isExpanded: true, ), - FlatButton( + ElevatedButton( child: Text("Select $preselectedValue"), onPressed: () { setState(() { @@ -540,9 +526,7 @@ class _MyAppState extends State { actions: appBarActions, bottom: TabBar( isScrollable: true, - tabs: Iterable.generate(widgets.length) - .toList() - .map((i) { + tabs: Iterable.generate(widgets.length).toList().map((i) { return (Tab( text: (i + 1).toString(), )); diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 26d813f..0559f1e 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -4,7 +4,7 @@ version: 0.0.1 publish_to: 'none' environment: - sdk: ">=2.1.0 <3.0.0" + sdk: '>=3.1.0 <4.0.0' dependencies: flutter: diff --git a/lib/searchable_dropdown.dart b/lib/searchable_dropdown.dart index cb9a2f4..e010a0f 100644 --- a/lib/searchable_dropdown.dart +++ b/lib/searchable_dropdown.dart @@ -1,8 +1,6 @@ import 'package:flutter/material.dart'; -import 'package:flutter/rendering.dart'; -const EdgeInsetsGeometry _kAlignedButtonPadding = - EdgeInsetsDirectional.only(start: 16.0, end: 4.0); +const EdgeInsetsGeometry _kAlignedButtonPadding = EdgeInsetsDirectional.only(start: 16.0, end: 4.0); const EdgeInsets _kUnalignedButtonPadding = EdgeInsets.zero; class NotGiven { @@ -14,10 +12,7 @@ class PointerThisPlease { PointerThisPlease(this.value); } -Widget prepareWidget(dynamic object, - {dynamic parameter = const NotGiven(), - BuildContext context, - Function stringToWidgetFunction}) { +Widget? prepareWidget(dynamic object, {dynamic parameter = const NotGiven(), BuildContext? context, Function? stringToWidgetFunction}) { if (object == null) { return (null); } @@ -34,28 +29,24 @@ Widget prepareWidget(dynamic object, if (object is Function) { if (parameter is NotGiven) { if (context == null) { - return (prepareWidget(object(), - stringToWidgetFunction: stringToWidgetFunction)); + return (prepareWidget(object(), stringToWidgetFunction: stringToWidgetFunction)); } else { - return (prepareWidget(object(context), - stringToWidgetFunction: stringToWidgetFunction)); + return (prepareWidget(object(context), stringToWidgetFunction: stringToWidgetFunction)); } } if (context == null) { - return (prepareWidget(object(parameter), - stringToWidgetFunction: stringToWidgetFunction)); + return (prepareWidget(object(parameter), stringToWidgetFunction: stringToWidgetFunction)); } - return (prepareWidget(object(parameter, context), - stringToWidgetFunction: stringToWidgetFunction)); + return (prepareWidget(object(parameter, context), stringToWidgetFunction: stringToWidgetFunction)); } return (Text("Unknown type: ${object.runtimeType.toString()}")); } class SearchableDropdown extends StatefulWidget { final List> items; - final Function onChanged; - final T value; - final TextStyle style; + final Function? onChanged; + final T? value; + final TextStyle? style; final dynamic searchHint; final dynamic hint; final dynamic disabledHint; @@ -66,23 +57,23 @@ class SearchableDropdown extends StatefulWidget { final dynamic closeButton; final bool displayClearIcon; final Icon clearIcon; - final Color iconEnabledColor; - final Color iconDisabledColor; + 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 Function? displayItem; + final bool? dialogBox; + final BoxConstraints? menuConstraints; final bool readOnly; - final Color menuBackgroundColor; + 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 +108,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 +123,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 +207,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,21 +222,21 @@ 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, @@ -283,8 +274,8 @@ class SearchableDropdown extends StatefulWidget { } SearchableDropdown._({ - Key key, - @required this.items, + Key? key, + required this.items, this.onChanged, this.value, this.style, @@ -315,17 +306,14 @@ class SearchableDropdown extends StatefulWidget { this.menuConstraints, this.readOnly = false, this.menuBackgroundColor, - }) : assert(items != null), - assert(iconSize != null), - assert(isExpanded != null), - assert(!multipleSelection || doneButton != null), - assert(menuConstraints == null || !dialogBox), + }) : assert(!multipleSelection || doneButton != null), + 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, @@ -355,35 +343,26 @@ class SearchableDropdown extends StatefulWidget { this.menuConstraints, this.readOnly = false, this.menuBackgroundColor, - }) : assert(items != null), - assert(iconSize != null), - assert(isExpanded != null), - assert(!multipleSelection || doneButton != null), - assert(menuConstraints == null || !dialogBox), + }) : assert(!multipleSelection || doneButton != null), + assert(menuConstraints == null || !dialogBox!), super(key: key); @override - _SearchableDropdownState createState() => new _SearchableDropdownState(); + _SearchableDropdownState createState() => _SearchableDropdownState(); } -class _SearchableDropdownState extends State> { - List selectedItems; +class _SearchableDropdownState extends State> { + var 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 - .subhead - .copyWith(color: _disabledIconColor)); - bool get _enabled => - widget.items != null && - widget.items.isNotEmpty && - widget.onChanged != null; - - Color get _enabledIconColor { + (_enabled && !(widget.readOnly) + ? Theme.of(context).textTheme.titleMedium + : Theme.of(context).textTheme.titleMedium!.copyWith(color: _disabledIconColor)); + bool get _enabled => widget.items.isNotEmpty && widget.onChanged != null; + + Color? get _enabledIconColor { if (widget.iconEnabledColor != null) { return widget.iconEnabledColor; } @@ -393,10 +372,9 @@ class _SearchableDropdownState extends State> { case Brightness.dark: return Colors.white70; } - return Colors.grey.shade700; } - Color get _disabledIconColor { + Color? get _disabledIconColor { if (widget.iconDisabledColor != null) { return widget.iconDisabledColor; } @@ -406,36 +384,33 @@ class _SearchableDropdownState extends State> { case Brightness.dark: return Colors.white10; } - return Colors.grey.shade400; } - Color get _iconColor { + Color? get _iconColor { // These colors are not defined in the Material Design spec. - return (_enabled && !(widget.readOnly ?? false) - ? _enabledIconColor - : _disabledIconColor); + return (_enabled && !(widget.readOnly) ? _enabledIconColor : _disabledIconColor); } bool get valid { 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.isNotEmpty; } dynamic get selectedResult { return (widget.multipleSelection ? selectedItems - : selectedItems?.isNotEmpty ?? false - ? widget.items[selectedItems.first]?.value + : (selectedItems.isNotEmpty && widget.items.isNotEmpty) + ? widget.items[selectedItems.first].value : null); } - int indexFromValue(T value) { + int indexFromValue(T? value) { return (widget.items.indexWhere((item) { return (item.value == value); })); @@ -452,19 +427,18 @@ class _SearchableDropdownState extends State> { return; } if (widget.multipleSelection) { - selectedItems = List.from(widget.selectedItems ?? []); + selectedItems = List.from(widget.selectedItems); } else if (widget.value != null) { int i = indexFromValue(widget.value); - if (i != null && i != -1) { + if (i != -1) { selectedItems = [i]; } } - if (selectedItems == null) selectedItems = []; } @override void didUpdateWidget(SearchableDropdown oldWidget) { - super.didUpdateWidget(oldWidget); + super.didUpdateWidget(oldWidget as SearchableDropdown); _updateSelectedIndex(); } @@ -486,10 +460,8 @@ class _SearchableDropdownState extends State> { menuConstraints: widget.menuConstraints, menuBackgroundColor: widget.menuBackgroundColor, callOnPop: () { - if (!widget.dialogBox && - widget.onChanged != null && - selectedItems != null) { - widget.onChanged(selectedResult); + if (!widget.dialogBox! && widget.onChanged != null) { + widget.onChanged!(selectedResult); } setState(() {}); }, @@ -498,58 +470,53 @@ class _SearchableDropdownState extends State> { @override Widget build(BuildContext context) { - final List items = - _enabled ? List.from(widget.items) : []; - int hintIndex; - if (widget.hint != null || - (!_enabled && prepareWidget(widget.disabledHint) != null)) { - final Widget emplacedHint = _enabled + final List items = _enabled ? List.from(widget.items) : []; + int? hintIndex; + if (widget.hint != null || (!_enabled && prepareWidget(widget.disabledHint) != null)) { + final Widget? emplacedHint = _enabled ? prepareWidget(widget.hint) - : DropdownMenuItem( - child: prepareWidget(widget.disabledHint) ?? - prepareWidget(widget.hint)); + : DropdownMenuItem(child: prepareWidget(widget.disabledHint) ?? prepareWidget(widget.hint)!); hintIndex = items.length; items.add(DefaultTextStyle( - style: _textStyle.copyWith(color: Theme.of(context).hintColor), - child: IgnorePointer( - child: emplacedHint, - ignoringSemantics: false, - ), + style: _textStyle!.copyWith(color: Theme.of(context).hintColor), + child: IgnorePointer(child: ExcludeSemantics(child: emplacedHint)), )); } Widget innerItemsWidget; - List list = List(); - selectedItems?.forEach((item) { - list.add(widget.selectedValueWidgetFn != null - ? widget.selectedValueWidgetFn(widget.items[item].value) - : items[item]); - }); + var list = []; + try { + selectedItems.forEach((item) { + if (widget.selectedValueWidgetFn != null) { + if (widget.items.length <= item) { + return; + } + } else if (items.length <= item) { + return; + } + list.add(widget.selectedValueWidgetFn != null ? widget.selectedValueWidgetFn!(widget.items[item].value) : items[item]); + }); + } catch (e) {} if (list.isEmpty && hintIndex != null) { innerItemsWidget = items[hintIndex]; } else { - innerItemsWidget = Column( - children: list, - ); + innerItemsWidget = Column(children: list); } - final EdgeInsetsGeometry padding = ButtonTheme.of(context).alignedDropdown - ? _kAlignedButtonPadding - : _kUnalignedButtonPadding; + final EdgeInsetsGeometry padding = ButtonTheme.of(context).alignedDropdown ? _kAlignedButtonPadding : _kUnalignedButtonPadding; Widget clickable = InkWell( - key: Key( - "clickableResultPlaceHolder"), //this key is used for running automated tests - onTap: (widget.readOnly ?? false) || !_enabled + key: Key("clickableResultPlaceHolder"), //this key is used for running automated tests + onTap: (widget.readOnly) || !_enabled ? null : () async { - if (widget.dialogBox) { + if (widget.dialogBox!) { await showDialog( context: context, barrierDismissible: true, builder: (context) { return (menuWidget); }); - if (widget.onChanged != null && selectedItems != null) { - widget.onChanged(selectedResult); + if (widget.onChanged != null) { + widget.onChanged!(selectedResult); } } else { displayMenu.value = true; @@ -558,22 +525,19 @@ class _SearchableDropdownState extends State> { }, child: Row( children: [ - widget.isExpanded - ? Expanded(child: innerItemsWidget) - : innerItemsWidget, + widget.isExpanded ? Expanded(child: innerItemsWidget) : innerItemsWidget, IconTheme( data: IconThemeData( color: _iconColor, size: widget.iconSize, ), - child: prepareWidget(widget.icon, parameter: selectedResult) ?? - SizedBox.shrink(), + child: prepareWidget(widget.icon, parameter: selectedResult) ?? SizedBox.shrink(), ), ], )); Widget result = DefaultTextStyle( - style: _textStyle, + style: _textStyle!, child: Container( padding: padding.resolve(Directionality.of(context)), child: Row( @@ -597,13 +561,10 @@ class _SearchableDropdownState extends State> { children: [ IconTheme( data: IconThemeData( - color: - hasSelection && _enabled && !widget.readOnly - ? _enabledIconColor - : _disabledIconColor, + color: hasSelection && _enabled && !widget.readOnly ? _enabledIconColor : _disabledIconColor, size: widget.iconSize, ), - child: widget.clearIcon ?? Icon(Icons.clear), + child: widget.clearIcon, ), ], ), @@ -617,12 +578,10 @@ 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) { - return (Text(string, - style: TextStyle(color: Colors.blueAccent, fontSize: 13))); + var labelOutput = prepareWidget(widget.label, parameter: selectedResult, stringToWidgetFunction: (string) { + return (Text(string, style: TextStyle(color: Colors.blueAccent, fontSize: 13))); }); return Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -640,17 +599,11 @@ class _SearchableDropdownState extends State> { left: 0.0, right: 0.0, bottom: bottom, - child: prepareWidget(widget.underline, - parameter: selectedResult) ?? + child: prepareWidget(widget.underline, parameter: selectedResult) ?? Container( height: 1.0, - decoration: BoxDecoration( - border: Border( - bottom: BorderSide( - color: valid - ? Color(0xFFBDBDBD) - : Colors.red, - width: 0.0))), + decoration: + BoxDecoration(border: Border(bottom: BorderSide(color: valid ? Color(0xFFBDBDBD) : Colors.red, width: 0.0))), ), ), ], @@ -671,10 +624,10 @@ class _SearchableDropdownState extends State> { clearSelection() { selectedItems.clear(); if (widget.onChanged != null) { - widget.onChanged(selectedResult); + widget.onChanged!(selectedResult); } if (widget.onClear != null) { - widget.onClear(); + widget.onClear!(); } setState(() {}); } @@ -682,25 +635,25 @@ class _SearchableDropdownState extends State> { class DropdownDialog extends StatefulWidget { final List> items; - final Widget hint; + 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, - this.items, + Key? key, + required this.items, this.hint, this.isCaseSensitiveSearch = false, this.closeButton, @@ -716,31 +669,30 @@ class DropdownDialog extends StatefulWidget { this.menuConstraints, this.callOnPop, this.menuBackgroundColor, - }) : assert(items != null), - super(key: key); + }) : super(key: key); - _DropdownDialogState createState() => new _DropdownDialogState(); + _DropdownDialogState createState() => _DropdownDialogState(); } class _DropdownDialogState extends State { - TextEditingController txtSearch = new TextEditingController(); - TextStyle defaultButtonStyle = - new TextStyle(fontSize: 16, fontWeight: FontWeight.w500); - List shownIndexes = []; - Function searchFn; + TextEditingController txtSearch = TextEditingController(); + final scrollController = ScrollController(); + TextStyle defaultButtonStyle = TextStyle(fontSize: 16, fontWeight: FontWeight.w500); + List? shownIndexes = []; + 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 @@ -755,10 +707,7 @@ class _DropdownDialogState extends State { }; } else { matchFn = (item, keyword) { - return (item.value - .toString() - .toLowerCase() - .contains(keyword.toLowerCase())); + return (item.value.toString().toLowerCase().contains(keyword.toLowerCase())); }; } searchFn = (keyword, items) { @@ -783,15 +732,13 @@ class _DropdownDialogState extends State { return AnimatedContainer( padding: MediaQuery.of(context).viewInsets, duration: const Duration(milliseconds: 300), - child: new Card( + child: Card( color: widget.menuBackgroundColor, - margin: EdgeInsets.symmetric( - vertical: widget.dialogBox ? 10 : 5, - horizontal: widget.dialogBox ? 10 : 4), - child: new Container( + margin: EdgeInsets.symmetric(vertical: widget.dialogBox! ? 10 : 5, horizontal: widget.dialogBox! ? 10 : 4), + child: Container( constraints: widget.menuConstraints, padding: EdgeInsets.symmetric(vertical: 15, horizontal: 15), - child: new Column( + child: Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, @@ -811,16 +758,16 @@ 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 + Widget? validatorOutputWidget = valid ? SizedBox.shrink() : validatorOutput is String ? Text( @@ -829,50 +776,43 @@ class _DropdownDialogState extends State { ) : validatorOutput; - Widget doneButtonWidget = - widget.multipleSelection || widget.doneButton != null - ? prepareWidget(widget.doneButton, - parameter: selectedResult, - context: context, stringToWidgetFunction: (string) { - return (FlatButton.icon( - onPressed: !valid - ? null - : () { - pop(); - setState(() {}); - }, - icon: Icon(Icons.close), - label: Text(string))); - }) - : SizedBox.shrink(); + Widget? doneButtonWidget = widget.multipleSelection! || widget.doneButton != null + ? prepareWidget(widget.doneButton, parameter: selectedResult, context: context, stringToWidgetFunction: (string) { + return (TextButton.icon( + onPressed: !valid + ? null + : () { + pop(); + setState(() {}); + }, + icon: Icon(Icons.close), + label: Text(string))); + }) + : SizedBox.shrink(); return widget.hint != null - ? new Container( + ? Container( margin: EdgeInsets.only(bottom: 8), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - prepareWidget(widget.hint), - Column( - children: [doneButtonWidget, validatorOutputWidget], - ), - ]), + child: Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ + prepareWidget(widget.hint)!, + Column( + children: [doneButtonWidget!, validatorOutputWidget!], + ), + ]), ) - : new Container( + : Container( child: Column( - children: [doneButtonWidget, validatorOutputWidget], + children: [doneButtonWidget!, validatorOutputWidget!], ), ); } Widget searchBar() { - return new Container( - child: new Stack( + return Container( + child: Stack( children: [ - new TextField( + TextField( controller: txtSearch, - decoration: InputDecoration( - contentPadding: - EdgeInsets.symmetric(horizontal: 32, vertical: 12)), + decoration: InputDecoration(contentPadding: EdgeInsets.symmetric(horizontal: 32, vertical: 12)), autofocus: true, onChanged: (value) { _updateShownIndexes(value); @@ -880,24 +820,24 @@ class _DropdownDialogState extends State { }, keyboardType: widget.keyboardType, ), - new Positioned( + Positioned( left: 0, top: 0, bottom: 0, - child: new Center( - child: new Icon( + child: Center( + child: Icon( Icons.search, size: 24, ), ), ), txtSearch.text.isNotEmpty - ? new Positioned( + ? Positioned( right: 0, top: 0, bottom: 0, - child: new Center( - child: new InkWell( + child: Center( + child: InkWell( onTap: () { _updateShownIndexes(''); setState(() { @@ -905,11 +845,11 @@ class _DropdownDialogState extends State { }); }, borderRadius: BorderRadius.all(Radius.circular(32)), - child: new Container( + child: Container( width: 32, height: 32, - child: new Center( - child: new Icon( + child: Center( + child: Icon( Icons.close, size: 24, ), @@ -918,42 +858,44 @@ class _DropdownDialogState extends State { ), ), ) - : new Container(), + : Container(), ], ), ); } 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!(); } } } Widget list() { - return new Expanded( + return Expanded( child: Scrollbar( - child: new ListView.builder( + controller: scrollController, + child: ListView.builder( + controller: scrollController, itemBuilder: (context, index) { - DropdownMenuItem item = widget.items[shownIndexes[index]]; - return new InkWell( + DropdownMenuItem item = widget.items[shownIndexes![index]]; + return 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,47 +903,42 @@ class _DropdownDialogState extends State { } } }, - child: widget.multipleSelection + child: widget.multipleSelection! ? widget.displayItem == null ? (Row(children: [ Icon( - widget.selectedItems.contains(shownIndexes[index]) - ? Icons.check_box - : Icons.check_box_outline_blank, + widget.selectedItems!.contains(shownIndexes![index]) ? Icons.check_box : Icons.check_box_outline_blank, ), SizedBox( width: 7, ), 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, + itemCount: shownIndexes!.length, ), ), ); } Widget closeButtonWrapper() { - return (prepareWidget(widget.closeButton, parameter: selectedResult, - stringToWidgetFunction: (string) { + return (prepareWidget(widget.closeButton, parameter: selectedResult, stringToWidgetFunction: (string) { return (Container( child: Row( crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.end, children: [ - FlatButton( + TextButton( onPressed: () { pop(); }, child: Container( - constraints: BoxConstraints( - maxWidth: MediaQuery.of(context).size.width / 2), + constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width / 2), child: Text( string, style: defaultButtonStyle, diff --git a/pubspec.lock b/pubspec.lock index 67a7df1..203ecd5 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,62 +1,54 @@ # 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" + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "2.11.0" boolean_selector: dependency: transitive description: name: boolean_selector - url: "https://pub.dartlang.org" + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + url: "https://pub.dev" source: hosted - version: "1.0.5" - charcode: + version: "2.1.1" + characters: dependency: transitive description: - name: charcode - url: "https://pub.dartlang.org" + name: characters + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + url: "https://pub.dev" source: hosted - version: "1.1.2" - collection: + version: "1.3.0" + clock: dependency: transitive description: - name: collection - url: "https://pub.dartlang.org" + name: clock + sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + url: "https://pub.dev" source: hosted - version: "1.14.11" - convert: + version: "1.1.1" + collection: dependency: transitive description: - name: convert - url: "https://pub.dartlang.org" + name: collection + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + url: "https://pub.dev" source: hosted - version: "2.1.1" - crypto: + version: "1.18.0" + fake_async: dependency: transitive description: - name: crypto - url: "https://pub.dartlang.org" + name: fake_async + sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + url: "https://pub.dev" source: hosted - version: "2.1.3" + version: "1.3.1" flutter: dependency: "direct main" description: flutter @@ -67,55 +59,62 @@ packages: description: flutter source: sdk version: "0.0.0" - image: + leak_tracker: dependency: transitive description: - name: image - url: "https://pub.dartlang.org" + name: leak_tracker + sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" + url: "https://pub.dev" source: hosted - version: "2.1.4" - matcher: + version: "10.0.4" + leak_tracker_flutter_testing: dependency: transitive description: - name: matcher - url: "https://pub.dartlang.org" + name: leak_tracker_flutter_testing + sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" + url: "https://pub.dev" source: hosted - version: "0.12.6" - meta: + version: "3.0.3" + leak_tracker_testing: dependency: transitive description: - name: meta - url: "https://pub.dartlang.org" + name: leak_tracker_testing + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + url: "https://pub.dev" source: hosted - version: "1.1.8" - path: + version: "3.0.1" + matcher: dependency: transitive description: - name: path - url: "https://pub.dartlang.org" + name: matcher + sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb + url: "https://pub.dev" source: hosted - version: "1.6.4" - pedantic: + version: "0.12.16+1" + material_color_utilities: dependency: transitive description: - name: pedantic - url: "https://pub.dartlang.org" + name: material_color_utilities + sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + url: "https://pub.dev" source: hosted - version: "1.8.0+1" - petitparser: + version: "0.8.0" + meta: dependency: transitive description: - name: petitparser - url: "https://pub.dartlang.org" + name: meta + sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" + url: "https://pub.dev" source: hosted - version: "2.4.0" - quiver: + version: "1.12.0" + path: dependency: transitive description: - name: quiver - url: "https://pub.dartlang.org" + name: path + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + url: "https://pub.dev" source: hosted - version: "2.0.5" + version: "1.9.0" sky_engine: dependency: transitive description: flutter @@ -125,64 +124,66 @@ packages: dependency: transitive description: name: source_span - url: "https://pub.dartlang.org" + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + url: "https://pub.dev" source: hosted - version: "1.5.5" + version: "1.10.0" stack_trace: dependency: transitive description: name: stack_trace - url: "https://pub.dartlang.org" + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + url: "https://pub.dev" source: hosted - version: "1.9.3" + version: "1.11.1" stream_channel: dependency: transitive description: name: stream_channel - url: "https://pub.dartlang.org" + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + url: "https://pub.dev" source: hosted - version: "2.0.0" + version: "2.1.2" string_scanner: dependency: transitive description: name: string_scanner - url: "https://pub.dartlang.org" + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + url: "https://pub.dev" source: hosted - version: "1.0.5" + version: "1.2.0" term_glyph: dependency: transitive description: name: term_glyph - url: "https://pub.dartlang.org" + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.2.1" test_api: dependency: transitive description: name: test_api - url: "https://pub.dartlang.org" + sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" + url: "https://pub.dev" source: hosted - version: "0.2.11" - typed_data: - dependency: transitive - description: - name: typed_data - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.6" + version: "0.7.0" vector_math: dependency: transitive description: name: vector_math - url: "https://pub.dartlang.org" + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" source: hosted - version: "2.0.8" - xml: + version: "2.1.4" + vm_service: dependency: transitive description: - name: xml - url: "https://pub.dartlang.org" + name: vm_service + sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" + url: "https://pub.dev" source: hosted - version: "3.5.0" + version: "14.2.1" sdks: - dart: ">=2.4.0 <3.0.0" + dart: ">=3.3.0 <4.0.0" + flutter: ">=3.18.0-18.0.pre.54" diff --git a/pubspec.yaml b/pubspec.yaml index 3565d75..fb687c1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -6,7 +6,7 @@ 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: '>=3.1.0 <4.0.0' dependencies: flutter: diff --git a/test/searchable_dropdown_test.dart b/test/searchable_dropdown_test.dart index 0b96a8d..fd7cf47 100644 --- a/test/searchable_dropdown_test.dart +++ b/test/searchable_dropdown_test.dart @@ -25,7 +25,7 @@ class ExampleNumber { 15: "fifteen", }; - String get numberString { + String? get numberString { return (map.containsKey(number) ? map[number] : "unknown"); } @@ -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++) { @@ -93,7 +93,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"); @@ -101,7 +101,7 @@ 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; @@ -142,7 +142,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,7 +191,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"); @@ -199,7 +199,7 @@ 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; @@ -240,12 +240,12 @@ 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( key: Key("dropdown${exNum.number}"), - child: Text(exNum.numberString), + child: Text(exNum.numberString!), value: exNum)); }).toList(); await tester.pumpWidget(MaterialApp( @@ -283,7 +283,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"); @@ -291,7 +291,7 @@ 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; @@ -314,7 +314,7 @@ void main() { reason: "Something selected"); final valueSelectedFinder = find.descendant( of: clickableResultPlaceHolderFinder, - matching: find.text(expectedValue.numberString)); + matching: find.text(expectedValue.numberString!)); expect(valueSelectedFinder, findsNWidgets(1), reason: "Expected is selected"); final clearButtonFinder = find.byIcon(Icons.clear); @@ -331,7 +331,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,7 +374,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"); @@ -382,7 +382,7 @@ 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; @@ -470,7 +470,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,7 +478,7 @@ 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; @@ -499,7 +499,7 @@ void main() { } } await tester.pump(); - final doneButtonFinder = find.widgetWithText(FlatButton, "Close"); + final doneButtonFinder = find.widgetWithText(TextButton, "Close"); expect(doneButtonFinder, findsNWidgets(1), reason: "Done button"); await tester.tap(doneButtonFinder); await tester.pump(); @@ -522,7 +522,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.length, 0, reason: "selectedValue cleared"); }, skip: false, );