1
1
import 'package:flutter/material.dart' ;
2
+ import 'package:flutter_bloc/flutter_bloc.dart' ;
2
3
import 'package:ui_kit/ui_kit.dart' ;
3
4
4
5
/// A generic type for the builder function that creates list items in the
@@ -21,17 +22,19 @@ typedef SearchableDropdownSelectedItemBuilder<T> = Widget Function(
21
22
/// This widget is generic and can be used for any type [T] . It requires
22
23
/// builders for constructing the list items and the selected item display,
23
24
/// 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 > {
25
27
/// {@macro searchable_dropdown_form_field}
26
28
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,
28
33
required ValueChanged <T ?> onChanged,
29
34
required ValueChanged <String > onSearchChanged,
30
35
required VoidCallback onLoadMore,
31
36
required SearchableDropdownItemBuilder <T > itemBuilder,
32
37
required SearchableDropdownSelectedItemBuilder <T > selectedItemBuilder,
33
- required bool hasMore,
34
- bool ? isLoading,
35
38
super .key,
36
39
T ? initialValue,
37
40
String ? labelText,
@@ -49,13 +52,14 @@ class SearchableDropdownFormField<T> extends FormField<T> {
49
52
onTap: () async {
50
53
final selectedItem = await showDialog <T >(
51
54
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,
54
60
onSearchChanged: onSearchChanged,
55
61
onLoadMore: onLoadMore,
56
62
itemBuilder: itemBuilder,
57
- hasMore: hasMore,
58
- isLoading: isLoading ?? false ,
59
63
searchHintText: searchHintText,
60
64
noItemsFoundText: noItemsFoundText,
61
65
),
@@ -83,35 +87,38 @@ class SearchableDropdownFormField<T> extends FormField<T> {
83
87
}
84
88
85
89
/// 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 {
87
92
const _SearchableSelectionDialog ({
88
- required this .items,
93
+ required this .bloc,
94
+ required this .itemsExtractor,
95
+ required this .hasMoreExtractor,
96
+ required this .isLoadingExtractor,
89
97
required this .onSearchChanged,
90
98
required this .onLoadMore,
91
99
required this .itemBuilder,
92
- required this .hasMore,
93
- required this .isLoading,
94
100
this .searchHintText,
95
101
this .noItemsFoundText,
96
102
super .key,
97
103
});
98
104
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;
100
109
final ValueChanged <String > onSearchChanged;
101
110
final VoidCallback onLoadMore;
102
111
final SearchableDropdownItemBuilder <T > itemBuilder;
103
- final bool hasMore;
104
- final bool isLoading;
105
112
final String ? searchHintText;
106
113
final String ? noItemsFoundText;
107
114
108
115
@override
109
- State <_SearchableSelectionDialog <T >> createState () =>
110
- _SearchableSelectionDialogState <T >();
116
+ State <_SearchableSelectionDialog <T , B , S >> createState () =>
117
+ _SearchableSelectionDialogState <T , B , S >();
111
118
}
112
119
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 >> {
115
122
final _scrollController = ScrollController ();
116
123
final _searchController = TextEditingController ();
117
124
@@ -168,7 +175,16 @@ class _SearchableSelectionDialogState<T>
168
175
),
169
176
const SizedBox (height: AppSpacing .md),
170
177
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
+ ),
172
188
),
173
189
],
174
190
),
@@ -177,29 +193,34 @@ class _SearchableSelectionDialogState<T>
177
193
);
178
194
}
179
195
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) {
182
198
return const Center (child: CircularProgressIndicator ());
183
199
}
184
200
185
- if (widget. items.isEmpty) {
201
+ if (items.isEmpty) {
186
202
return Center (
187
203
child: Text (widget.noItemsFoundText ?? 'No items found.' ),
188
204
);
189
205
}
190
206
191
207
return ListView .builder (
192
208
controller: _scrollController,
193
- itemCount:
194
- widget.hasMore ? widget.items.length + 1 : widget.items.length,
209
+ itemCount: items.length + (hasMore ? 1 : 0 ),
195
210
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 ();
201
220
}
202
- final item = widget.items[index];
221
+
222
+ // This is a regular item.
223
+ final item = items[index];
203
224
return InkWell (
204
225
onTap: () => Navigator .of (context).pop (item),
205
226
child: widget.itemBuilder (context, item),
0 commit comments