From 1be9cd15ac2d6c2bd7abae3e2751511bfecfa84a Mon Sep 17 00:00:00 2001 From: koukibadr Date: Thu, 2 Jul 2026 14:30:17 +0300 Subject: [PATCH] feat #168: implement search query field feature --- example/lib/country_data.dart | 28 ++++ example/lib/main.dart | 218 +++++++++++----------------- example/pubspec.lock | 2 +- example/pubspec.yaml | 2 +- lib/bottom_picker.dart | 98 +++++++++---- lib/widgets/simple_picker.dart | 30 +++- test/simple_bottom_picker_test.dart | 30 +++- 7 files changed, 231 insertions(+), 177 deletions(-) create mode 100644 example/lib/country_data.dart diff --git a/example/lib/country_data.dart b/example/lib/country_data.dart new file mode 100644 index 0000000..df8af4a --- /dev/null +++ b/example/lib/country_data.dart @@ -0,0 +1,28 @@ +class CountryModel { + int id; + String name; + + CountryModel({required this.id, required this.name}); +} + +final countryList = [ + CountryModel(id: 1, name: 'Atlantis'), + CountryModel(id: 2, name: 'Narnia'), + CountryModel(id: 3, name: 'Hogwarts'), + CountryModel(id: 4, name: 'Gondor'), + CountryModel(id: 5, name: 'Mordor'), + CountryModel(id: 6, name: 'Rivendell'), + CountryModel(id: 7, name: 'Asgard'), + CountryModel(id: 8, name: 'Avalon'), + CountryModel(id: 9, name: 'Camelot'), + CountryModel(id: 10, name: 'Shangri-La'), + CountryModel(id: 11, name: 'Wonderland'), + CountryModel(id: 12, name: 'Neverland'), + CountryModel(id: 13, name: 'Wakanda'), + CountryModel(id: 14, name: 'Themyscira'), + CountryModel(id: 15, name: 'Krypton'), + CountryModel(id: 16, name: 'Metropolis'), + CountryModel(id: 17, name: 'Gotham'), + CountryModel(id: 18, name: 'Middle-earth'), + CountryModel(id: 19, name: 'Eastwatch'), +]; diff --git a/example/lib/main.dart b/example/lib/main.dart index 3220c19..cb3bfee 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -3,6 +3,7 @@ import 'package:bottom_picker/bottom_picker.dart'; import 'package:bottom_picker/cupertino/cupertino_date_picker.dart'; import 'package:bottom_picker/resources/arrays.dart'; +import 'package:example/country_data.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; @@ -17,55 +18,23 @@ class MyApp extends StatelessWidget { Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', - theme: ThemeData( - primarySwatch: Colors.blueGrey, - ), + theme: ThemeData(primarySwatch: Colors.blueGrey), localizationsDelegates: [ GlobalMaterialLocalizations.delegate, GlobalWidgetsLocalizations.delegate, GlobalCupertinoLocalizations.delegate, ], - supportedLocales: [ - Locale('en'), - Locale('ar'), - ], - home: Scaffold( - body: ExampleApp(), - ), + supportedLocales: [Locale('en'), Locale('ar')], + home: Scaffold(body: ExampleApp()), ); } } class ExampleApp extends StatelessWidget { - final countryList = [ - Center( - child: Text('Algeria 🇩🇿'), - ), - Center( - child: Text('Maroco 🇲🇦'), - ), - Center( - child: Text('Tunisia 🇹🇳'), - ), - Center( - child: Text('Palestine 🇵🇸'), - ), - Center( - child: Text('Egypt 🇪🇬'), - ), - Center( - child: Text('Syria 🇸🇾'), - ), - Center( - child: Text('Irak 🇮🇶'), - ), - Center( - child: Text('Mauritania 🇲🇷'), - ), - ]; - final buttonWidth = 300.0; + final hugeList = List.generate(10000, (index) => index); + @override Widget build(BuildContext context) { return Container( @@ -80,33 +49,25 @@ class ExampleApp extends StatelessWidget { width: buttonWidth, child: ElevatedButton( onPressed: () { - _openSimpleItemPicker(context, countryList); + _openSimpleItemPicker(context); }, - child: Text( - 'Simple Item picker', - textAlign: TextAlign.center, - ), + child: Text('Simple Item picker', textAlign: TextAlign.center), ), ), SizedBox( width: buttonWidth, child: ElevatedButton( onPressed: () { - _openSecondSimpleItemPicker(context, countryList); + _displayLongListPicker(context); }, - child: Text( - 'Simple Item picker with different theme', - textAlign: TextAlign.center, - ), + child: Text('Long list item picker', textAlign: TextAlign.center), ), ), SizedBox( width: buttonWidth, child: ElevatedButton( onPressed: () { - _openDateTimePicker( - context, - ); + _openDateTimePicker(context); }, child: Text('Date time Picker', textAlign: TextAlign.center), ), @@ -190,9 +151,7 @@ class ExampleApp extends StatelessWidget { width: buttonWidth, child: ElevatedButton( onPressed: () { - _openDateTimePicker( - context, - ); + _openDateTimePicker(context); }, child: Text('Workday Picker', textAlign: TextAlign.center), ), @@ -201,9 +160,7 @@ class ExampleApp extends StatelessWidget { width: buttonWidth, child: ElevatedButton( onPressed: () { - _openDateTimePickerWeekend( - context, - ); + _openDateTimePickerWeekend(context); }, child: Text('Weekend Day Picker', textAlign: TextAlign.center), ), @@ -213,19 +170,27 @@ class ExampleApp extends StatelessWidget { ); } - void _openSimpleItemPicker(BuildContext context, List items) { - BottomPicker( - items: items, + void _openSimpleItemPicker(BuildContext context) { + BottomPicker( + items: countryList, + selectedItemIndex: 5, + itemBuilder: (item, index) { + return ListTile( + leading: CircleAvatar( + child: Text(item.name.substring(item.name.length - 2)), + ), + title: Text(item.name), + subtitle: Text('Country ID: ${item.id}'), + ); + }, + itemExtent: 70, headerBuilder: (context) { return Row( children: [ Expanded( child: Text( 'Choose your country', - style: TextStyle( - fontWeight: FontWeight.bold, - fontSize: 15, - ), + style: TextStyle(fontWeight: FontWeight.bold, fontSize: 15), ), ), InkWell( @@ -237,27 +202,46 @@ class ExampleApp extends StatelessWidget { ], ); }, - backgroundColor: Colors.yellow.withValues( - alpha: 0.6, - ), + backgroundColor: Colors.yellow.withValues(alpha: 0.6), bottomPickerTheme: BottomPickerTheme.morningSalad, - onSubmit: (index) { - print(index); + onChange: (item) { + print('== Selected Country: ${item.name}, ID: ${item.id}'); }, buttonAlignment: MainAxisAlignment.start, displaySubmitButton: false, dismissable: true, + filterPredicate: (item, value) { + if (int.tryParse(value) != null) { + return item.id.toString().contains(value); + } + return item.name.toLowerCase().contains(value.toLowerCase()); + }, + searchFieldDecoration: InputDecoration( + hintText: 'Search country', + prefixIcon: Icon(Icons.search), + border: OutlineInputBorder(borderRadius: BorderRadius.circular(10)), + ), onDismiss: (p0) { - print(p0); + print('Picker dismissed'); + print('== Selected Country: ${p0.name}, ID: ${p0.id}'); }, ).show(context); } - void _openSecondSimpleItemPicker(BuildContext context, List items) { - BottomPicker( - items: items, - selectedItemIndex: 1, - diameterRatio: 200, + void _displayLongListPicker(BuildContext context) { + BottomPicker( + height: MediaQuery.of(context).size.height * 0.5, + items: hugeList, + itemExtent: 80, + diameterRatio: 50, + searchFieldDecoration: InputDecoration( + hintText: 'Number', + prefixIcon: Icon(Icons.search), + border: OutlineInputBorder( + borderSide: BorderSide(color: Colors.blue), + borderRadius: BorderRadius.all(Radius.circular(10)), + ), + ), headerBuilder: (context) { return Container( child: Row( @@ -267,12 +251,10 @@ class ExampleApp extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - 'Choose country', - style: TextStyle( - fontWeight: FontWeight.bold, - ), + 'Super long list picker', + style: TextStyle(fontWeight: FontWeight.bold), ), - Text('Select your country of origins'), + Text('Testing a long list of items'), ], ), InkWell( @@ -285,10 +267,13 @@ class ExampleApp extends StatelessWidget { ), ); }, - onChange: (index) { + onChange: (int index) { print(index); }, - onSubmit: (index) { + filterPredicate: (item, value) { + return item.toString().contains(value); + }, + onSubmit: (int index) { print(index); Navigator.pop(context); }, @@ -421,29 +406,16 @@ class ExampleApp extends StatelessWidget { buttonStyle: BoxDecoration( color: Colors.blue, borderRadius: BorderRadius.circular(20), - border: Border.all( - color: Colors.blue[200]!, - ), + border: Border.all(color: Colors.blue[200]!), ), buttonWidth: 200, buttonContent: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 10, - ), + padding: const EdgeInsets.symmetric(horizontal: 10), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text( - 'Select date', - style: TextStyle( - color: Colors.white, - ), - ), - Icon( - Icons.arrow_forward_ios, - color: Colors.white, - size: 15, - ), + Text('Select date', style: TextStyle(color: Colors.white)), + Icon(Icons.arrow_forward_ios, color: Colors.white, size: 15), ], ), ), @@ -465,9 +437,7 @@ class ExampleApp extends StatelessWidget { ), Text( 'Please select a first date and an end date', - style: TextStyle( - color: Colors.black, - ), + style: TextStyle(color: Colors.black), ), ], ); @@ -502,9 +472,7 @@ class ExampleApp extends StatelessWidget { ), Text( 'Please select a first time and an end time', - style: TextStyle( - color: Colors.black, - ), + style: TextStyle(color: Colors.black), ), ], ); @@ -543,9 +511,7 @@ class ExampleApp extends StatelessWidget { }, child: Text( 'close', - style: TextStyle( - decoration: TextDecoration.underline, - ), + style: TextStyle(decoration: TextDecoration.underline), ), ), ], @@ -556,12 +522,8 @@ class ExampleApp extends StatelessWidget { print(index); }, bottomPickerTheme: BottomPickerTheme.orange, - initialTime: Time( - minutes: 23, - ), - maxTime: Time( - hours: 17, - ), + initialTime: Time(minutes: 23), + maxTime: Time(hours: 17), ).show(context); } @@ -586,9 +548,7 @@ class ExampleApp extends StatelessWidget { }, child: Text( 'close', - style: TextStyle( - decoration: TextDecoration.underline, - ), + style: TextStyle(decoration: TextDecoration.underline), ), ), ], @@ -598,17 +558,12 @@ class ExampleApp extends StatelessWidget { onSubmit: (index) { print(index); }, - initialTimerDuration: Duration( - hours: 6, - minutes: 30, - ), + initialTimerDuration: Duration(hours: 6, minutes: 30), timerPickerMode: CupertinoTimerPickerMode.hms, ).show(context); } - void _openDateTimePicker( - BuildContext context, - ) { + void _openDateTimePicker(BuildContext context) { BottomPicker.dateTime( hourPredicate: (hour) { return hour > 6; @@ -632,9 +587,7 @@ class ExampleApp extends StatelessWidget { }, child: Text( 'close', - style: TextStyle( - decoration: TextDecoration.underline, - ), + style: TextStyle(decoration: TextDecoration.underline), ), ), ], @@ -647,9 +600,7 @@ class ExampleApp extends StatelessWidget { ).show(context); } - void _openDateTimePickerWeekend( - BuildContext context, - ) { + void _openDateTimePickerWeekend(BuildContext context) { BottomPicker.dateTime( minuteInterval: 2, headerBuilder: (context) { @@ -671,9 +622,7 @@ class ExampleApp extends StatelessWidget { }, child: Text( 'close', - style: TextStyle( - decoration: TextDecoration.underline, - ), + style: TextStyle(decoration: TextDecoration.underline), ), ), ], @@ -685,10 +634,7 @@ class ExampleApp extends StatelessWidget { minDateTime: DateTime(2021, 5, 1), maxDateTime: DateTime(2021, 8, 2), initialDateTime: DateTime(2021, 5, 1), - gradientColors: [ - Color(0xfffdcbf1), - Color(0xffe6dee9), - ], + gradientColors: [Color(0xfffdcbf1), Color(0xffe6dee9)], calendarDays: CupertinoDatePickerWidget.weekend, ).show(context); } diff --git a/example/pubspec.lock b/example/pubspec.lock index c015a85..55d17e7 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -213,5 +213,5 @@ packages: source: hosted version: "15.0.0" sdks: - dart: ">=3.9.0-0 <4.0.0" + dart: ">=3.10.0 <4.0.0" flutter: ">=3.18.0-18.0.pre.54" diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 34d96e0..2b9efbb 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -3,7 +3,7 @@ description: A new Flutter project. publish_to: none version: 1.0.0+1 environment: - sdk: '>=2.12.0 <3.0.0' + sdk: '>=3.10.0 <4.0.0' dependencies: flutter: sdk: flutter diff --git a/lib/bottom_picker.dart b/lib/bottom_picker.dart index 8fd38bd..ac91afc 100644 --- a/lib/bottom_picker.dart +++ b/lib/bottom_picker.dart @@ -20,7 +20,7 @@ import 'package:flutter/services.dart'; export 'package:bottom_picker/resources/time.dart'; // ignore: must_be_immutable -class BottomPicker extends StatefulWidget { +class BottomPicker extends StatefulWidget { ///The dateTime picker mode ///[CupertinoDatePickerMode.date] or [CupertinoDatePickerMode.dateAndTime] or [CupertinoDatePickerMode.time] /// @@ -38,6 +38,7 @@ class BottomPicker extends StatefulWidget { BottomPicker({ super.key, required this.items, + this.itemBuilder, this.dismissable = false, this.onChange, this.onSubmit, @@ -62,6 +63,9 @@ class BottomPicker extends StatefulWidget { this.headerBuilder, this.diameterRatio = 1.1, this.useSafeArea = false, + this.filterPredicate, + this.textInputAction, + this.searchFieldDecoration, }) { dateOrder = null; onRangeDateSubmitPressed = null; @@ -429,26 +433,39 @@ class BottomPicker extends StatefulWidget { ///and should not be empty or null /// ///for date/dateTime/time items parameter is not available - /// - late List? items; + late List? items; + + /// Callback function used to build the item widget for each item in the list. + /// only for simple item picker, for date/time/dateTime picker this parameter is not available + /// if Null the picker will display [Text] widget with the item.toString() value + Widget Function(T item, int index)? itemBuilder; ///Nullable function, invoked when navigating between picker items ///whether it's date picker or simple item picker it will return a value DateTime or int(index) - /// - late Function(dynamic)? onChange; + late Function(T)? onChange; + + /// Predicate function used to filter items in the search field + /// if not null the search field will be displayed and the user can filter the items based on the predicate + /// Takes two parameters: the item and the search query, and returns a boolean indicating whether the item should be filtered out or not. + bool Function(T, String)? filterPredicate; + + /// The search field text input action, which determines the action button on the keyboard. + TextInputAction? textInputAction; + + /// The decoration for the search field, which allows customization of the appearance of the search field. + InputDecoration? searchFieldDecoration; ///Nullable function invoked when clicking on submit button ///if the picker type is date/time/dateTime it will return DateTime value ///else it will return the index of the selected item /// - late Function(dynamic)? onSubmit; + late Function(T)? onSubmit; /// Nullable function invoked when the picker get dismissed /// it will return the selected value - late Function(dynamic)? onDismiss; + late Function(T)? onDismiss; ///Invoked when clicking on the close button - /// Function? onCloseButtonPressed; ///set the theme of the bottom picker (the button theme) @@ -675,7 +692,6 @@ class BottomPicker extends StatefulWidget { /// Indiacate whether the bottom picker will be closed (poped out of the Navigator) /// when the submit button is pressed. - /// /// By default closeOnSubmit = true. bool? closeOnSubmit; @@ -717,29 +733,35 @@ class BottomPicker extends StatefulWidget { } @override - BottomPickerState createState() => BottomPickerState(); + BottomPickerState createState() => BottomPickerState(); } -class BottomPickerState extends State { +class BottomPickerState extends State> { late int selectedItemIndex; + late T selectedItem; late DateTime selectedDateTime; + Duration? selectedTimerDuration; late DateTime selectedFirstDateTime = widget.initialFirstDate ?? DateTime.now(); late DateTime selectedSecondDateTime = widget.initialSecondDate ?? DateTime.now(); - Duration? selectedTimerDuration; - bool disposed = false; + late final List? originalItemList = widget.items; + late List? displayedItemList = originalItemList; + @override void initState() { super.initState(); if (widget.bottomPickerType == BottomPickerType.simple) { selectedItemIndex = widget.selectedItemIndex; + selectedItem = widget.items![selectedItemIndex]; } else if (widget.bottomPickerType == BottomPickerType.time) { selectedDateTime = (widget.initialTime ?? Time.now()).toDateTime; + } else if (widget.bottomPickerType == BottomPickerType.timer) { + selectedTimerDuration = widget.initialTimerDuration ?? Duration.zero; } else { selectedDateTime = widget.initialDateTime ?? DateTime.now(); } @@ -770,11 +792,11 @@ class BottomPickerState extends State { selectedSecondDateTime, ); } else if (widget.bottomPickerType == BottomPickerType.simple) { - widget.onDismiss?.call(selectedItemIndex); + widget.onDismiss?.call(selectedItem); } else if (widget.bottomPickerType == BottomPickerType.timer) { - widget.onDismiss?.call(selectedTimerDuration); + widget.onDismiss?.call(selectedTimerDuration as T); } else { - widget.onDismiss?.call(selectedDateTime); + widget.onDismiss?.call(selectedDateTime as T); } super.dispose(); @@ -846,13 +868,35 @@ class BottomPickerState extends State { ), ), ), + if (widget.filterPredicate != null && + widget.bottomPickerType == BottomPickerType.simple) + Padding( + padding: const EdgeInsets.symmetric(vertical: 10.0), + child: TextField( + onChanged: (value) { + displayedItemList = originalItemList! + .where((item) => widget.filterPredicate!(item, value)) + .toList(); + setState(() {}); + }, + textInputAction: widget.textInputAction, + decoration: widget.searchFieldDecoration, + ), + ), Expanded( child: widget.bottomPickerType == BottomPickerType.simple - ? SimplePicker( - items: widget.items!, + ? SimplePicker( + items: displayedItemList ?? [], + itemBuilder: widget.itemBuilder, onChange: (int index) { - selectedItemIndex = index; - widget.onChange?.call(index); + if (displayedItemList == null || + displayedItemList!.isEmpty) { + return; + } + final item = displayedItemList![index]; + selectedItem = item; + selectedItemIndex = originalItemList!.indexOf(item); + widget.onChange?.call(item); }, selectedItemIndex: widget.selectedItemIndex, textStyle: widget.pickerTextStyle, @@ -869,7 +913,7 @@ class BottomPickerState extends State { itemExtent: widget.itemExtent, initialDuration: widget.initialTimerDuration, onChange: (p0) { - widget.onChange?.call(p0); + widget.onChange?.call(p0 as T); selectedTimerDuration = p0; }, secondInterval: widget.timerSecondsInterval, @@ -884,7 +928,7 @@ class BottomPickerState extends State { mode: widget.datePickerMode, onDateChanged: (DateTime date) { selectedDateTime = date; - widget.onChange?.call(date); + widget.onChange?.call(date as T); }, use24hFormat: widget.use24hFormat, dateOrder: widget.dateOrder, @@ -902,7 +946,7 @@ class BottomPickerState extends State { mode: widget.datePickerMode, onDateChanged: (DateTime date) { selectedDateTime = date; - widget.onChange?.call(date); + widget.onChange?.call(date as T); }, use24hFormat: widget.use24hFormat, dateOrder: widget.dateOrder, @@ -919,7 +963,7 @@ class BottomPickerState extends State { minDateTime: widget.minDateTime, onDateChanged: (DateTime date) { selectedDateTime = date; - widget.onChange?.call(date); + widget.onChange?.call(date as T); }, itemExtent: widget.itemExtent, pickerThemeData: widget.pickerThemeData, @@ -1006,12 +1050,12 @@ class BottomPickerState extends State { BottomPickerType.dateTime || widget.bottomPickerType == BottomPickerType.time || widget.bottomPickerType == BottomPickerType.year) { - widget.onSubmit?.call(selectedDateTime); + widget.onSubmit?.call(selectedDateTime as T); } else if (widget.bottomPickerType == BottomPickerType.timer) { - widget.onSubmit?.call(selectedTimerDuration); + widget.onSubmit?.call(selectedTimerDuration as T); } else { - widget.onSubmit?.call(selectedItemIndex); + widget.onSubmit?.call(selectedItem); } if (widget.closeOnSubmit ?? false) { diff --git a/lib/widgets/simple_picker.dart b/lib/widgets/simple_picker.dart index 5899995..00137e1 100644 --- a/lib/widgets/simple_picker.dart +++ b/lib/widgets/simple_picker.dart @@ -4,7 +4,7 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; /// A simple picker widget that can be used to select an item from a list of items. -class SimplePicker extends StatelessWidget { +class SimplePicker extends StatelessWidget { /// The index of the selected item in the list of items. final int selectedItemIndex; @@ -12,7 +12,9 @@ class SimplePicker extends StatelessWidget { final Function(int)? onChange; /// The list of items to be displayed in the picker. - final List items; + final List items; + + final Widget Function(T item, int index)? itemBuilder; /// The text style to be used for the items in the picker. final TextStyle? textStyle; @@ -32,6 +34,7 @@ class SimplePicker extends StatelessWidget { const SimplePicker({ super.key, required this.items, + this.itemBuilder, required this.onChange, required this.selectedItemIndex, this.textStyle, @@ -60,14 +63,33 @@ class SimplePicker extends StatelessWidget { scrollController: FixedExtentScrollController( initialItem: selectedItemIndex, ), + useMagnifier: false, onSelectedItemChanged: onChange, - children: items, + children: List.generate(items.length, (index) { + if (itemBuilder != null) { + return itemBuilder!(items[index], index); + } else { + return Text( + items[index].toString(), + style: textStyle, + ); + } + }), ), ); } else { return ListWheelScrollView( itemExtent: itemExtent, - children: items, + children: List.generate(items.length, (index) { + if (itemBuilder != null) { + return itemBuilder!(items[index], index); + } else { + return Text( + items[index].toString(), + style: textStyle, + ); + } + }), useMagnifier: true, magnification: 1.5, controller: FixedExtentScrollController( diff --git a/test/simple_bottom_picker_test.dart b/test/simple_bottom_picker_test.dart index aa16614..45ce8f2 100644 --- a/test/simple_bottom_picker_test.dart +++ b/test/simple_bottom_picker_test.dart @@ -6,9 +6,9 @@ import 'package:flutter_test/flutter_test.dart'; void main() { group('Simple picker item testing...', () { - late List items = List.generate( + late List items = List.generate( 10, - (index) => Text('Item $index'), + (index) => index, ); testWidgets('Simple use case: picker should function properly', @@ -30,7 +30,18 @@ void main() { // Verify that the picker title is displayed expect(find.text('Item picker'), findsOneWidget); // Verify that the items are displayed - expect(find.text('Item 1'), findsOneWidget); + expect(find.text('1'), findsOneWidget); + + expect(find.text('2'), findsOneWidget); + + // Verify that if the [itemBuilder] is null the picker + // will display [Text] widget with the item.toString() value + expect( + find.byWidgetPredicate( + (widget) => widget is Text && widget.data == '1', + ), + findsOneWidget, + ); expect(bottomPicker.bottomPickerType, BottomPickerType.simple); }); @@ -47,8 +58,11 @@ void main() { return Text('Item picker'); }, items: items, - onChange: (index) { - itemIndex = index; + itemBuilder: (item, index) { + return Text('Item $item'); + }, + onChange: (item) { + itemIndex = item; }, ), ), @@ -101,7 +115,7 @@ void main() { ); await tester.drag( - find.text('Item 2'), + find.text('2'), const Offset(0.0, -60.0), ); // see top of file await tester.pump(); @@ -133,7 +147,7 @@ void main() { ); await tester.drag( - find.text('Item 2'), + find.text('2'), const Offset(0.0, -60.0), ); // see top of file await tester.pump(); @@ -174,7 +188,7 @@ void main() { ); await tester.drag( - find.text('Item 2'), + find.text('2'), const Offset(0.0, -60.0), ); // see top of file await tester.pump();