@@ -8,6 +8,14 @@ import 'package:flutter/foundation.dart';
8
8
part 'edit_source_event.dart' ;
9
9
part 'edit_source_state.dart' ;
10
10
11
+ final class _FetchNextCountryPage extends EditSourceEvent {
12
+ const _FetchNextCountryPage ();
13
+ }
14
+
15
+ final class _FetchNextLanguagePage extends EditSourceEvent {
16
+ const _FetchNextLanguagePage ();
17
+ }
18
+
11
19
const _searchDebounceDuration = Duration (milliseconds: 300 );
12
20
13
21
/// A BLoC to manage the state of editing a single source.
@@ -18,11 +26,11 @@ class EditSourceBloc extends Bloc<EditSourceEvent, EditSourceState> {
18
26
required DataRepository <Country > countriesRepository,
19
27
required DataRepository <Language > languagesRepository,
20
28
required String sourceId,
21
- }) : _sourcesRepository = sourcesRepository,
22
- _countriesRepository = countriesRepository,
23
- _languagesRepository = languagesRepository,
24
- _sourceId = sourceId,
25
- super (const EditSourceState ()) {
29
+ }) : _sourcesRepository = sourcesRepository,
30
+ _countriesRepository = countriesRepository,
31
+ _languagesRepository = languagesRepository,
32
+ _sourceId = sourceId,
33
+ super (const EditSourceState ()) {
26
34
on < EditSourceLoaded > (_onLoaded);
27
35
on < EditSourceNameChanged > (_onNameChanged);
28
36
on < EditSourceDescriptionChanged > (_onDescriptionChanged);
@@ -32,6 +40,8 @@ class EditSourceBloc extends Bloc<EditSourceEvent, EditSourceState> {
32
40
on < EditSourceHeadquartersChanged > (_onHeadquartersChanged);
33
41
on < EditSourceStatusChanged > (_onStatusChanged);
34
42
on < EditSourceSubmitted > (_onSubmitted);
43
+ on < _FetchNextCountryPage > (_onFetchNextCountryPage);
44
+ on < _FetchNextLanguagePage > (_onFetchNextLanguagePage);
35
45
}
36
46
37
47
final DataRepository <Source > _sourcesRepository;
@@ -90,43 +100,13 @@ class EditSourceBloc extends Bloc<EditSourceEvent, EditSourceState> {
90
100
),
91
101
);
92
102
93
- // After the initial page is loaded, start a background process to
103
+ // After the initial page is loaded, start background processes to
94
104
// fetch all remaining pages for countries and languages.
95
- //
96
- // This approach is used for the following reasons:
97
- // 1. UI Consistency: It allows us to use the standard
98
- // `DropdownButtonFormField`, which is used elsewhere in the app.
99
- // 2. Technical Limitation: The standard dropdown does not expose a
100
- // scroll controller, making on-scroll pagination impossible.
101
- //
102
- // The UI will update progressively and silently in the background as
103
- // more data arrives.
104
- while (state.countriesHasMore) {
105
- final nextCountries = await _countriesRepository.readAll (
106
- pagination: PaginationOptions (cursor: state.countriesCursor),
107
- sort: [const SortOption ('name' , SortOrder .asc)],
108
- );
109
- emit (
110
- state.copyWith (
111
- countries: List .of (state.countries)..addAll (nextCountries.items),
112
- countriesCursor: nextCountries.cursor,
113
- countriesHasMore: nextCountries.hasMore,
114
- ),
115
- );
105
+ if (state.countriesHasMore) {
106
+ add (const _FetchNextCountryPage ());
116
107
}
117
-
118
- while (state.languagesHasMore) {
119
- final nextLanguages = await _languagesRepository.readAll (
120
- pagination: PaginationOptions (cursor: state.languagesCursor),
121
- sort: [const SortOption ('name' , SortOrder .asc)],
122
- );
123
- emit (
124
- state.copyWith (
125
- languages: List .of (state.languages)..addAll (nextLanguages.items),
126
- languagesCursor: nextLanguages.cursor,
127
- languagesHasMore: nextLanguages.hasMore,
128
- ),
129
- );
108
+ if (state.languagesHasMore) {
109
+ add (const _FetchNextLanguagePage ());
130
110
}
131
111
} on HttpException catch (e) {
132
112
emit (state.copyWith (status: EditSourceStatus .failure, exception: e));
@@ -214,6 +194,83 @@ class EditSourceBloc extends Bloc<EditSourceEvent, EditSourceState> {
214
194
);
215
195
}
216
196
197
+ // --- Background Data Fetching for Dropdown ---
198
+ // The DropdownButtonFormField widget does not natively support on-scroll
199
+ // pagination. To preserve UI consistency across the application, this BLoC
200
+ // employs an event-driven background fetching mechanism.
201
+ //
202
+ // After the first page of items is loaded, a chain of events is initiated
203
+ // to progressively fetch all remaining pages. This process is throttled
204
+ // and runs in the background, ensuring the UI remains responsive while the
205
+ // full list of dropdown options is populated over time.
206
+ Future <void > _onFetchNextCountryPage (
207
+ _FetchNextCountryPage event,
208
+ Emitter <EditSourceState > emit,
209
+ ) async {
210
+ if (! state.countriesHasMore || state.countriesIsLoadingMore) return ;
211
+
212
+ try {
213
+ emit (state.copyWith (countriesIsLoadingMore: true ));
214
+
215
+ await Future .delayed (const Duration (milliseconds: 400 ));
216
+
217
+ final nextCountries = await _countriesRepository.readAll (
218
+ pagination: PaginationOptions (cursor: state.countriesCursor),
219
+ sort: [const SortOption ('name' , SortOrder .asc)],
220
+ );
221
+
222
+ emit (
223
+ state.copyWith (
224
+ countries: List .of (state.countries)..addAll (nextCountries.items),
225
+ countriesCursor: nextCountries.cursor,
226
+ countriesHasMore: nextCountries.hasMore,
227
+ countriesIsLoadingMore: false ,
228
+ ),
229
+ );
230
+
231
+ if (nextCountries.hasMore) {
232
+ add (const _FetchNextCountryPage ());
233
+ }
234
+ } catch (e) {
235
+ emit (state.copyWith (countriesIsLoadingMore: false ));
236
+ // Optionally log the error without disrupting the user
237
+ }
238
+ }
239
+
240
+ Future <void > _onFetchNextLanguagePage (
241
+ _FetchNextLanguagePage event,
242
+ Emitter <EditSourceState > emit,
243
+ ) async {
244
+ if (! state.languagesHasMore || state.languagesIsLoadingMore) return ;
245
+
246
+ try {
247
+ emit (state.copyWith (languagesIsLoadingMore: true ));
248
+
249
+ await Future .delayed (const Duration (milliseconds: 400 ));
250
+
251
+ final nextLanguages = await _languagesRepository.readAll (
252
+ pagination: PaginationOptions (cursor: state.languagesCursor),
253
+ sort: [const SortOption ('name' , SortOrder .asc)],
254
+ );
255
+
256
+ emit (
257
+ state.copyWith (
258
+ languages: List .of (state.languages)..addAll (nextLanguages.items),
259
+ languagesCursor: nextLanguages.cursor,
260
+ languagesHasMore: nextLanguages.hasMore,
261
+ languagesIsLoadingMore: false ,
262
+ ),
263
+ );
264
+
265
+ if (nextLanguages.hasMore) {
266
+ add (const _FetchNextLanguagePage ());
267
+ }
268
+ } catch (e) {
269
+ emit (state.copyWith (languagesIsLoadingMore: false ));
270
+ // Optionally log the error without disrupting the user
271
+ }
272
+ }
273
+
217
274
Future <void > _onSubmitted (
218
275
EditSourceSubmitted event,
219
276
Emitter <EditSourceState > emit,
0 commit comments