diff --git a/analysis_options.yaml b/analysis_options.yaml index a5744c1..5623e76 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,4 +1,7 @@ + include: package:flutter_lints/flutter.yaml -# Additional information about this file can be found at -# https://dart.dev/guides/language/analysis-options +linter: + + rules: + avoid_print: true # Uncomment to disable the `avoid_print` rule \ No newline at end of file diff --git a/example/lib/main.dart b/example/lib/main.dart index 6d3ef02..bea4976 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,3 +1,4 @@ +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_simple_multiselect/flutter_simple_multiselect.dart'; @@ -38,22 +39,22 @@ class _MyHomePageState extends State { final _formKey = GlobalKey(); List> testData = [ - {"uuid": 1, "name": "Alfred Johanson"}, - {"uuid": 2, "name": "Goran Borovic"}, - {"uuid": 3, "name": "Ivan Horvat"}, - {"uuid": 4, "name": "Bjorn Sigurdson"} + {'uuid': 1, 'name': 'Alfred Johanson'}, + {'uuid': 2, 'name': 'Goran Borovic'}, + {'uuid': 3, 'name': 'Ivan Horvat'}, + {'uuid': 4, 'name': 'Bjorn Sigurdson'} ]; Future>> searchFunction(query) async { return testData.where((element) { - return element["name"].toLowerCase().contains(query.toLowerCase()); + return element['name'].toLowerCase().contains(query.toLowerCase()); }).toList(); } Future>> searchFunctionAsync(query) async { return Future.delayed(const Duration(seconds: 1), () { return testData.where((element) { - return element["name"].toLowerCase().contains(query.toLowerCase()); + return element['name'].toLowerCase().contains(query.toLowerCase()); }).toList(); }); } @@ -83,21 +84,21 @@ class _MyHomePageState extends State { children: [ const Padding( padding: EdgeInsets.only(bottom: 10), - child: Text("Static data multiselect")), + child: Text('Static data multiselect')), _staticData(), const Padding( padding: EdgeInsets.only(bottom: 10, top: 20), - child: Text("Async data multiselect")), + child: Text('Async data multiselect')), _asyncData(), const Padding( padding: EdgeInsets.only(bottom: 10, top: 20), - child: Text("Data single select")), + child: Text('Data single select')), _staticSingleData(), Padding( padding: const EdgeInsets.symmetric(vertical: 16.0), child: Center( child: ElevatedButton( - child: Text("submit"), + child: const Text('submit'), onPressed: () { // Validate returns true if the form is valid, or false otherwise. if (_formKey.currentState!.validate()) {} @@ -123,9 +124,11 @@ class _MyHomePageState extends State { borderRadius: 5, borderSize: 1, validator: (value) { - print("--- ${value}"); - if (value == null || value == "") { - return "Required"; + if (kDebugMode) { + print('--- $value'); + } + if (value == null || value == '') { + return 'Required'; } return null; }, @@ -135,7 +138,7 @@ class _MyHomePageState extends State { length: 1, tagBuilder: (context, index) => SelectTag( index: index, - label: selectedItems[index]["name"], + label: selectedItems[index]['name'], onDeleted: (value) { selectedItems.removeAt(index); setState(() {}); @@ -150,12 +153,12 @@ class _MyHomePageState extends State { selectedColor: Colors.white, selectedTileColor: Colors.green, dense: true, - title: Text(data["name"].toString()), + title: Text(data['name'].toString()), onTap: () { singleItem = existing ? null : data; state.selectAndClose(data, - singleItem != null ? singleItem!["name"].toString() : ""); + singleItem != null ? singleItem!['name'].toString() : ''); setState(() {}); }), ); @@ -187,7 +190,7 @@ class _MyHomePageState extends State { length: selectedItems.length, tagBuilder: (context, index) => SelectTag( index: index, - label: selectedItems[index]["name"], + label: selectedItems[index]['name'], onDeleted: (value) { selectedItems.removeAt(index); setState(() {}); @@ -195,7 +198,7 @@ class _MyHomePageState extends State { ), suggestionBuilder: (context, state, data) { var existingIndex = selectedItems - .indexWhere((element) => element["uuid"] == data["uuid"]); + .indexWhere((element) => element['uuid'] == data['uuid']); var selectedData = data; return Material( child: ListTile( @@ -204,7 +207,7 @@ class _MyHomePageState extends State { trailing: existingIndex >= 0 ? const Icon(Icons.check) : null, selectedColor: Colors.white, selectedTileColor: Colors.green, - title: Text(selectedData["name"].toString()), + title: Text(selectedData['name'].toString()), onTap: () { if (existingIndex >= 0) { selectedItems.removeAt(existingIndex); @@ -241,9 +244,11 @@ class _MyHomePageState extends State { resetTextOnSubmitted: true, minTextFieldWidth: 300, validator: (value) { - print("--- ${selectedItemsAsync.length}"); + if (kDebugMode) { + print('--- ${selectedItemsAsync.length}'); + } if (selectedItemsAsync.length < 2) { - return "Min 2 items required"; + return 'Min 2 items required'; } return null; }, @@ -252,7 +257,7 @@ class _MyHomePageState extends State { isLoading: isLoading, tagBuilder: (context, index) => SelectTag( index: index, - label: selectedItemsAsync[index]["name"], + label: selectedItemsAsync[index]['name'], onDeleted: (value) { selectedItemsAsync.removeAt(index); setState(() {}); @@ -260,7 +265,7 @@ class _MyHomePageState extends State { ), suggestionBuilder: (context, state, data) { var existingIndex = selectedItemsAsync - .indexWhere((element) => element["uuid"] == data["uuid"]); + .indexWhere((element) => element['uuid'] == data['uuid']); var selectedData = data; return Material( child: ListTile( @@ -268,10 +273,10 @@ class _MyHomePageState extends State { trailing: existingIndex >= 0 ? const Icon(Icons.check) : null, selectedColor: Colors.white, selectedTileColor: Colors.green, - title: Text(selectedData["name"].toString()), + title: Text(selectedData['name'].toString()), onTap: () { var existingIndex = selectedItemsAsync.indexWhere( - (element) => element["uuid"] == data["uuid"]); + (element) => element['uuid'] == data['uuid']); if (existingIndex >= 0) { selectedItemsAsync.removeAt(existingIndex); } else { diff --git a/lib/flutter_multiselect_layout.dart b/lib/flutter_multiselect_layout.dart index 084e557..25aeb76 100644 --- a/lib/flutter_multiselect_layout.dart +++ b/lib/flutter_multiselect_layout.dart @@ -6,8 +6,8 @@ import './flutter_multiselect_layout_delegate.dart'; /// overrided [createRenderObject] to use custom [RenderCustomMultiChildLayoutBox] class FlutterMultiselectLayout extends CustomMultiChildLayout { FlutterMultiselectLayout({ - Key? key, required FlutterMultiselectLayoutDelegate delegate, + Key? key, List children = const [], }) : super(key: key, children: children, delegate: delegate); diff --git a/lib/flutter_multiselect_layout_delegate.dart b/lib/flutter_multiselect_layout_delegate.dart index 5d230c6..fd7067b 100644 --- a/lib/flutter_multiselect_layout_delegate.dart +++ b/lib/flutter_multiselect_layout_delegate.dart @@ -2,6 +2,8 @@ import 'dart:math'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; +/// This is the layout delegate that is used to layout the tags and the text field. +/// It is used in [FlutterMultiselectLayout] class FlutterMultiselectLayoutDelegate extends MultiChildLayoutDelegate { FlutterMultiselectLayoutDelegate( {required this.length, @@ -86,7 +88,7 @@ class FlutterMultiselectLayoutDelegate extends MultiChildLayoutDelegate { var textFieldSize = Size.zero; - //* Layout the textbox + //* Layout the TextBox if (hasChild(textFieldId)) { final currentRowWidth = tagSizes.fold(0, (result, tag) { return result + tag.width; @@ -94,7 +96,7 @@ class FlutterMultiselectLayoutDelegate extends MultiChildLayoutDelegate { final spacingWidth = spacing * max(tagSizes.length - 1, 0); final leftOverWidth = size.width - currentRowWidth - spacingWidth; final textWidth = max(leftOverWidth, minTextFieldWidth); - //* Check if Textbox is overflowing + //* Check if TextBox is overflowing //* Check if overflowing if (_isOverflow( childWidth: textWidth, diff --git a/lib/flutter_multiselect_render_layout_box.dart b/lib/flutter_multiselect_render_layout_box.dart index 833bee0..9355952 100644 --- a/lib/flutter_multiselect_render_layout_box.dart +++ b/lib/flutter_multiselect_render_layout_box.dart @@ -6,8 +6,8 @@ import 'flutter_multiselect_layout_delegate.dart'; class FlutterMultiselectRenderLayoutBox extends RenderCustomMultiChildLayoutBox { FlutterMultiselectRenderLayoutBox({ - List? children, required FlutterMultiselectLayoutDelegate delegate, + List? children, }) : super(children: children, delegate: delegate); @override diff --git a/lib/flutter_simple_multiselect.dart b/lib/flutter_simple_multiselect.dart index 90ca777..b4e2258 100644 --- a/lib/flutter_simple_multiselect.dart +++ b/lib/flutter_simple_multiselect.dart @@ -20,12 +20,12 @@ typedef SearchSuggestions = FutureOr> Function(); /// email address input widget in the iOS app. class FlutterMultiselect extends StatefulWidget { const FlutterMultiselect( - {required this.length, - this.minTextFieldWidth = 160.0, - this.tagSpacing = 4.0, - required this.tagBuilder, + {required this.tagBuilder, required this.suggestionBuilder, required this.findSuggestions, + required this.length, + this.minTextFieldWidth = 160.0, + this.tagSpacing = 4.0, Key? key, this.focusNode, this.isLoading = false, @@ -176,15 +176,18 @@ class FlutterMultiselectState extends State> { _focusNode = (widget.focusNode ?? FocusNode()) ..addListener(_onFocusChanged); - if (widget.activateSuggestionBox) _initializeSuggestionBox(); + if (widget.activateSuggestionBox) { + _initializeSuggestionBox(); + } } @override void dispose() { developer.log('FlutterMultiselectState::dispose():'); if (widget.autoDisposeFocusNode || widget.focusNode == null) { - _focusNode.removeListener(_onFocusChanged); - _focusNode.dispose(); + _focusNode + ..removeListener(_onFocusChanged) + ..dispose(); } _suggestionsStreamController?.close(); _suggestionsBoxController?.close(); @@ -211,7 +214,7 @@ class FlutterMultiselectState extends State> { void _onFocusChanged() { if (_focusNode.hasFocus) { _scrollToVisible(); - _onSearchChanged(""); + _onSearchChanged(''); _suggestionsBoxController?.open(); } else { _suggestionsBoxController?.close(); @@ -260,27 +263,30 @@ class FlutterMultiselectState extends State> { widget.suggestionsBoxRadius ?? 20), color: widget.suggestionsBoxBackgroundColor ?? Colors.white, - child: Container( - decoration: BoxDecoration( + child: ConstrainedBox( + constraints: + BoxConstraints(maxHeight: suggestionBoxHeight), + child: DecoratedBox( + decoration: BoxDecoration( color: widget.suggestionsBoxBackgroundColor ?? Colors.white, borderRadius: BorderRadius.all(Radius.circular( - widget.suggestionsBoxRadius ?? 0))), - constraints: - BoxConstraints(maxHeight: suggestionBoxHeight), - child: ListView.builder( - shrinkWrap: true, - padding: - widget.suggestionPadding ?? EdgeInsets.zero, - itemCount: snapshot.data!.length, - itemBuilder: (context, index) { - return _suggestions != null && - _suggestions?.isNotEmpty == true - ? widget.suggestionBuilder( - context, this, _suggestions![index]!) - : Container(); - }, - )), + widget.suggestionsBoxRadius ?? 0)), + ), + child: ListView.builder( + shrinkWrap: true, + padding: + widget.suggestionPadding ?? EdgeInsets.zero, + itemCount: snapshot.data!.length, + itemBuilder: (context, index) { + return _suggestions != null && + _suggestions?.isNotEmpty == true + ? widget.suggestionBuilder( + context, this, _suggestions![index]!) + : const SizedBox.shrink(); + }, + )), + ), ), ), ); @@ -299,11 +305,11 @@ class FlutterMultiselectState extends State> { ), ); } - return Container(); + return const SizedBox.shrink(); }, ); } - return Container(); + return const SizedBox.shrink(); }, ); } @@ -338,7 +344,7 @@ class FlutterMultiselectState extends State> { Future.delayed(const Duration(milliseconds: 300), () { WidgetsBinding.instance.addPostFrameCallback((_) async { final renderBox = context.findRenderObject() as RenderBox; - await Scrollable.of(context)?.position.ensureVisible(renderBox); + await Scrollable.of(context).position.ensureVisible(renderBox); }); }); } @@ -349,7 +355,7 @@ class FlutterMultiselectState extends State> { if (widget.multiselect) { _resetTextField(); } else { - _textFieldController.text = newString ?? ""; + _textFieldController.text = newString ?? ''; } } @@ -367,7 +373,7 @@ class FlutterMultiselectState extends State> { @override Widget build(BuildContext context) { - InputDecoration customDec = widget.inputDecoration ?? + final InputDecoration customDec = widget.inputDecoration ?? InputDecoration( errorBorder: widget.multiselect ? OutlineInputBorder( @@ -400,7 +406,7 @@ class FlutterMultiselectState extends State> { color: Colors.transparent, width: 0, )), - hintText: "Type to search", + hintText: 'Type to search', ); final decoration = widget.isLoading ? customDec.copyWith( @@ -461,7 +467,7 @@ class FlutterMultiselectState extends State> { ? TextFormField( onTap: () { if (_isFocused) { - _onSearchChanged(""); + _onSearchChanged(''); } }, validator: (value) { @@ -538,11 +544,11 @@ class FlutterMultiselectState extends State> { color: Colors.transparent, width: 0.1, )), - hintText: "", + hintText: '', ), onTap: () { if (_isFocused) { - _onSearchChanged(""); + _onSearchChanged(''); } }, validator: (value) { @@ -560,7 +566,7 @@ class FlutterMultiselectState extends State> { : TextFormField( onTap: () { if (_isFocused) { - _onSearchChanged(""); + _onSearchChanged(''); } }, validator: (value) { @@ -625,7 +631,7 @@ class FlutterMultiselectState extends State> { itemChild, CompositedTransformTarget( link: _layerLink, - child: Container(), + child: const SizedBox.shrink(), ), ], ), diff --git a/lib/suggestions_box_controller.dart b/lib/suggestions_box_controller.dart index d16c839..05de456 100644 --- a/lib/suggestions_box_controller.dart +++ b/lib/suggestions_box_controller.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; +/// This is a simple controller that can be used to open and close the suggestions box. class SuggestionsBoxController { final BuildContext context; @@ -14,7 +15,7 @@ class SuggestionsBoxController { void open() { if (_isOpened) return; assert(overlayEntry != null); - Overlay.of(context)?.insert(overlayEntry!); + Overlay.of(context).insert(overlayEntry!); _isOpened = true; } diff --git a/pubspec.yaml b/pubspec.yaml index 19300f5..00db62d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_simple_multiselect description: Flutter simple multiselect project that can be used with static or async values, also it can be used as single or multiselect and everything is customizable. -version: 1.0.0 +version: 1.0.1 homepage: https://github.com/vbalagovic/flutter_simple_multiselect.git environment: @@ -11,12 +11,12 @@ dependencies: debounce_throttle: ^2.0.0 flutter: sdk: flutter - pointer_interceptor: ^0.9.3+3 + pointer_interceptor: ^0.9.3+4 dev_dependencies: flutter_test: sdk: flutter - flutter_lints: ^2.0.0 + flutter_lints: ^2.0.1 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec