@@ -8,6 +8,14 @@ import 'package:flutter/foundation.dart';
88part 'edit_source_event.dart' ;
99part 'edit_source_state.dart' ;
1010
11+ final class _FetchNextCountryPage extends EditSourceEvent {
12+ const _FetchNextCountryPage ();
13+ }
14+
15+ final class _FetchNextLanguagePage extends EditSourceEvent {
16+ const _FetchNextLanguagePage ();
17+ }
18+
1119const _searchDebounceDuration = Duration (milliseconds: 300 );
1220
1321/// A BLoC to manage the state of editing a single source.
@@ -18,11 +26,11 @@ class EditSourceBloc extends Bloc<EditSourceEvent, EditSourceState> {
1826 required DataRepository <Country > countriesRepository,
1927 required DataRepository <Language > languagesRepository,
2028 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 ()) {
2634 on < EditSourceLoaded > (_onLoaded);
2735 on < EditSourceNameChanged > (_onNameChanged);
2836 on < EditSourceDescriptionChanged > (_onDescriptionChanged);
@@ -32,6 +40,8 @@ class EditSourceBloc extends Bloc<EditSourceEvent, EditSourceState> {
3240 on < EditSourceHeadquartersChanged > (_onHeadquartersChanged);
3341 on < EditSourceStatusChanged > (_onStatusChanged);
3442 on < EditSourceSubmitted > (_onSubmitted);
43+ on < _FetchNextCountryPage > (_onFetchNextCountryPage);
44+ on < _FetchNextLanguagePage > (_onFetchNextLanguagePage);
3545 }
3646
3747 final DataRepository <Source > _sourcesRepository;
@@ -90,43 +100,13 @@ class EditSourceBloc extends Bloc<EditSourceEvent, EditSourceState> {
90100 ),
91101 );
92102
93- // After the initial page is loaded, start a background process to
103+ // After the initial page is loaded, start background processes to
94104 // 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 ());
116107 }
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 ());
130110 }
131111 } on HttpException catch (e) {
132112 emit (state.copyWith (status: EditSourceStatus .failure, exception: e));
@@ -214,6 +194,83 @@ class EditSourceBloc extends Bloc<EditSourceEvent, EditSourceState> {
214194 );
215195 }
216196
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+
217274 Future <void > _onSubmitted (
218275 EditSourceSubmitted event,
219276 Emitter <EditSourceState > emit,
0 commit comments