11import 'package:flutter/material.dart' ;
2+ import 'package:flutter_bloc/flutter_bloc.dart' ;
23import 'package:ui_kit/ui_kit.dart' ;
34
45/// A generic type for the builder function that creates list items in the
@@ -21,17 +22,19 @@ typedef SearchableDropdownSelectedItemBuilder<T> = Widget Function(
2122/// This widget is generic and can be used for any type [T] . It requires
2223/// builders for constructing the list items and the selected item display,
2324/// as well as callbacks to handle searching and pagination.
24- class SearchableDropdownFormField <T > extends FormField <T > {
25+ class SearchableDropdownFormField <T , B extends BlocBase <S >, S >
26+ extends FormField <T > {
2527 /// {@macro searchable_dropdown_form_field}
2628 SearchableDropdownFormField ({
27- required List <T > items,
29+ required B bloc,
30+ required List <T > Function (S state) itemsExtractor,
31+ required bool Function (S state) hasMoreExtractor,
32+ required bool Function (S state) isLoadingExtractor,
2833 required ValueChanged <T ?> onChanged,
2934 required ValueChanged <String > onSearchChanged,
3035 required VoidCallback onLoadMore,
3136 required SearchableDropdownItemBuilder <T > itemBuilder,
3237 required SearchableDropdownSelectedItemBuilder <T > selectedItemBuilder,
33- required bool hasMore,
34- bool ? isLoading,
3538 super .key,
3639 T ? initialValue,
3740 String ? labelText,
@@ -49,13 +52,14 @@ class SearchableDropdownFormField<T> extends FormField<T> {
4952 onTap: () async {
5053 final selectedItem = await showDialog <T >(
5154 context: state.context,
52- builder: (context) => _SearchableSelectionDialog <T >(
53- items: items,
55+ builder: (context) => _SearchableSelectionDialog <T , B , S >(
56+ bloc: bloc,
57+ itemsExtractor: itemsExtractor,
58+ hasMoreExtractor: hasMoreExtractor,
59+ isLoadingExtractor: isLoadingExtractor,
5460 onSearchChanged: onSearchChanged,
5561 onLoadMore: onLoadMore,
5662 itemBuilder: itemBuilder,
57- hasMore: hasMore,
58- isLoading: isLoading ?? false ,
5963 searchHintText: searchHintText,
6064 noItemsFoundText: noItemsFoundText,
6165 ),
@@ -83,35 +87,38 @@ class SearchableDropdownFormField<T> extends FormField<T> {
8387}
8488
8589/// The modal dialog that contains the searchable and paginated list.
86- class _SearchableSelectionDialog <T > extends StatefulWidget {
90+ class _SearchableSelectionDialog <T , B extends BlocBase <S >, S >
91+ extends StatefulWidget {
8792 const _SearchableSelectionDialog ({
88- required this .items,
93+ required this .bloc,
94+ required this .itemsExtractor,
95+ required this .hasMoreExtractor,
96+ required this .isLoadingExtractor,
8997 required this .onSearchChanged,
9098 required this .onLoadMore,
9199 required this .itemBuilder,
92- required this .hasMore,
93- required this .isLoading,
94100 this .searchHintText,
95101 this .noItemsFoundText,
96102 super .key,
97103 });
98104
99- final List <T > items;
105+ final B bloc;
106+ final List <T > Function (S state) itemsExtractor;
107+ final bool Function (S state) hasMoreExtractor;
108+ final bool Function (S state) isLoadingExtractor;
100109 final ValueChanged <String > onSearchChanged;
101110 final VoidCallback onLoadMore;
102111 final SearchableDropdownItemBuilder <T > itemBuilder;
103- final bool hasMore;
104- final bool isLoading;
105112 final String ? searchHintText;
106113 final String ? noItemsFoundText;
107114
108115 @override
109- State <_SearchableSelectionDialog <T >> createState () =>
110- _SearchableSelectionDialogState <T >();
116+ State <_SearchableSelectionDialog <T , B , S >> createState () =>
117+ _SearchableSelectionDialogState <T , B , S >();
111118}
112119
113- class _SearchableSelectionDialogState <T >
114- extends State <_SearchableSelectionDialog <T >> {
120+ class _SearchableSelectionDialogState <T , B extends BlocBase < S >, S >
121+ extends State <_SearchableSelectionDialog <T , B , S >> {
115122 final _scrollController = ScrollController ();
116123 final _searchController = TextEditingController ();
117124
@@ -168,7 +175,16 @@ class _SearchableSelectionDialogState<T>
168175 ),
169176 const SizedBox (height: AppSpacing .md),
170177 Expanded (
171- child: _buildList (),
178+ child: BlocBuilder <B , S >(
179+ bloc: widget.bloc,
180+ builder: (context, state) {
181+ final items = widget.itemsExtractor (state);
182+ final hasMore = widget.hasMoreExtractor (state);
183+ final isLoading = widget.isLoadingExtractor (state);
184+
185+ return _buildList (items, hasMore, isLoading);
186+ },
187+ ),
172188 ),
173189 ],
174190 ),
@@ -177,29 +193,34 @@ class _SearchableSelectionDialogState<T>
177193 );
178194 }
179195
180- Widget _buildList () {
181- if (widget. isLoading && widget. items.isEmpty) {
196+ Widget _buildList (List < T > items, bool hasMore, bool isLoading ) {
197+ if (isLoading && items.isEmpty) {
182198 return const Center (child: CircularProgressIndicator ());
183199 }
184200
185- if (widget. items.isEmpty) {
201+ if (items.isEmpty) {
186202 return Center (
187203 child: Text (widget.noItemsFoundText ?? 'No items found.' ),
188204 );
189205 }
190206
191207 return ListView .builder (
192208 controller: _scrollController,
193- itemCount:
194- widget.hasMore ? widget.items.length + 1 : widget.items.length,
209+ itemCount: items.length + (hasMore ? 1 : 0 ),
195210 itemBuilder: (context, index) {
196- if (index >= widget.items.length) {
197- return const Padding (
198- padding: EdgeInsets .symmetric (vertical: AppSpacing .md),
199- child: Center (child: CircularProgressIndicator ()),
200- );
211+ if (index >= items.length) {
212+ // This is the last item, which is the loading indicator.
213+ // It's only shown if we have more items and are currently loading.
214+ return isLoading
215+ ? const Padding (
216+ padding: EdgeInsets .symmetric (vertical: AppSpacing .md),
217+ child: Center (child: CircularProgressIndicator ()),
218+ )
219+ : const SizedBox .shrink ();
201220 }
202- final item = widget.items[index];
221+
222+ // This is a regular item.
223+ final item = items[index];
203224 return InkWell (
204225 onTap: () => Navigator .of (context).pop (item),
205226 child: widget.itemBuilder (context, item),
0 commit comments