diff --git a/lib/content_management/bloc/create_headline/create_headline_bloc.dart b/lib/content_management/bloc/create_headline/create_headline_bloc.dart index 5b491ee..456fb6a 100644 --- a/lib/content_management/bloc/create_headline/create_headline_bloc.dart +++ b/lib/content_management/bloc/create_headline/create_headline_bloc.dart @@ -20,11 +20,11 @@ class CreateHeadlineBloc required DataRepository sourcesRepository, required DataRepository topicsRepository, required DataRepository countriesRepository, - }) : _headlinesRepository = headlinesRepository, - _sourcesRepository = sourcesRepository, - _topicsRepository = topicsRepository, - _countriesRepository = countriesRepository, - super(const CreateHeadlineState()) { + }) : _headlinesRepository = headlinesRepository, + _sourcesRepository = sourcesRepository, + _topicsRepository = topicsRepository, + _countriesRepository = countriesRepository, + super(const CreateHeadlineState()) { on(_onDataLoaded); on(_onTitleChanged); on(_onExcerptChanged); @@ -37,10 +37,9 @@ class CreateHeadlineBloc on(_onSubmitted); on( _onCountrySearchChanged, - transformer: restartable(), + transformer: droppable(), ); - on( - _onLoadMoreCountriesRequested); + on(_onLoadMoreCountriesRequested); } final DataRepository _headlinesRepository; @@ -204,8 +203,7 @@ class CreateHeadlineBloc emit(state.copyWith(countrySearchTerm: event.searchTerm)); try { final countriesResponse = await _countriesRepository.readAll( - filter: - event.searchTerm.isNotEmpty ? {'name': event.searchTerm} : null, + filter: event.searchTerm.isNotEmpty ? {'name': event.searchTerm} : null, sort: [const SortOption('name', SortOrder.asc)], ); @@ -232,7 +230,9 @@ class CreateHeadlineBloc CreateHeadlineLoadMoreCountriesRequested event, Emitter emit, ) async { - if (!state.countriesHasMore) return; + if (!state.countriesHasMore || state.countriesIsLoadingMore) return; + + emit(state.copyWith(countriesIsLoadingMore: true)); try { final countriesResponse = await _countriesRepository.readAll( @@ -250,15 +250,23 @@ class CreateHeadlineBloc countries: List.of(state.countries)..addAll(countriesResponse.items), countriesCursor: countriesResponse.cursor, countriesHasMore: countriesResponse.hasMore, + countriesIsLoadingMore: false, ), ); } on HttpException catch (e) { - emit(state.copyWith(status: CreateHeadlineStatus.failure, exception: e)); + emit( + state.copyWith( + status: CreateHeadlineStatus.failure, + exception: e, + countriesIsLoadingMore: false, + ), + ); } catch (e) { emit( state.copyWith( status: CreateHeadlineStatus.failure, exception: UnknownException('An unexpected error occurred: $e'), + countriesIsLoadingMore: false, ), ); } diff --git a/lib/content_management/bloc/create_headline/create_headline_state.dart b/lib/content_management/bloc/create_headline/create_headline_state.dart index c914953..f4effd4 100644 --- a/lib/content_management/bloc/create_headline/create_headline_state.dart +++ b/lib/content_management/bloc/create_headline/create_headline_state.dart @@ -33,6 +33,7 @@ final class CreateHeadlineState extends Equatable { this.topics = const [], this.countries = const [], this.countriesHasMore = true, + this.countriesIsLoadingMore = false, this.countriesCursor, this.countrySearchTerm = '', this.contentStatus = ContentStatus.active, @@ -52,6 +53,7 @@ final class CreateHeadlineState extends Equatable { final List topics; final List countries; final bool countriesHasMore; + final bool countriesIsLoadingMore; final String? countriesCursor; final String countrySearchTerm; final ContentStatus contentStatus; @@ -81,6 +83,7 @@ final class CreateHeadlineState extends Equatable { List? topics, List? countries, bool? countriesHasMore, + bool? countriesIsLoadingMore, String? countriesCursor, String? countrySearchTerm, ContentStatus? contentStatus, @@ -100,6 +103,8 @@ final class CreateHeadlineState extends Equatable { topics: topics ?? this.topics, countries: countries ?? this.countries, countriesHasMore: countriesHasMore ?? this.countriesHasMore, + countriesIsLoadingMore: + countriesIsLoadingMore ?? this.countriesIsLoadingMore, countriesCursor: countriesCursor ?? this.countriesCursor, countrySearchTerm: countrySearchTerm ?? this.countrySearchTerm, contentStatus: contentStatus ?? this.contentStatus, @@ -122,6 +127,7 @@ final class CreateHeadlineState extends Equatable { topics, countries, countriesHasMore, + countriesIsLoadingMore, countriesCursor, countrySearchTerm, contentStatus, diff --git a/lib/content_management/bloc/create_source/create_source_bloc.dart b/lib/content_management/bloc/create_source/create_source_bloc.dart index fe4e63b..dda551c 100644 --- a/lib/content_management/bloc/create_source/create_source_bloc.dart +++ b/lib/content_management/bloc/create_source/create_source_bloc.dart @@ -18,10 +18,10 @@ class CreateSourceBloc extends Bloc { required DataRepository sourcesRepository, required DataRepository countriesRepository, required DataRepository languagesRepository, - }) : _sourcesRepository = sourcesRepository, - _countriesRepository = countriesRepository, - _languagesRepository = languagesRepository, - super(const CreateSourceState()) { + }) : _sourcesRepository = sourcesRepository, + _countriesRepository = countriesRepository, + _languagesRepository = languagesRepository, + super(const CreateSourceState()) { on(_onDataLoaded); on(_onNameChanged); on(_onDescriptionChanged); @@ -33,14 +33,14 @@ class CreateSourceBloc extends Bloc { on(_onSubmitted); on( _onCountrySearchChanged, - transformer: restartable(), + transformer: droppable(), ); on( _onLoadMoreCountriesRequested, ); on( _onLanguageSearchChanged, - transformer: restartable(), + transformer: droppable(), ); on( _onLoadMoreLanguagesRequested, @@ -194,8 +194,7 @@ class CreateSourceBloc extends Bloc { emit(state.copyWith(countrySearchTerm: event.searchTerm)); try { final countriesResponse = await _countriesRepository.readAll( - filter: - event.searchTerm.isNotEmpty ? {'name': event.searchTerm} : null, + filter: event.searchTerm.isNotEmpty ? {'name': event.searchTerm} : null, sort: [const SortOption('name', SortOrder.asc)], ); @@ -222,7 +221,9 @@ class CreateSourceBloc extends Bloc { CreateSourceLoadMoreCountriesRequested event, Emitter emit, ) async { - if (!state.countriesHasMore) return; + if (!state.countriesHasMore || state.countriesIsLoadingMore) return; + + emit(state.copyWith(countriesIsLoadingMore: true)); try { final countriesResponse = await _countriesRepository.readAll( @@ -240,15 +241,23 @@ class CreateSourceBloc extends Bloc { countries: List.of(state.countries)..addAll(countriesResponse.items), countriesCursor: countriesResponse.cursor, countriesHasMore: countriesResponse.hasMore, + countriesIsLoadingMore: false, ), ); } on HttpException catch (e) { - emit(state.copyWith(status: CreateSourceStatus.failure, exception: e)); + emit( + state.copyWith( + status: CreateSourceStatus.failure, + exception: e, + countriesIsLoadingMore: false, + ), + ); } catch (e) { emit( state.copyWith( status: CreateSourceStatus.failure, exception: UnknownException('An unexpected error occurred: $e'), + countriesIsLoadingMore: false, ), ); } @@ -262,8 +271,7 @@ class CreateSourceBloc extends Bloc { emit(state.copyWith(languageSearchTerm: event.searchTerm)); try { final languagesResponse = await _languagesRepository.readAll( - filter: - event.searchTerm.isNotEmpty ? {'name': event.searchTerm} : null, + filter: event.searchTerm.isNotEmpty ? {'name': event.searchTerm} : null, sort: [const SortOption('name', SortOrder.asc)], ); @@ -290,7 +298,9 @@ class CreateSourceBloc extends Bloc { CreateSourceLoadMoreLanguagesRequested event, Emitter emit, ) async { - if (!state.languagesHasMore) return; + if (!state.languagesHasMore || state.languagesIsLoadingMore) return; + + emit(state.copyWith(languagesIsLoadingMore: true)); try { final languagesResponse = await _languagesRepository.readAll( @@ -305,19 +315,26 @@ class CreateSourceBloc extends Bloc { emit( state.copyWith( - languages: List.of(state.languages) - ..addAll(languagesResponse.items), + languages: List.of(state.languages)..addAll(languagesResponse.items), languagesCursor: languagesResponse.cursor, languagesHasMore: languagesResponse.hasMore, + languagesIsLoadingMore: false, ), ); } on HttpException catch (e) { - emit(state.copyWith(status: CreateSourceStatus.failure, exception: e)); + emit( + state.copyWith( + status: CreateSourceStatus.failure, + exception: e, + languagesIsLoadingMore: false, + ), + ); } catch (e) { emit( state.copyWith( status: CreateSourceStatus.failure, exception: UnknownException('An unexpected error occurred: $e'), + languagesIsLoadingMore: false, ), ); } diff --git a/lib/content_management/bloc/create_source/create_source_state.dart b/lib/content_management/bloc/create_source/create_source_state.dart index 783a200..cc0aef6 100644 --- a/lib/content_management/bloc/create_source/create_source_state.dart +++ b/lib/content_management/bloc/create_source/create_source_state.dart @@ -31,10 +31,12 @@ final class CreateSourceState extends Equatable { this.headquarters, this.countries = const [], this.countriesHasMore = true, + this.countriesIsLoadingMore = false, this.countriesCursor, this.countrySearchTerm = '', this.languages = const [], this.languagesHasMore = true, + this.languagesIsLoadingMore = false, this.languagesCursor, this.languageSearchTerm = '', this.contentStatus = ContentStatus.active, @@ -51,10 +53,12 @@ final class CreateSourceState extends Equatable { final Country? headquarters; final List countries; final bool countriesHasMore; + final bool countriesIsLoadingMore; final String? countriesCursor; final String countrySearchTerm; final List languages; final bool languagesHasMore; + final bool languagesIsLoadingMore; final String? languagesCursor; final String languageSearchTerm; final ContentStatus contentStatus; @@ -80,10 +84,12 @@ final class CreateSourceState extends Equatable { ValueGetter? headquarters, List? countries, bool? countriesHasMore, + bool? countriesIsLoadingMore, String? countriesCursor, String? countrySearchTerm, List? languages, bool? languagesHasMore, + bool? languagesIsLoadingMore, String? languagesCursor, String? languageSearchTerm, ContentStatus? contentStatus, @@ -100,10 +106,14 @@ final class CreateSourceState extends Equatable { headquarters: headquarters != null ? headquarters() : this.headquarters, countries: countries ?? this.countries, countriesHasMore: countriesHasMore ?? this.countriesHasMore, + countriesIsLoadingMore: + countriesIsLoadingMore ?? this.countriesIsLoadingMore, countriesCursor: countriesCursor ?? this.countriesCursor, countrySearchTerm: countrySearchTerm ?? this.countrySearchTerm, languages: languages ?? this.languages, languagesHasMore: languagesHasMore ?? this.languagesHasMore, + languagesIsLoadingMore: + languagesIsLoadingMore ?? this.languagesIsLoadingMore, languagesCursor: languagesCursor ?? this.languagesCursor, languageSearchTerm: languageSearchTerm ?? this.languageSearchTerm, contentStatus: contentStatus ?? this.contentStatus, @@ -123,10 +133,12 @@ final class CreateSourceState extends Equatable { headquarters, countries, countriesHasMore, + countriesIsLoadingMore, countriesCursor, countrySearchTerm, languages, languagesHasMore, + languagesIsLoadingMore, languagesCursor, languageSearchTerm, contentStatus, diff --git a/lib/content_management/bloc/edit_headline/edit_headline_bloc.dart b/lib/content_management/bloc/edit_headline/edit_headline_bloc.dart index 412cde1..c1129e0 100644 --- a/lib/content_management/bloc/edit_headline/edit_headline_bloc.dart +++ b/lib/content_management/bloc/edit_headline/edit_headline_bloc.dart @@ -19,12 +19,12 @@ class EditHeadlineBloc extends Bloc { required DataRepository topicsRepository, required DataRepository countriesRepository, required String headlineId, - }) : _headlinesRepository = headlinesRepository, - _sourcesRepository = sourcesRepository, - _topicsRepository = topicsRepository, - _countriesRepository = countriesRepository, - _headlineId = headlineId, - super(const EditHeadlineState()) { + }) : _headlinesRepository = headlinesRepository, + _sourcesRepository = sourcesRepository, + _topicsRepository = topicsRepository, + _countriesRepository = countriesRepository, + _headlineId = headlineId, + super(const EditHeadlineState()) { on(_onLoaded); on(_onTitleChanged); on(_onExcerptChanged); @@ -37,7 +37,7 @@ class EditHeadlineBloc extends Bloc { on(_onSubmitted); on( _onCountrySearchChanged, - transformer: restartable(), + transformer: droppable(), ); on( _onLoadMoreCountriesRequested, @@ -253,8 +253,7 @@ class EditHeadlineBloc extends Bloc { emit(state.copyWith(countrySearchTerm: event.searchTerm)); try { final countriesResponse = await _countriesRepository.readAll( - filter: - event.searchTerm.isNotEmpty ? {'name': event.searchTerm} : null, + filter: event.searchTerm.isNotEmpty ? {'name': event.searchTerm} : null, sort: [const SortOption('name', SortOrder.asc)], ); @@ -281,7 +280,9 @@ class EditHeadlineBloc extends Bloc { EditHeadlineLoadMoreCountriesRequested event, Emitter emit, ) async { - if (!state.countriesHasMore) return; + if (!state.countriesHasMore || state.countriesIsLoadingMore) return; + + emit(state.copyWith(countriesIsLoadingMore: true)); try { final countriesResponse = await _countriesRepository.readAll( @@ -299,15 +300,23 @@ class EditHeadlineBloc extends Bloc { countries: List.of(state.countries)..addAll(countriesResponse.items), countriesCursor: countriesResponse.cursor, countriesHasMore: countriesResponse.hasMore, + countriesIsLoadingMore: false, ), ); } on HttpException catch (e) { - emit(state.copyWith(status: EditHeadlineStatus.failure, exception: e)); + emit( + state.copyWith( + status: EditHeadlineStatus.failure, + exception: e, + countriesIsLoadingMore: false, + ), + ); } catch (e) { emit( state.copyWith( status: EditHeadlineStatus.failure, exception: UnknownException('An unexpected error occurred: $e'), + countriesIsLoadingMore: false, ), ); } diff --git a/lib/content_management/bloc/edit_headline/edit_headline_state.dart b/lib/content_management/bloc/edit_headline/edit_headline_state.dart index 11e5b07..804e639 100644 --- a/lib/content_management/bloc/edit_headline/edit_headline_state.dart +++ b/lib/content_management/bloc/edit_headline/edit_headline_state.dart @@ -34,6 +34,7 @@ final class EditHeadlineState extends Equatable { this.topics = const [], this.countries = const [], this.countriesHasMore = true, + this.countriesIsLoadingMore = false, this.countriesCursor, this.countrySearchTerm = '', this.contentStatus = ContentStatus.active, @@ -54,6 +55,7 @@ final class EditHeadlineState extends Equatable { final List topics; final List countries; final bool countriesHasMore; + final bool countriesIsLoadingMore; final String? countriesCursor; final String countrySearchTerm; final ContentStatus contentStatus; @@ -84,6 +86,7 @@ final class EditHeadlineState extends Equatable { List? topics, List? countries, bool? countriesHasMore, + bool? countriesIsLoadingMore, String? countriesCursor, String? countrySearchTerm, ContentStatus? contentStatus, @@ -104,6 +107,8 @@ final class EditHeadlineState extends Equatable { topics: topics ?? this.topics, countries: countries ?? this.countries, countriesHasMore: countriesHasMore ?? this.countriesHasMore, + countriesIsLoadingMore: + countriesIsLoadingMore ?? this.countriesIsLoadingMore, countriesCursor: countriesCursor ?? this.countriesCursor, countrySearchTerm: countrySearchTerm ?? this.countrySearchTerm, contentStatus: contentStatus ?? this.contentStatus, @@ -127,6 +132,7 @@ final class EditHeadlineState extends Equatable { topics, countries, countriesHasMore, + countriesIsLoadingMore, countriesCursor, countrySearchTerm, contentStatus, diff --git a/lib/content_management/bloc/edit_source/edit_source_bloc.dart b/lib/content_management/bloc/edit_source/edit_source_bloc.dart index c8d8c7a..5f886a0 100644 --- a/lib/content_management/bloc/edit_source/edit_source_bloc.dart +++ b/lib/content_management/bloc/edit_source/edit_source_bloc.dart @@ -18,11 +18,11 @@ class EditSourceBloc extends Bloc { required DataRepository countriesRepository, required DataRepository languagesRepository, required String sourceId, - }) : _sourcesRepository = sourcesRepository, - _countriesRepository = countriesRepository, - _languagesRepository = languagesRepository, - _sourceId = sourceId, - super(const EditSourceState()) { + }) : _sourcesRepository = sourcesRepository, + _countriesRepository = countriesRepository, + _languagesRepository = languagesRepository, + _sourceId = sourceId, + super(const EditSourceState()) { on(_onLoaded); on(_onNameChanged); on(_onDescriptionChanged); @@ -34,14 +34,14 @@ class EditSourceBloc extends Bloc { on(_onSubmitted); on( _onCountrySearchChanged, - transformer: restartable(), + transformer: droppable(), ); on( _onLoadMoreCountriesRequested, ); on( _onLanguageSearchChanged, - transformer: restartable(), + transformer: droppable(), ); on( _onLoadMoreLanguagesRequested, @@ -248,8 +248,7 @@ class EditSourceBloc extends Bloc { emit(state.copyWith(countrySearchTerm: event.searchTerm)); try { final countriesResponse = await _countriesRepository.readAll( - filter: - event.searchTerm.isNotEmpty ? {'name': event.searchTerm} : null, + filter: event.searchTerm.isNotEmpty ? {'name': event.searchTerm} : null, sort: [const SortOption('name', SortOrder.asc)], ); @@ -276,7 +275,9 @@ class EditSourceBloc extends Bloc { EditSourceLoadMoreCountriesRequested event, Emitter emit, ) async { - if (!state.countriesHasMore) return; + if (!state.countriesHasMore || state.countriesIsLoadingMore) return; + + emit(state.copyWith(countriesIsLoadingMore: true)); try { final countriesResponse = await _countriesRepository.readAll( @@ -294,15 +295,23 @@ class EditSourceBloc extends Bloc { countries: List.of(state.countries)..addAll(countriesResponse.items), countriesCursor: countriesResponse.cursor, countriesHasMore: countriesResponse.hasMore, + countriesIsLoadingMore: false, ), ); } on HttpException catch (e) { - emit(state.copyWith(status: EditSourceStatus.failure, exception: e)); + emit( + state.copyWith( + status: EditSourceStatus.failure, + exception: e, + countriesIsLoadingMore: false, + ), + ); } catch (e) { emit( state.copyWith( status: EditSourceStatus.failure, exception: UnknownException('An unexpected error occurred: $e'), + countriesIsLoadingMore: false, ), ); } @@ -316,8 +325,7 @@ class EditSourceBloc extends Bloc { emit(state.copyWith(languageSearchTerm: event.searchTerm)); try { final languagesResponse = await _languagesRepository.readAll( - filter: - event.searchTerm.isNotEmpty ? {'name': event.searchTerm} : null, + filter: event.searchTerm.isNotEmpty ? {'name': event.searchTerm} : null, sort: [const SortOption('name', SortOrder.asc)], ); @@ -344,7 +352,9 @@ class EditSourceBloc extends Bloc { EditSourceLoadMoreLanguagesRequested event, Emitter emit, ) async { - if (!state.languagesHasMore) return; + if (!state.languagesHasMore || state.languagesIsLoadingMore) return; + + emit(state.copyWith(languagesIsLoadingMore: true)); try { final languagesResponse = await _languagesRepository.readAll( @@ -362,15 +372,23 @@ class EditSourceBloc extends Bloc { languages: List.of(state.languages)..addAll(languagesResponse.items), languagesCursor: languagesResponse.cursor, languagesHasMore: languagesResponse.hasMore, + languagesIsLoadingMore: false, ), ); } on HttpException catch (e) { - emit(state.copyWith(status: EditSourceStatus.failure, exception: e)); + emit( + state.copyWith( + status: EditSourceStatus.failure, + exception: e, + languagesIsLoadingMore: false, + ), + ); } catch (e) { emit( state.copyWith( status: EditSourceStatus.failure, exception: UnknownException('An unexpected error occurred: $e'), + languagesIsLoadingMore: false, ), ); } diff --git a/lib/content_management/bloc/edit_source/edit_source_state.dart b/lib/content_management/bloc/edit_source/edit_source_state.dart index f4e3281..82dda1c 100644 --- a/lib/content_management/bloc/edit_source/edit_source_state.dart +++ b/lib/content_management/bloc/edit_source/edit_source_state.dart @@ -31,10 +31,12 @@ final class EditSourceState extends Equatable { this.headquarters, this.countries = const [], this.countriesHasMore = true, + this.countriesIsLoadingMore = false, this.countriesCursor, this.countrySearchTerm = '', this.languages = const [], this.languagesHasMore = true, + this.languagesIsLoadingMore = false, this.languagesCursor, this.languageSearchTerm = '', this.contentStatus = ContentStatus.active, @@ -52,10 +54,12 @@ final class EditSourceState extends Equatable { final Country? headquarters; final List countries; final bool countriesHasMore; + final bool countriesIsLoadingMore; final String? countriesCursor; final String countrySearchTerm; final List languages; final bool languagesHasMore; + final bool languagesIsLoadingMore; final String? languagesCursor; final String languageSearchTerm; final ContentStatus contentStatus; @@ -82,10 +86,12 @@ final class EditSourceState extends Equatable { ValueGetter? headquarters, List? countries, bool? countriesHasMore, + bool? countriesIsLoadingMore, String? countriesCursor, String? countrySearchTerm, List? languages, bool? languagesHasMore, + bool? languagesIsLoadingMore, String? languagesCursor, String? languageSearchTerm, ContentStatus? contentStatus, @@ -103,10 +109,14 @@ final class EditSourceState extends Equatable { headquarters: headquarters != null ? headquarters() : this.headquarters, countries: countries ?? this.countries, countriesHasMore: countriesHasMore ?? this.countriesHasMore, + countriesIsLoadingMore: + countriesIsLoadingMore ?? this.countriesIsLoadingMore, countriesCursor: countriesCursor ?? this.countriesCursor, countrySearchTerm: countrySearchTerm ?? this.countrySearchTerm, languages: languages ?? this.languages, languagesHasMore: languagesHasMore ?? this.languagesHasMore, + languagesIsLoadingMore: + languagesIsLoadingMore ?? this.languagesIsLoadingMore, languagesCursor: languagesCursor ?? this.languagesCursor, languageSearchTerm: languageSearchTerm ?? this.languageSearchTerm, contentStatus: contentStatus ?? this.contentStatus, @@ -127,10 +137,12 @@ final class EditSourceState extends Equatable { headquarters, countries, countriesHasMore, + countriesIsLoadingMore, countriesCursor, countrySearchTerm, languages, languagesHasMore, + languagesIsLoadingMore, languagesCursor, languageSearchTerm, contentStatus, diff --git a/lib/shared/widgets/searchable_dropdown_form_field.dart b/lib/shared/widgets/searchable_dropdown_form_field.dart index e97b730..b7c1cc5 100644 --- a/lib/shared/widgets/searchable_dropdown_form_field.dart +++ b/lib/shared/widgets/searchable_dropdown_form_field.dart @@ -141,7 +141,8 @@ class _SearchableSelectionDialogState, S> } void _onScroll() { - if (_isBottom) { + final isLoading = widget.isLoadingExtractor(widget.bloc.state); + if (_isBottom && !isLoading) { widget.onLoadMore(); } } @@ -228,4 +229,4 @@ class _SearchableSelectionDialogState, S> }, ); } -} \ No newline at end of file +}