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 9c0da77..5b392cf 100644 --- a/lib/content_management/bloc/create_headline/create_headline_bloc.dart +++ b/lib/content_management/bloc/create_headline/create_headline_bloc.dart @@ -35,7 +35,6 @@ class CreateHeadlineBloc on(_onCountryChanged); on(_onStatusChanged); on(_onSubmitted); - on(_onLoadMoreCountriesRequested); } final DataRepository _headlinesRepository; @@ -76,6 +75,21 @@ class CreateHeadlineBloc countriesHasMore: countriesResponse.hasMore, ), ); + + // Start background fetching for all countries + while (state.countriesHasMore) { + final nextCountries = await _countriesRepository.readAll( + pagination: PaginationOptions(cursor: state.countriesCursor), + sort: [const SortOption('name', SortOrder.asc)], + ); + emit( + state.copyWith( + countries: List.of(state.countries)..addAll(nextCountries.items), + countriesCursor: nextCountries.cursor, + countriesHasMore: nextCountries.hasMore, + ), + ); + } } on HttpException catch (e) { emit(state.copyWith(status: CreateHeadlineStatus.failure, exception: e)); } catch (e) { @@ -190,47 +204,4 @@ class CreateHeadlineBloc ); } } - - Future _onLoadMoreCountriesRequested( - CreateHeadlineLoadMoreCountriesRequested event, - Emitter emit, - ) async { - if (!state.countriesHasMore || state.countriesIsLoadingMore) return; - - emit(state.copyWith(countriesIsLoadingMore: true)); - - try { - final countriesResponse = await _countriesRepository.readAll( - pagination: state.countriesCursor != null - ? PaginationOptions(cursor: state.countriesCursor) - : null, - sort: [const SortOption('name', SortOrder.asc)], - ); - - emit( - state.copyWith( - 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, - 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_event.dart b/lib/content_management/bloc/create_headline/create_headline_event.dart index f93a664..655eae9 100644 --- a/lib/content_management/bloc/create_headline/create_headline_event.dart +++ b/lib/content_management/bloc/create_headline/create_headline_event.dart @@ -83,9 +83,3 @@ final class CreateHeadlineStatusChanged extends CreateHeadlineEvent { final class CreateHeadlineSubmitted extends CreateHeadlineEvent { const CreateHeadlineSubmitted(); } - -/// Event to request loading more countries. -final class CreateHeadlineLoadMoreCountriesRequested - extends CreateHeadlineEvent { - const CreateHeadlineLoadMoreCountriesRequested(); -} 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 c2f2c88..3f534a6 100644 --- a/lib/content_management/bloc/create_source/create_source_bloc.dart +++ b/lib/content_management/bloc/create_source/create_source_bloc.dart @@ -31,12 +31,6 @@ class CreateSourceBloc extends Bloc { on(_onHeadquartersChanged); on(_onStatusChanged); on(_onSubmitted); - on( - _onLoadMoreCountriesRequested, - ); - on( - _onLoadMoreLanguagesRequested, - ); } final DataRepository _sourcesRepository; @@ -71,6 +65,36 @@ class CreateSourceBloc extends Bloc { languagesHasMore: languagesPaginated.hasMore, ), ); + + // Start background fetching for all countries + while (state.countriesHasMore) { + final nextCountries = await _countriesRepository.readAll( + pagination: PaginationOptions(cursor: state.countriesCursor), + sort: [const SortOption('name', SortOrder.asc)], + ); + emit( + state.copyWith( + countries: List.of(state.countries)..addAll(nextCountries.items), + countriesCursor: nextCountries.cursor, + countriesHasMore: nextCountries.hasMore, + ), + ); + } + + // Start background fetching for all languages + while (state.languagesHasMore) { + final nextLanguages = await _languagesRepository.readAll( + pagination: PaginationOptions(cursor: state.languagesCursor), + sort: [const SortOption('name', SortOrder.asc)], + ); + emit( + state.copyWith( + languages: List.of(state.languages)..addAll(nextLanguages.items), + languagesCursor: nextLanguages.cursor, + languagesHasMore: nextLanguages.hasMore, + ), + ); + } } on HttpException catch (e) { emit(state.copyWith(status: CreateSourceStatus.failure, exception: e)); } catch (e) { @@ -177,90 +201,4 @@ class CreateSourceBloc extends Bloc { ); } } - - Future _onLoadMoreCountriesRequested( - CreateSourceLoadMoreCountriesRequested event, - Emitter emit, - ) async { - if (!state.countriesHasMore || state.countriesIsLoadingMore) return; - - emit(state.copyWith(countriesIsLoadingMore: true)); - - try { - final countriesResponse = await _countriesRepository.readAll( - pagination: state.countriesCursor != null - ? PaginationOptions(cursor: state.countriesCursor) - : null, - sort: [const SortOption('name', SortOrder.asc)], - ); - - emit( - state.copyWith( - 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, - countriesIsLoadingMore: false, - ), - ); - } catch (e) { - emit( - state.copyWith( - status: CreateSourceStatus.failure, - exception: UnknownException('An unexpected error occurred: $e'), - countriesIsLoadingMore: false, - ), - ); - } - } - - Future _onLoadMoreLanguagesRequested( - CreateSourceLoadMoreLanguagesRequested event, - Emitter emit, - ) async { - if (!state.languagesHasMore || state.languagesIsLoadingMore) return; - - emit(state.copyWith(languagesIsLoadingMore: true)); - - try { - final languagesResponse = await _languagesRepository.readAll( - pagination: state.languagesCursor != null - ? PaginationOptions(cursor: state.languagesCursor) - : null, - sort: [const SortOption('name', SortOrder.asc)], - ); - - emit( - state.copyWith( - 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, - 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_event.dart b/lib/content_management/bloc/create_source/create_source_event.dart index 739cc3f..e4c4f56 100644 --- a/lib/content_management/bloc/create_source/create_source_event.dart +++ b/lib/content_management/bloc/create_source/create_source_event.dart @@ -75,13 +75,3 @@ final class CreateSourceStatusChanged extends CreateSourceEvent { final class CreateSourceSubmitted extends CreateSourceEvent { const CreateSourceSubmitted(); } - -/// Event to request loading more countries. -final class CreateSourceLoadMoreCountriesRequested extends CreateSourceEvent { - const CreateSourceLoadMoreCountriesRequested(); -} - -/// Event to request loading more languages. -final class CreateSourceLoadMoreLanguagesRequested extends CreateSourceEvent { - const CreateSourceLoadMoreLanguagesRequested(); -} 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 2d45156..8d358da 100644 --- a/lib/content_management/bloc/create_source/create_source_state.dart +++ b/lib/content_management/bloc/create_source/create_source_state.dart @@ -125,14 +125,14 @@ final class CreateSourceState extends Equatable { headquarters, countries, countriesHasMore, - countriesIsLoadingMore, - countriesCursor, - languages, - languagesHasMore, - languagesIsLoadingMore, - languagesCursor, - contentStatus, - exception, + countriesIsLoadingMore, + countriesCursor, + languages, + languagesHasMore, + languagesIsLoadingMore, + languagesCursor, + contentStatus, + exception, createdSource, ]; } 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 f280c48..8f12e49 100644 --- a/lib/content_management/bloc/edit_headline/edit_headline_bloc.dart +++ b/lib/content_management/bloc/edit_headline/edit_headline_bloc.dart @@ -35,9 +35,6 @@ class EditHeadlineBloc extends Bloc { on(_onCountryChanged); on(_onStatusChanged); on(_onSubmitted); - on( - _onLoadMoreCountriesRequested, - ); } final DataRepository _headlinesRepository; @@ -89,6 +86,21 @@ class EditHeadlineBloc extends Bloc { contentStatus: headline.status, ), ); + + // Start background fetching for all countries + while (state.countriesHasMore) { + final nextCountries = await _countriesRepository.readAll( + pagination: PaginationOptions(cursor: state.countriesCursor), + sort: [const SortOption('name', SortOrder.asc)], + ); + emit( + state.copyWith( + countries: List.of(state.countries)..addAll(nextCountries.items), + countriesCursor: nextCountries.cursor, + countriesHasMore: nextCountries.hasMore, + ), + ); + } } on HttpException catch (e) { emit(state.copyWith(status: EditHeadlineStatus.failure, exception: e)); } catch (e) { @@ -240,47 +252,4 @@ class EditHeadlineBloc extends Bloc { ); } } - - Future _onLoadMoreCountriesRequested( - EditHeadlineLoadMoreCountriesRequested event, - Emitter emit, - ) async { - if (!state.countriesHasMore || state.countriesIsLoadingMore) return; - - emit(state.copyWith(countriesIsLoadingMore: true)); - - try { - final countriesResponse = await _countriesRepository.readAll( - pagination: state.countriesCursor != null - ? PaginationOptions(cursor: state.countriesCursor) - : null, - sort: [const SortOption('name', SortOrder.asc)], - ); - - emit( - state.copyWith( - 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, - 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_event.dart b/lib/content_management/bloc/edit_headline/edit_headline_event.dart index 31de495..6bad7bd 100644 --- a/lib/content_management/bloc/edit_headline/edit_headline_event.dart +++ b/lib/content_management/bloc/edit_headline/edit_headline_event.dart @@ -83,8 +83,3 @@ final class EditHeadlineStatusChanged extends EditHeadlineEvent { final class EditHeadlineSubmitted extends EditHeadlineEvent { const EditHeadlineSubmitted(); } - -/// Event to request loading more countries. -final class EditHeadlineLoadMoreCountriesRequested extends EditHeadlineEvent { - const EditHeadlineLoadMoreCountriesRequested(); -} 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 a503235..1c98adc 100644 --- a/lib/content_management/bloc/edit_source/edit_source_bloc.dart +++ b/lib/content_management/bloc/edit_source/edit_source_bloc.dart @@ -32,12 +32,6 @@ class EditSourceBloc extends Bloc { on(_onHeadquartersChanged); on(_onStatusChanged); on(_onSubmitted); - on( - _onLoadMoreCountriesRequested, - ); - on( - _onLoadMoreLanguagesRequested, - ); } final DataRepository _sourcesRepository; @@ -95,6 +89,36 @@ class EditSourceBloc extends Bloc { languagesHasMore: languagesPaginated.hasMore, ), ); + + // Start background fetching for all countries + while (state.countriesHasMore) { + final nextCountries = await _countriesRepository.readAll( + pagination: PaginationOptions(cursor: state.countriesCursor), + sort: [const SortOption('name', SortOrder.asc)], + ); + emit( + state.copyWith( + countries: List.of(state.countries)..addAll(nextCountries.items), + countriesCursor: nextCountries.cursor, + countriesHasMore: nextCountries.hasMore, + ), + ); + } + + // Start background fetching for all languages + while (state.languagesHasMore) { + final nextLanguages = await _languagesRepository.readAll( + pagination: PaginationOptions(cursor: state.languagesCursor), + sort: [const SortOption('name', SortOrder.asc)], + ); + emit( + state.copyWith( + languages: List.of(state.languages)..addAll(nextLanguages.items), + languagesCursor: nextLanguages.cursor, + languagesHasMore: nextLanguages.hasMore, + ), + ); + } } on HttpException catch (e) { emit(state.copyWith(status: EditSourceStatus.failure, exception: e)); } catch (e) { @@ -231,90 +255,4 @@ class EditSourceBloc extends Bloc { ); } } - - Future _onLoadMoreCountriesRequested( - EditSourceLoadMoreCountriesRequested event, - Emitter emit, - ) async { - if (!state.countriesHasMore || state.countriesIsLoadingMore) return; - - emit(state.copyWith(countriesIsLoadingMore: true)); - - try { - final countriesResponse = await _countriesRepository.readAll( - pagination: state.countriesCursor != null - ? PaginationOptions(cursor: state.countriesCursor) - : null, - sort: [const SortOption('name', SortOrder.asc)], - ); - - emit( - state.copyWith( - 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, - countriesIsLoadingMore: false, - ), - ); - } catch (e) { - emit( - state.copyWith( - status: EditSourceStatus.failure, - exception: UnknownException('An unexpected error occurred: $e'), - countriesIsLoadingMore: false, - ), - ); - } - } - - Future _onLoadMoreLanguagesRequested( - EditSourceLoadMoreLanguagesRequested event, - Emitter emit, - ) async { - if (!state.languagesHasMore || state.languagesIsLoadingMore) return; - - emit(state.copyWith(languagesIsLoadingMore: true)); - - try { - final languagesResponse = await _languagesRepository.readAll( - pagination: state.languagesCursor != null - ? PaginationOptions(cursor: state.languagesCursor) - : null, - sort: [const SortOption('name', SortOrder.asc)], - ); - - emit( - state.copyWith( - 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, - 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_event.dart b/lib/content_management/bloc/edit_source/edit_source_event.dart index ce57667..8620d9a 100644 --- a/lib/content_management/bloc/edit_source/edit_source_event.dart +++ b/lib/content_management/bloc/edit_source/edit_source_event.dart @@ -86,13 +86,3 @@ final class EditSourceStatusChanged extends EditSourceEvent { final class EditSourceSubmitted extends EditSourceEvent { const EditSourceSubmitted(); } - -/// Event to request loading more countries. -final class EditSourceLoadMoreCountriesRequested extends EditSourceEvent { - const EditSourceLoadMoreCountriesRequested(); -} - -/// Event to request loading more languages. -final class EditSourceLoadMoreLanguagesRequested extends EditSourceEvent { - const EditSourceLoadMoreLanguagesRequested(); -} diff --git a/lib/content_management/view/create_headline_page.dart b/lib/content_management/view/create_headline_page.dart index 5829651..57a5087 100644 --- a/lib/content_management/view/create_headline_page.dart +++ b/lib/content_management/view/create_headline_page.dart @@ -244,16 +244,6 @@ class _CreateHeadlineViewState extends State<_CreateHeadlineView> { ), ), ), - if (state.countriesHasMore) - DropdownMenuItem( - value: null, - child: const Center( - child: Text('Load More'), - ), - onTap: () => context.read().add( - const CreateHeadlineLoadMoreCountriesRequested(), - ), - ), ], onChanged: (value) => context .read() diff --git a/lib/content_management/view/create_source_page.dart b/lib/content_management/view/create_source_page.dart index d073d2a..7487e05 100644 --- a/lib/content_management/view/create_source_page.dart +++ b/lib/content_management/view/create_source_page.dart @@ -175,16 +175,6 @@ class _CreateSourceViewState extends State<_CreateSourceView> { child: Text(language.name), ), ), - if (state.languagesHasMore) - DropdownMenuItem( - value: null, - child: const Center( - child: Text('Load More'), - ), - onTap: () => context.read().add( - const CreateSourceLoadMoreLanguagesRequested(), - ), - ), ], onChanged: (value) => context .read() @@ -241,16 +231,6 @@ class _CreateSourceViewState extends State<_CreateSourceView> { ), ), ), - if (state.countriesHasMore) - DropdownMenuItem( - value: null, - child: const Center( - child: Text('Load More'), - ), - onTap: () => context.read().add( - const CreateSourceLoadMoreCountriesRequested(), - ), - ), ], onChanged: (value) => context .read() diff --git a/lib/content_management/view/edit_headline_page.dart b/lib/content_management/view/edit_headline_page.dart index 5b5b006..bd92f72 100644 --- a/lib/content_management/view/edit_headline_page.dart +++ b/lib/content_management/view/edit_headline_page.dart @@ -312,16 +312,6 @@ class _EditHeadlineViewState extends State<_EditHeadlineView> { ), ), ), - if (state.countriesHasMore) - DropdownMenuItem( - value: null, - child: const Center( - child: Text('Load More'), - ), - onTap: () => context.read().add( - const EditHeadlineLoadMoreCountriesRequested(), - ), - ), ], onChanged: (value) => context .read() diff --git a/lib/content_management/view/edit_source_page.dart b/lib/content_management/view/edit_source_page.dart index c050df9..edb25a4 100644 --- a/lib/content_management/view/edit_source_page.dart +++ b/lib/content_management/view/edit_source_page.dart @@ -205,16 +205,6 @@ class _EditSourceViewState extends State<_EditSourceView> { child: Text(language.name), ), ), - if (state.languagesHasMore) - DropdownMenuItem( - value: null, - child: const Center( - child: Text('Load More'), - ), - onTap: () => context.read().add( - const EditSourceLoadMoreLanguagesRequested(), - ), - ), ], onChanged: (value) => context .read() @@ -271,16 +261,6 @@ class _EditSourceViewState extends State<_EditSourceView> { ), ), ), - if (state.countriesHasMore) - DropdownMenuItem( - value: null, - child: const Center( - child: Text('Load More'), - ), - onTap: () => context.read().add( - const EditSourceLoadMoreCountriesRequested(), - ), - ), ], onChanged: (value) => context .read() diff --git a/lib/shared/shared.dart b/lib/shared/shared.dart index b6d4d45..6ddc72e 100644 --- a/lib/shared/shared.dart +++ b/lib/shared/shared.dart @@ -1,2 +1 @@ export 'extensions/extensions.dart'; -export 'widgets/widgets.dart'; diff --git a/lib/shared/widgets/searchable_dropdown_form_field.dart b/lib/shared/widgets/searchable_dropdown_form_field.dart deleted file mode 100644 index b7c1cc5..0000000 --- a/lib/shared/widgets/searchable_dropdown_form_field.dart +++ /dev/null @@ -1,232 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:ui_kit/ui_kit.dart'; - -/// A generic type for the builder function that creates list items in the -/// searchable dropdown. -typedef SearchableDropdownItemBuilder = Widget Function( - BuildContext context, - T item, -); - -/// A generic type for the builder function that creates the widget to display -/// the selected item within the form field. -typedef SearchableDropdownSelectedItemBuilder = Widget Function( - BuildContext context, - T item, -); - -/// A form field that allows users to select an item from a searchable, -/// paginated list displayed in a modal dialog. -/// -/// This widget is generic and can be used for any type [T]. It requires -/// builders for constructing the list items and the selected item display, -/// as well as callbacks to handle searching and pagination. -class SearchableDropdownFormField, S> - extends FormField { - /// {@macro searchable_dropdown_form_field} - SearchableDropdownFormField({ - required B bloc, - required List Function(S state) itemsExtractor, - required bool Function(S state) hasMoreExtractor, - required bool Function(S state) isLoadingExtractor, - required ValueChanged onChanged, - required ValueChanged onSearchChanged, - required VoidCallback onLoadMore, - required SearchableDropdownItemBuilder itemBuilder, - required SearchableDropdownSelectedItemBuilder selectedItemBuilder, - super.key, - T? initialValue, - String? labelText, - String? searchHintText, - String? noItemsFoundText, - super.onSaved, - super.validator, - super.autovalidateMode = AutovalidateMode.onUserInteraction, - }) : super( - initialValue: initialValue, - builder: (FormFieldState state) { - // This is the widget that will be displayed in the form. - // It looks like a text field but opens a dialog on tap. - return InkWell( - onTap: () async { - final selectedItem = await showDialog( - context: state.context, - builder: (context) => _SearchableSelectionDialog( - bloc: bloc, - itemsExtractor: itemsExtractor, - hasMoreExtractor: hasMoreExtractor, - isLoadingExtractor: isLoadingExtractor, - onSearchChanged: onSearchChanged, - onLoadMore: onLoadMore, - itemBuilder: itemBuilder, - searchHintText: searchHintText, - noItemsFoundText: noItemsFoundText, - ), - ); - - if (selectedItem != null) { - state.didChange(selectedItem); - onChanged(selectedItem); - } - }, - child: InputDecorator( - decoration: InputDecoration( - labelText: labelText, - border: const OutlineInputBorder(), - errorText: state.errorText, - suffixIcon: const Icon(Icons.arrow_drop_down), - ), - child: state.value == null - ? const SizedBox(height: 20) // To maintain field height - : selectedItemBuilder(state.context, state.value as T), - ), - ); - }, - ); -} - -/// The modal dialog that contains the searchable and paginated list. -class _SearchableSelectionDialog, S> - extends StatefulWidget { - const _SearchableSelectionDialog({ - required this.bloc, - required this.itemsExtractor, - required this.hasMoreExtractor, - required this.isLoadingExtractor, - required this.onSearchChanged, - required this.onLoadMore, - required this.itemBuilder, - this.searchHintText, - this.noItemsFoundText, - super.key, - }); - - final B bloc; - final List Function(S state) itemsExtractor; - final bool Function(S state) hasMoreExtractor; - final bool Function(S state) isLoadingExtractor; - final ValueChanged onSearchChanged; - final VoidCallback onLoadMore; - final SearchableDropdownItemBuilder itemBuilder; - final String? searchHintText; - final String? noItemsFoundText; - - @override - State<_SearchableSelectionDialog> createState() => - _SearchableSelectionDialogState(); -} - -class _SearchableSelectionDialogState, S> - extends State<_SearchableSelectionDialog> { - final _scrollController = ScrollController(); - final _searchController = TextEditingController(); - - @override - void initState() { - super.initState(); - _scrollController.addListener(_onScroll); - _searchController.addListener(() { - widget.onSearchChanged(_searchController.text); - }); - } - - @override - void dispose() { - _scrollController - ..removeListener(_onScroll) - ..dispose(); - _searchController.dispose(); - super.dispose(); - } - - void _onScroll() { - final isLoading = widget.isLoadingExtractor(widget.bloc.state); - if (_isBottom && !isLoading) { - widget.onLoadMore(); - } - } - - bool get _isBottom { - if (!_scrollController.hasClients) return false; - final maxScroll = _scrollController.position.maxScrollExtent; - final currentScroll = _scrollController.offset; - // Add a small buffer to trigger before reaching the absolute bottom. - return currentScroll >= (maxScroll * 0.9); - } - - @override - Widget build(BuildContext context) { - return Dialog( - child: SizedBox( - width: 400, - height: 600, - child: Padding( - padding: const EdgeInsets.all(AppSpacing.lg), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - TextField( - controller: _searchController, - decoration: InputDecoration( - hintText: widget.searchHintText ?? 'Search...', - prefixIcon: const Icon(Icons.search), - border: const OutlineInputBorder(), - ), - ), - const SizedBox(height: AppSpacing.md), - Expanded( - child: BlocBuilder( - bloc: widget.bloc, - builder: (context, state) { - final items = widget.itemsExtractor(state); - final hasMore = widget.hasMoreExtractor(state); - final isLoading = widget.isLoadingExtractor(state); - - return _buildList(items, hasMore, isLoading); - }, - ), - ), - ], - ), - ), - ), - ); - } - - Widget _buildList(List items, bool hasMore, bool isLoading) { - if (isLoading && items.isEmpty) { - return const Center(child: CircularProgressIndicator()); - } - - if (items.isEmpty) { - return Center( - child: Text(widget.noItemsFoundText ?? 'No items found.'), - ); - } - - return ListView.builder( - controller: _scrollController, - itemCount: items.length + (hasMore ? 1 : 0), - itemBuilder: (context, index) { - if (index >= items.length) { - // This is the last item, which is the loading indicator. - // It's only shown if we have more items and are currently loading. - return isLoading - ? const Padding( - padding: EdgeInsets.symmetric(vertical: AppSpacing.md), - child: Center(child: CircularProgressIndicator()), - ) - : const SizedBox.shrink(); - } - - // This is a regular item. - final item = items[index]; - return InkWell( - onTap: () => Navigator.of(context).pop(item), - child: widget.itemBuilder(context, item), - ); - }, - ); - } -} diff --git a/lib/shared/widgets/widgets.dart b/lib/shared/widgets/widgets.dart deleted file mode 100644 index 70d878e..0000000 --- a/lib/shared/widgets/widgets.dart +++ /dev/null @@ -1 +0,0 @@ -export 'searchable_dropdown_form_field.dart';