Skip to content

Fix countrries and languages uncomplete list in the con,tent managment #43

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Conversation

fulleni
Copy link
Member

@fulleni fulleni commented Jul 31, 2025

Status

READY/IN DEVELOPMENT/HOLD

Description

Type of Change

  • ✨ New feature (non-breaking change which adds functionality)
  • 🛠️ Bug fix (non-breaking change which fixes an issue)
  • ❌ Breaking change (fix or feature that would cause existing functionality to change)
  • 🧹 Code refactor
  • ✅ Build configuration change
  • 📝 Documentation
  • 🗑️ Chore

fulleni added 22 commits July 31, 2025 19:20
- Add HttpException handling to provide more specific error information
- Introduce UnknownException for unexpected errors
- Update state.copyWith calls to include exception details
Refactors `CreateHeadlineState` and `EditHeadlineState` to support
paginated loading of countries.
Introduces new events to `CreateHeadlineBloc` and `EditHeadlineBloc`
to support paginated and searchable country dropdowns.

- `CountrySearchChanged`: Dispatched when the user types in the
  search field for countries.
- `LoadMoreCountriesRequested`: Dispatched when the user scrolls to the
  end of the country list, requesting the next page of data.
- Add debounce functionality for country search
- Implement load more countries functionality
- Refactor country data loading process
- Import bloc_concurrency package for concurrency management
- Implement country search functionality in CreateHeadlineBloc
- Add pagination support for loading more countries
- Update CreateHeadlineState to include country search term, cursor, and hasMore flag
…management

- Add bloc_concurrency package to improve handling of concurrent events
Refactors `CreateHeadlineBloc` and `EditHeadlineBloc` to support
paginated and searchable country data fetching.

- Updates `_onDataLoaded` and `_onLoaded` to fetch only the first page
  of countries initially.
- Implements `_onCountrySearchChanged` to fetch a new list of countries
  based on a search term, with a debounce to prevent excessive API calls.
- Implements `_onLoadMoreCountriesRequested` to fetch and append the
  next page of countries to the existing list.
- Updates state with pagination cursor and `hasMore` flag from the
  repository response.
Introduces a new reusable `SearchableDropdownFormField` widget. This
generic component provides a user-friendly and performant way to select
an item from a large, paginated data set.

- Displays a form field that, when tapped, opens a modal dialog.
- The modal contains a search input to filter the list.
- The list is paginated, fetching more items as the user scrolls.
- The widget is generic (`<T>`) and uses builder functions for items,
  making it adaptable for various data types (e.g., Country, Language).
Replaces the standard `CountryDropdownFormField` with the new
`SearchableDropdownFormField` in both the `CreateHeadlinePage` and
`EditHeadlinePage`.

This change connects the UI to the new pagination and search logic
in the `CreateHeadlineBloc` and `EditHeadlineBloc`, allowing users
to efficiently search and load countries from a large dataset.

The dropdown now displays the country's flag alongside its name for
an improved user experience.
Refactors `CreateSourceState` and `EditSourceState` to support
paginated loading of both countries and languages.

Replaces the simple lists with a set of fields to manage pagination
state for each entity: `hasMore`, `cursor`, and `searchTerm`.

This is the first step towards implementing searchable, paginated
dropdowns for country and language selection in the source forms.
Introduces new events to `CreateSourceBloc` and `EditSourceBloc`
to support paginated and searchable dropdowns for both countries
and languages.

- `CountrySearchChanged` / `LanguageSearchChanged`: Dispatched when the
  user types in the search field.
- `LoadMoreCountriesRequested` / `LoadMoreLanguagesRequested`: Dispatched
  when the user scrolls to the end of the list, requesting the next page.
Refactors `CreateSourceBloc` to support paginated and searchable data
fetching for both countries and languages.

- Updates `_onDataLoaded` to fetch only the first page of data.
- Implements handlers for searching and loading more data for both
  countries and languages, with a debounce for search inputs.
- Updates state with pagination cursors and `hasMore` flags from the
  repository responses.
Refactors `EditSourceBloc` to support paginated and searchable data
fetching for both countries and languages.

- Updates `_onLoaded` to fetch only the first page of data for dropdowns.
- Implements handlers for searching and loading more data for both
  countries and languages, with a debounce for search inputs.
- Updates state with pagination cursors and `hasMore` flags from the
  repository responses.
Replaces the standard `CountryDropdownFormField` and
`LanguageDropdownFormField` with the new `SearchableDropdownFormField`
in both the `CreateSourcePage` and `EditSourcePage`.

This change connects the UI to the new pagination and search logic
in the `CreateSourceBloc` and `EditSourceBloc`, allowing users
to efficiently search and load countries and languages from large
datasets.
Deletes the `CountryDropdownFormField` and `LanguageDropdownFormField`
widgets as they have been fully replaced by the new, more capable
`SearchableDropdownFormField` across the application.

Updates the shared widgets barrel file to remove the exports for the
deleted files.
Replaces the incorrect `debounce` transformer with the correct `restartable`
transformer from `bloc_concurrency` in all form BLoCs. This fixes the
runtime error and correctly implements a debounce for search fields.

Also completes the implementation for the language search and load-more
handlers in `CreateSourceBloc` and `EditSourceBloc`, which were
previously left incomplete. This includes adding proper error handling.
- Remove exports for 'country_dropdown_form_field.dart' and 'language_dropdown_form_field.dart'
- Keep export for 'searchable_dropdown_form_field.dart'
- Update country search logic to handle empty search terms
- Refine pagination handling when fetching more countries
- Remove unnecessary type casting from repository responses
- Refactor initial data loading to use typed responses
- Enhance country and language search to handle empty search terms
- Add null safety checks for pagination options
- Simplify response handling in various repository calls
- Enhance country filter to only apply when search term is not empty
- Refine pagination logic for country list retrieval
- Simplify response handling in Future.wait
- Use explicit type casting for repository responses
- Simplify variable assignments and type inference
- Remove unnecessary comments
Corrects multiple errors in `EditSourceBloc`:
- Fixes type inference for `Future.wait` by explicitly handling the returned list of dynamic objects.
- Replaces incorrect syntax for finding the selected language with a robust `try-catch` block around `firstWhere`.
- Correctly passes pagination parameters by wrapping the cursor in a `PaginationOptions` object.
- Refines search logic to prevent sending filters with empty search terms.
- Removes unnecessary casts.
@fulleni fulleni merged commit 1b13a0f into main Jul 31, 2025
1 check failed
@fulleni fulleni deleted the fix-countrries-and-languages-uncomplete-list-in-the-con,tent-managment branch July 31, 2025 20:16
Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary of Changes

Hello @fulleni, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the user experience in content management by introducing robust search and pagination capabilities for country and language selection fields. It refactors the underlying BLoC logic and introduces a new generic UI component to support dynamic data loading, ensuring users can efficiently find and select items from potentially large datasets.

Highlights

  • Searchable & Paginated Dropdowns: Implemented search and pagination for country and language selection across content management forms (Create/Edit Headline, Create/Edit Source) to address incomplete lists.
  • New UI Component: Introduced a new generic SearchableDropdownFormField widget (lib/shared/widgets/searchable_dropdown_form_field.dart) that provides a reusable UI for searchable and paginated dropdowns, replacing the previous static CountryDropdownFormField and LanguageDropdownFormField.
  • BLoC Logic Updates: Updated the BLoC layers (CreateHeadlineBloc, CreateSourceBloc, EditHeadlineBloc, EditSourceBloc) to manage search terms, pagination cursors, and 'hasMore' flags for countries and languages. New events and handlers were added to support dynamic fetching, searching, and infinite scrolling of these lists.
  • Dependency Update: Added bloc_concurrency to manage event processing, specifically using restartable() transformer for search events to debounce and cancel previous searches.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in issue comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments or fill out our survey to provide feedback.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a significant improvement by replacing standard dropdowns with a new searchable and paginated component for selecting countries and languages. This will greatly enhance the user experience, especially for long lists. My review focuses on improving the implementation within the BLoCs that support this new UI, suggesting debounce for search, safer Future.wait handling, removing legacy code, and highlighting areas of code duplication.

Comment on lines +61 to +70
final responses = await Future.wait([
_countriesRepository.readAll(
sort: [const SortOption('name', SortOrder.asc)],
),
_languagesRepository.readAll(
sort: [const SortOption('name', SortOrder.asc)],
),
]);

final countries = (countriesResponse as PaginatedResponse<Country>).items;
final languages = (languagesResponse as PaginatedResponse<Language>).items;

final countriesPaginated = responses[0] as PaginatedResponse<Country>;
final languagesPaginated = responses[1] as PaginatedResponse<Language>;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Accessing results from Future.wait by index is brittle. If the order of futures changes, this will lead to runtime errors that are hard to debug. It's safer to use destructuring with a pattern, which also makes the code more readable.

      final [countriesResponse, languagesResponse] = await Future.wait([
        _countriesRepository.readAll(
          sort: [const SortOption('name', SortOrder.asc)],
        ),
        _languagesRepository.readAll(
          sort: [const SortOption('name', SortOrder.asc)],
        ),
      ]);
      final countriesPaginated = countriesResponse as PaginatedResponse<Country>;
      final languagesPaginated = languagesResponse as PaginatedResponse<Language>;

Comment on lines +59 to +71
final responses = await Future.wait([
_headlinesRepository.read(id: _headlineId),
_sourcesRepository.readAll(
sort: [const SortOption('updatedAt', SortOrder.desc)],
),
_topicsRepository.readAll(
sort: [const SortOption('updatedAt', SortOrder.desc)],
),
_countriesRepository.readAll(
sort: [const SortOption('name', SortOrder.asc)],
),
]);

final headline = headlineResponse as Headline;
final sources = (sourcesResponse as PaginatedResponse<Source>).items;
final topics = (topicsResponse as PaginatedResponse<Topic>).items;
final countries = (countriesResponse as PaginatedResponse<Country>).items;
final headline = responses[0] as Headline;
final sources = (responses[1] as PaginatedResponse<Source>).items;
final topics = (responses[2] as PaginatedResponse<Topic>).items;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Accessing results from Future.wait by index is brittle. If the order of futures changes, this will lead to runtime errors. It's safer to use destructuring with a pattern, which also makes the code more readable.

      final [headlineResponse, sourcesResponse, topicsResponse] =
          await Future.wait([
        _headlinesRepository.read(id: _headlineId),
        _sourcesRepository.readAll(
          sort: [const SortOption('updatedAt', SortOrder.desc)],
        ),
        _topicsRepository.readAll(
          sort: [const SortOption('updatedAt', SortOrder.desc)],
        ),
      ]);

      final headline = headlineResponse as Headline;
      final sources = (sourcesResponse as PaginatedResponse<Source>).items;
      final topics = (topicsResponse as PaginatedResponse<Topic>).items;

CreateSourceLanguageSearchChanged event,
Emitter<CreateSourceState> emit,
) async {
await Future<void>.delayed(_searchDebounceDuration);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

With the debounce transformer applied to the event handler, this manual delay is no longer necessary and should be removed.

Comment on lines +41 to +44
on<CreateSourceLanguageSearchChanged>(
_onLanguageSearchChanged,
transformer: restartable(),
);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Similar to the country search, it's better to use the debounce transformer here for a cleaner implementation.

    on<CreateSourceLanguageSearchChanged>(
      _onLanguageSearchChanged,
      transformer: debounce(_searchDebounceDuration),
    );

Comment on lines +34 to +37
on<CreateSourceCountrySearchChanged>(
_onCountrySearchChanged,
transformer: restartable(),
);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

For handling search input with a delay, bloc_concurrency provides a debounce transformer that is more idiomatic for this use case than restartable() combined with a manual Future.delayed. Using debounce will make the intent clearer and the event handler code cleaner.

    on<CreateSourceCountrySearchChanged>(
      _onCountrySearchChanged,
      transformer: debounce(_searchDebounceDuration),
    );

Comment on lines +35 to +38
on<EditSourceCountrySearchChanged>(
_onCountrySearchChanged,
transformer: restartable(),
);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

For handling search input with a delay, bloc_concurrency provides a debounce transformer that is more idiomatic for this use case than restartable() combined with a manual Future.delayed. Using debounce will make the intent clearer and the event handler code cleaner.

    on<EditSourceCountrySearchChanged>(
      _onCountrySearchChanged,
      transformer: debounce(_searchDebounceDuration),
    );

Comment on lines +42 to +45
on<EditSourceLanguageSearchChanged>(
_onLanguageSearchChanged,
transformer: restartable(),
);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Similar to the country search, it's better to use the debounce transformer here for a cleaner implementation.

    on<EditSourceLanguageSearchChanged>(
      _onLanguageSearchChanged,
      transformer: debounce(_searchDebounceDuration),
    );

Comment on lines +76 to +85
Language? selectedLanguage;
try {
// Find the equivalent language object from the full list.
// This ensures the DropdownButton can identify it by reference.
selectedLanguage = languagesPaginated.items.firstWhere(
(listLanguage) => listLanguage.id == source.language?.id,
);
} catch (_) {
selectedLanguage = source.language;
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This logic to find selectedLanguage from the languagesPaginated.items list appears to be a leftover from when a standard DropdownButtonFormField was used. The new SearchableDropdownFormField does not require the initialValue to be an instance from the items list. You can simplify this by directly assigning source.language.

      final selectedLanguage = source.language;

EditSourceCountrySearchChanged event,
Emitter<EditSourceState> emit,
) async {
await Future<void>.delayed(_searchDebounceDuration);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

With the debounce transformer applied to the event handler, this manual delay is no longer necessary and should be removed.

EditSourceLanguageSearchChanged event,
Emitter<EditSourceState> emit,
) async {
await Future<void>.delayed(_searchDebounceDuration);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

With the debounce transformer applied to the event handler, this manual delay is no longer necessary and should be removed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant