diff --git a/lib/headlines-feed/bloc/headlines_feed_bloc.dart b/lib/headlines-feed/bloc/headlines_feed_bloc.dart index d07c6faa..687a83c5 100644 --- a/lib/headlines-feed/bloc/headlines_feed_bloc.dart +++ b/lib/headlines-feed/bloc/headlines_feed_bloc.dart @@ -60,20 +60,22 @@ class HeadlinesFeedBloc extends Bloc { ) async { emit(HeadlinesFeedLoading()); // Show loading for filter application try { - final response = await _headlinesRepository.readAllByQuery({ - if (event.filter.categories?.isNotEmpty ?? false) - 'categories': - event.filter.categories! - .whereType() - .map((c) => c.id) - .toList(), - if (event.filter.sources?.isNotEmpty ?? false) - 'sources': - event.filter.sources! - .whereType() - .map((s) => s.id) - .toList(), - }, limit: _headlinesFetchLimit); + final queryParams = {}; + if (event.filter.categories?.isNotEmpty ?? false) { + queryParams['categories'] = event.filter.categories! + .map((c) => c.id) + .join(','); + } + if (event.filter.sources?.isNotEmpty ?? false) { + queryParams['sources'] = event.filter.sources! + .map((s) => s.id) + .join(','); + } + + final response = await _headlinesRepository.readAllByQuery( + queryParams, + limit: _headlinesFetchLimit, + ); emit( HeadlinesFeedLoaded( headlines: response.items, @@ -133,8 +135,8 @@ class HeadlinesFeedBloc extends Bloc { Emitter emit, ) async { // Determine current filter and cursor based on state - var currentFilter = const HeadlineFilter(); - var currentCursor = + var currentFilter = const HeadlineFilter(); // Made type explicit + var currentCursor = // Made type explicit event.cursor; // Use event's cursor if provided (for pagination) var currentHeadlines = []; var isPaginating = false; @@ -169,21 +171,20 @@ class HeadlinesFeedBloc extends Bloc { } try { + final queryParams = {}; + if (currentFilter.categories?.isNotEmpty ?? false) { + queryParams['categories'] = currentFilter.categories! + .map((c) => c.id) + .join(','); + } + if (currentFilter.sources?.isNotEmpty ?? false) { + queryParams['sources'] = currentFilter.sources! + .map((s) => s.id) + .join(','); + } + final response = await _headlinesRepository.readAllByQuery( - { - if (currentFilter.categories?.isNotEmpty ?? false) - 'categories': - currentFilter.categories! - .whereType() - .map((c) => c.id) - .toList(), - if (currentFilter.sources?.isNotEmpty ?? false) - 'sources': - currentFilter.sources! - .whereType() - .map((s) => s.id) - .toList(), - }, + queryParams, limit: _headlinesFetchLimit, startAfterId: currentCursor, // Use determined cursor ); @@ -216,27 +217,29 @@ class HeadlinesFeedBloc extends Bloc { emit(HeadlinesFeedLoading()); // Show loading indicator for refresh // Determine the filter currently applied in the state - var currentFilter = const HeadlineFilter(); + var currentFilter = const HeadlineFilter(); // Made type explicit if (state is HeadlinesFeedLoaded) { currentFilter = (state as HeadlinesFeedLoaded).filter; } try { + final queryParams = {}; + if (currentFilter.categories?.isNotEmpty ?? false) { + queryParams['categories'] = currentFilter.categories! + .map((c) => c.id) + .join(','); + } + if (currentFilter.sources?.isNotEmpty ?? false) { + queryParams['sources'] = currentFilter.sources! + .map((s) => s.id) + .join(','); + } + // Fetch the first page using the current filter - final response = await _headlinesRepository.readAllByQuery({ - if (currentFilter.categories?.isNotEmpty ?? false) - 'categories': - currentFilter.categories! - .whereType() - .map((c) => c.id) - .toList(), - if (currentFilter.sources?.isNotEmpty ?? false) - 'sources': - currentFilter.sources! - .whereType() - .map((s) => s.id) - .toList(), - }, limit: _headlinesFetchLimit); + final response = await _headlinesRepository.readAllByQuery( + queryParams, + limit: _headlinesFetchLimit, + ); emit( HeadlinesFeedLoaded( headlines: response.items, // Replace headlines on refresh diff --git a/lib/headlines-feed/bloc/sources_filter_bloc.dart b/lib/headlines-feed/bloc/sources_filter_bloc.dart index 703aab30..048a98bf 100644 --- a/lib/headlines-feed/bloc/sources_filter_bloc.dart +++ b/lib/headlines-feed/bloc/sources_filter_bloc.dart @@ -18,11 +18,11 @@ class SourcesFilterBloc extends Bloc { super(const SourcesFilterState()) { on(_onLoadSourceFilterData); on(_onCountryCapsuleToggled); - on(_onAllSourceTypesCapsuleToggled); // Added + on(_onAllSourceTypesCapsuleToggled); on(_onSourceTypeCapsuleToggled); on(_onSourceCheckboxToggled); on(_onClearSourceFiltersRequested); - on<_FetchFilteredSourcesRequested>(_onFetchFilteredSourcesRequested); + // Removed _FetchFilteredSourcesRequested event listener } final HtDataRepository _sourcesRepository; @@ -40,32 +40,37 @@ class SourcesFilterBloc extends Bloc { final initialSelectedSourceIds = event.initialSelectedSources.map((s) => s.id).toSet(); - // Initialize selected capsules based on initialSelectedSources - final initialSelectedCountryIsoCodes = {}; - final initialSelectedSourceTypes = {}; - - if (event.initialSelectedSources.isNotEmpty) { - for (final source in event.initialSelectedSources) { - if (source.headquarters?.isoCode != null) { - initialSelectedCountryIsoCodes.add(source.headquarters!.isoCode); - } - if (source.sourceType != null) { - initialSelectedSourceTypes.add(source.sourceType!); - } - } - } + // Use the passed-in initial capsule selections directly + final initialSelectedCountryIsoCodes = + event.initialSelectedCountryIsoCodes; + final initialSelectedSourceTypes = event.initialSelectedSourceTypes; + + final allSourcesResponse = await _sourcesRepository.readAll(); + final allAvailableSources = allSourcesResponse.items; + + // Initially, display all sources. Capsules are visually set but don't filter the list yet. + // Filtering will occur if a capsule is manually toggled. + // However, if initial capsule filters ARE provided, we should respect them for the initial display. + final displayableSources = _getFilteredSources( + allSources: allAvailableSources, + selectedCountries: initialSelectedCountryIsoCodes, // Use event's data + selectedTypes: initialSelectedSourceTypes, // Use event's data + ); emit( state.copyWith( availableCountries: availableCountries.items, + allAvailableSources: allAvailableSources, + displayableSources: + displayableSources, // Now correctly filtered if initial capsules were set finallySelectedSourceIds: initialSelectedSourceIds, - selectedCountryIsoCodes: initialSelectedCountryIsoCodes, - selectedSourceTypes: initialSelectedSourceTypes, - // Keep loading status until sources are fetched + selectedCountryIsoCodes: + initialSelectedCountryIsoCodes, // Use event's data + selectedSourceTypes: initialSelectedSourceTypes, // Use event's data + dataLoadingStatus: SourceFilterDataLoadingStatus.success, + clearErrorMessage: true, ), ); - // Trigger initial fetch of displayable sources - add(const _FetchFilteredSourcesRequested()); } catch (e) { emit( state.copyWith( @@ -95,34 +100,57 @@ class SourcesFilterBloc extends Bloc { currentSelected.add(event.countryIsoCode); } } - emit(state.copyWith(selectedCountryIsoCodes: currentSelected)); - add(const _FetchFilteredSourcesRequested()); + final newDisplayableSources = _getFilteredSources( + allSources: state.allAvailableSources, + selectedCountries: currentSelected, + selectedTypes: state.selectedSourceTypes, + ); + emit( + state.copyWith( + selectedCountryIsoCodes: currentSelected, + displayableSources: newDisplayableSources, + ), + ); } - Future _onAllSourceTypesCapsuleToggled( + void _onAllSourceTypesCapsuleToggled( AllSourceTypesCapsuleToggled event, Emitter emit, - ) async { - // Toggling "All" for source types means clearing any specific selections. - // If already clear, it remains clear. - emit(state.copyWith(selectedSourceTypes: {})); - add(const _FetchFilteredSourcesRequested()); + ) { + final newDisplayableSources = _getFilteredSources( + allSources: state.allAvailableSources, + selectedCountries: state.selectedCountryIsoCodes, + selectedTypes: {}, // Cleared source types + ); + emit( + state.copyWith( + selectedSourceTypes: {}, + displayableSources: newDisplayableSources, + ), + ); } - Future _onSourceTypeCapsuleToggled( + void _onSourceTypeCapsuleToggled( SourceTypeCapsuleToggled event, Emitter emit, - ) async { + ) { final currentSelected = Set.from(state.selectedSourceTypes); if (currentSelected.contains(event.sourceType)) { currentSelected.remove(event.sourceType); } else { currentSelected.add(event.sourceType); } - // If specific types are selected, "All" is no longer true. - // The UI will derive "All" state from selectedSourceTypes.isEmpty - emit(state.copyWith(selectedSourceTypes: currentSelected)); - add(const _FetchFilteredSourcesRequested()); + final newDisplayableSources = _getFilteredSources( + allSources: state.allAvailableSources, + selectedCountries: state.selectedCountryIsoCodes, + selectedTypes: currentSelected, + ); + emit( + state.copyWith( + selectedSourceTypes: currentSelected, + displayableSources: newDisplayableSources, + ), + ); } void _onSourceCheckboxToggled( @@ -147,55 +175,33 @@ class SourcesFilterBloc extends Bloc { selectedCountryIsoCodes: {}, selectedSourceTypes: {}, finallySelectedSourceIds: {}, - // Keep availableCountries and availableSourceTypes - ), - ); - add(const _FetchFilteredSourcesRequested()); - } - - Future _onFetchFilteredSourcesRequested( - _FetchFilteredSourcesRequested event, - Emitter emit, - ) async { - emit( - state.copyWith( - dataLoadingStatus: SourceFilterDataLoadingStatus.loading, - displayableSources: [], // Clear previous sources + displayableSources: List.from(state.allAvailableSources), // Reset + dataLoadingStatus: SourceFilterDataLoadingStatus.success, clearErrorMessage: true, ), ); - try { - final queryParameters = {}; - if (state.selectedCountryIsoCodes.isNotEmpty) { - queryParameters['countries'] = state.selectedCountryIsoCodes.join(','); - } - if (state.selectedSourceTypes.isNotEmpty) { - queryParameters['sourceTypes'] = state.selectedSourceTypes - .map((st) => st.name) - .join(','); - } + } - final response = await _sourcesRepository.readAllByQuery(queryParameters); - emit( - state.copyWith( - displayableSources: response.items, - dataLoadingStatus: SourceFilterDataLoadingStatus.success, - ), - ); - } on HtHttpException catch (e) { - emit( - state.copyWith( - dataLoadingStatus: SourceFilterDataLoadingStatus.failure, - errorMessage: e.message, - ), - ); - } catch (e) { - emit( - state.copyWith( - dataLoadingStatus: SourceFilterDataLoadingStatus.failure, - errorMessage: 'An unexpected error occurred while fetching sources.', - ), - ); + // Helper method to filter sources based on selected countries and types + List _getFilteredSources({ + required List allSources, + required Set selectedCountries, + required Set selectedTypes, + }) { + if (selectedCountries.isEmpty && selectedTypes.isEmpty) { + return List.from(allSources); // Return all if no filters } + + return allSources.where((source) { + final matchesCountry = + selectedCountries.isEmpty || + (source.headquarters != null && + selectedCountries.contains(source.headquarters!.isoCode)); + final matchesType = + selectedTypes.isEmpty || + (source.sourceType != null && + selectedTypes.contains(source.sourceType)); + return matchesCountry && matchesType; + }).toList(); } } diff --git a/lib/headlines-feed/bloc/sources_filter_event.dart b/lib/headlines-feed/bloc/sources_filter_event.dart index f41451a3..e5ff1220 100644 --- a/lib/headlines-feed/bloc/sources_filter_event.dart +++ b/lib/headlines-feed/bloc/sources_filter_event.dart @@ -10,12 +10,22 @@ abstract class SourcesFilterEvent extends Equatable { } class LoadSourceFilterData extends SourcesFilterEvent { - const LoadSourceFilterData({this.initialSelectedSources = const []}); + const LoadSourceFilterData({ + this.initialSelectedSources = const [], + this.initialSelectedCountryIsoCodes = const {}, + this.initialSelectedSourceTypes = const {}, + }); final List initialSelectedSources; + final Set initialSelectedCountryIsoCodes; + final Set initialSelectedSourceTypes; @override - List get props => [initialSelectedSources]; + List get props => [ + initialSelectedSources, + initialSelectedCountryIsoCodes, + initialSelectedSourceTypes, + ]; } class CountryCapsuleToggled extends SourcesFilterEvent { diff --git a/lib/headlines-feed/bloc/sources_filter_state.dart b/lib/headlines-feed/bloc/sources_filter_state.dart index 22402cd5..818b10da 100644 --- a/lib/headlines-feed/bloc/sources_filter_state.dart +++ b/lib/headlines-feed/bloc/sources_filter_state.dart @@ -10,6 +10,7 @@ class SourcesFilterState extends Equatable { this.selectedCountryIsoCodes = const {}, this.availableSourceTypes = SourceType.values, this.selectedSourceTypes = const {}, + this.allAvailableSources = const [], // Added new property this.displayableSources = const [], this.finallySelectedSourceIds = const {}, this.dataLoadingStatus = SourceFilterDataLoadingStatus.initial, @@ -20,6 +21,7 @@ class SourcesFilterState extends Equatable { final Set selectedCountryIsoCodes; final List availableSourceTypes; final Set selectedSourceTypes; + final List allAvailableSources; // Added new property final List displayableSources; final Set finallySelectedSourceIds; final SourceFilterDataLoadingStatus dataLoadingStatus; @@ -30,6 +32,7 @@ class SourcesFilterState extends Equatable { Set? selectedCountryIsoCodes, List? availableSourceTypes, Set? selectedSourceTypes, + List? allAvailableSources, // Added new property List? displayableSources, Set? finallySelectedSourceIds, SourceFilterDataLoadingStatus? dataLoadingStatus, @@ -42,6 +45,8 @@ class SourcesFilterState extends Equatable { selectedCountryIsoCodes ?? this.selectedCountryIsoCodes, availableSourceTypes: availableSourceTypes ?? this.availableSourceTypes, selectedSourceTypes: selectedSourceTypes ?? this.selectedSourceTypes, + allAvailableSources: + allAvailableSources ?? this.allAvailableSources, // Added displayableSources: displayableSources ?? this.displayableSources, finallySelectedSourceIds: finallySelectedSourceIds ?? this.finallySelectedSourceIds, @@ -57,6 +62,7 @@ class SourcesFilterState extends Equatable { selectedCountryIsoCodes, availableSourceTypes, selectedSourceTypes, + allAvailableSources, // Added new property displayableSources, finallySelectedSourceIds, dataLoadingStatus, diff --git a/lib/headlines-feed/models/headline_filter.dart b/lib/headlines-feed/models/headline_filter.dart index 6b617875..e44d586c 100644 --- a/lib/headlines-feed/models/headline_filter.dart +++ b/lib/headlines-feed/models/headline_filter.dart @@ -6,7 +6,12 @@ import 'package:ht_shared/ht_shared.dart'; /// {@endtemplate} class HeadlineFilter extends Equatable { /// {@macro headline_filter} - const HeadlineFilter({this.categories, this.sources}); + const HeadlineFilter({ + this.categories, + this.sources, + this.selectedSourceCountryIsoCodes, + this.selectedSourceSourceTypes, + }); /// The list of selected category filters. /// Headlines matching *any* of these categories will be included (OR logic). @@ -16,15 +21,35 @@ class HeadlineFilter extends Equatable { /// Headlines matching *any* of these sources will be included (OR logic). final List? sources; + /// The set of selected country ISO codes for source filtering. + final Set? selectedSourceCountryIsoCodes; + + /// The set of selected source types for source filtering. + final Set? selectedSourceSourceTypes; + @override - List get props => [categories, sources]; + List get props => [ + categories, + sources, + selectedSourceCountryIsoCodes, + selectedSourceSourceTypes, + ]; /// Creates a copy of this [HeadlineFilter] with the given fields /// replaced with the new values. - HeadlineFilter copyWith({List? categories, List? sources}) { + HeadlineFilter copyWith({ + List? categories, + List? sources, + Set? selectedSourceCountryIsoCodes, + Set? selectedSourceSourceTypes, + }) { return HeadlineFilter( categories: categories ?? this.categories, sources: sources ?? this.sources, + selectedSourceCountryIsoCodes: + selectedSourceCountryIsoCodes ?? this.selectedSourceCountryIsoCodes, + selectedSourceSourceTypes: + selectedSourceSourceTypes ?? this.selectedSourceSourceTypes, ); } } diff --git a/lib/headlines-feed/view/headlines_filter_page.dart b/lib/headlines-feed/view/headlines_filter_page.dart index 43d2dc4d..095d92f1 100644 --- a/lib/headlines-feed/view/headlines_filter_page.dart +++ b/lib/headlines-feed/view/headlines_filter_page.dart @@ -1,5 +1,5 @@ // -// ignore_for_file: lines_longer_than_80_chars +// ignore_for_file: lines_longer_than_80_chars, public_member_api_docs import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -9,8 +9,12 @@ import 'package:ht_main/headlines-feed/models/headline_filter.dart'; import 'package:ht_main/l10n/l10n.dart'; import 'package:ht_main/router/routes.dart'; import 'package:ht_main/shared/constants/constants.dart'; -import 'package:ht_shared/ht_shared.dart' - show Category, Source; // Import models from ht_shared, Country removed +import 'package:ht_shared/ht_shared.dart' show Category, Source, SourceType; + +// Keys for passing data to/from SourceFilterPage +const String keySelectedSources = 'selectedSources'; +const String keySelectedCountryIsoCodes = 'selectedCountryIsoCodes'; +const String keySelectedSourceTypes = 'selectedSourceTypes'; /// {@template headlines_filter_page} /// A full-screen dialog page for selecting headline filters. @@ -35,7 +39,9 @@ class _HeadlinesFilterPageState extends State { /// and are only applied back to the BLoC when the user taps 'Apply'. late List _tempSelectedCategories; late List _tempSelectedSources; - // late List _tempSelectedCountries; // Removed + // State for source filter capsules, to be passed to and from SourceFilterPage + late Set _tempSelectedSourceCountryIsoCodes; + late Set _tempSelectedSourceSourceTypes; @override void initState() { @@ -48,15 +54,18 @@ class _HeadlinesFilterPageState extends State { // Create copies of the lists to avoid modifying the BLoC state directly. _tempSelectedCategories = List.from(currentState.filter.categories ?? []); _tempSelectedSources = List.from(currentState.filter.sources ?? []); - // _tempSelectedCountries = List.from( - // currentState.filter.eventCountries ?? [], - // ); // Removed + // Initialize source capsule states from the BLoC's current filter + _tempSelectedSourceCountryIsoCodes = Set.from( + currentState.filter.selectedSourceCountryIsoCodes ?? {}, + ); + _tempSelectedSourceSourceTypes = Set.from( + currentState.filter.selectedSourceSourceTypes ?? {}, + ); } else { - // Default to empty lists if the feed isn't loaded yet. This might happen - // if the filter page is accessed before the initial feed load completes. _tempSelectedCategories = []; _tempSelectedSources = []; - // _tempSelectedCountries = []; // Removed + _tempSelectedSourceCountryIsoCodes = {}; + _tempSelectedSourceSourceTypes = {}; } } @@ -76,8 +85,9 @@ class _HeadlinesFilterPageState extends State { required String title, required int selectedCount, required String routeName, - required List currentSelection, // Pass current temp selection - required void Function(List?) onResult, + // For sources, currentSelection will be a Map + required dynamic currentSelectionData, + required void Function(dynamic)? onResult, // Result can also be a Map }) { final l10n = context.l10n; final allLabel = l10n.headlinesFeedFilterAllLabel; @@ -92,21 +102,12 @@ class _HeadlinesFilterPageState extends State { subtitle: Text(subtitle), trailing: const Icon(Icons.chevron_right), onTap: () async { - // Navigate to the specific filter selection page (e.g., SourceFilterPage). - // Use pushNamed to wait for a result when the page is popped. - final result = await context.pushNamed>( - routeName, // The route name for the specific filter page. - // Pass the current temporary selection for this filter type - // (e.g., _tempSelectedSources) to the next page. This allows - // the next page to initialize its UI reflecting the current state. - extra: currentSelection, + final result = await context.pushNamed( + routeName, + extra: currentSelectionData, // Pass the map or list ); - // When the filter selection page pops (usually via its 'Apply' button), - // it returns the potentially modified list of selected items. - // If the result is not null (meaning the user didn't just cancel/go back), - // update the corresponding temporary state list on *this* page. - if (result != null) { - onResult(result); // Calls setState to update the UI here. + if (result != null && onResult != null) { + onResult(result); } }, ); @@ -150,11 +151,19 @@ class _HeadlinesFilterPageState extends State { categories: _tempSelectedCategories.isNotEmpty ? _tempSelectedCategories - : null, // Use null if empty + : null, sources: _tempSelectedSources.isNotEmpty ? _tempSelectedSources - : null, // Use null if empty + : null, + selectedSourceCountryIsoCodes: + _tempSelectedSourceCountryIsoCodes.isNotEmpty + ? _tempSelectedSourceCountryIsoCodes + : null, + selectedSourceSourceTypes: + _tempSelectedSourceSourceTypes.isNotEmpty + ? _tempSelectedSourceSourceTypes + : null, ); // Add an event to the main HeadlinesFeedBloc to apply the @@ -175,7 +184,7 @@ class _HeadlinesFilterPageState extends State { title: l10n.headlinesFeedFilterCategoryLabel, selectedCount: _tempSelectedCategories.length, routeName: Routes.feedFilterCategoriesName, - currentSelection: _tempSelectedCategories, + currentSelectionData: _tempSelectedCategories, onResult: (result) { if (result is List) { setState(() => _tempSelectedCategories = result); @@ -187,10 +196,21 @@ class _HeadlinesFilterPageState extends State { title: l10n.headlinesFeedFilterSourceLabel, selectedCount: _tempSelectedSources.length, routeName: Routes.feedFilterSourcesName, - currentSelection: _tempSelectedSources, + currentSelectionData: { + keySelectedSources: _tempSelectedSources, + keySelectedCountryIsoCodes: _tempSelectedSourceCountryIsoCodes, + keySelectedSourceTypes: _tempSelectedSourceSourceTypes, + }, onResult: (result) { - if (result is List) { - setState(() => _tempSelectedSources = result); + if (result is Map) { + setState(() { + _tempSelectedSources = + result[keySelectedSources] as List? ?? []; + _tempSelectedSourceCountryIsoCodes = + result[keySelectedCountryIsoCodes] as Set? ?? {}; + _tempSelectedSourceSourceTypes = + result[keySelectedSourceTypes] as Set? ?? {}; + }); } }, ), diff --git a/lib/headlines-feed/view/source_filter_page.dart b/lib/headlines-feed/view/source_filter_page.dart index 170e3b30..7a652f4b 100644 --- a/lib/headlines-feed/view/source_filter_page.dart +++ b/lib/headlines-feed/view/source_filter_page.dart @@ -4,16 +4,30 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:ht_data_repository/ht_data_repository.dart'; import 'package:ht_main/headlines-feed/bloc/sources_filter_bloc.dart'; +import 'package:ht_main/headlines-feed/view/headlines_filter_page.dart' + show keySelectedCountryIsoCodes, keySelectedSourceTypes, keySelectedSources; import 'package:ht_main/l10n/l10n.dart'; import 'package:ht_main/shared/constants/app_spacing.dart'; import 'package:ht_main/shared/widgets/failure_state_widget.dart'; import 'package:ht_main/shared/widgets/loading_state_widget.dart'; -import 'package:ht_shared/ht_shared.dart' show Country, Source; +import 'package:ht_shared/ht_shared.dart' show Country, Source, SourceType; + +// Keys are defined in headlines_filter_page.dart and imported by router.dart +// const String keySelectedSources = 'selectedSources'; // REMOVED +// const String keySelectedCountryIsoCodes = 'selectedCountryIsoCodes'; // REMOVED +// const String keySelectedSourceTypes = 'selectedSourceTypes'; // REMOVED class SourceFilterPage extends StatelessWidget { - const SourceFilterPage({super.key, this.initialSelectedSources = const []}); + const SourceFilterPage({ + super.key, + this.initialSelectedSources = const [], + this.initialSelectedCountryIsoCodes = const {}, + this.initialSelectedSourceTypes = const {}, + }); final List initialSelectedSources; + final Set initialSelectedCountryIsoCodes; + final Set initialSelectedSourceTypes; @override Widget build(BuildContext context) { @@ -25,6 +39,8 @@ class SourceFilterPage extends StatelessWidget { )..add( LoadSourceFilterData( initialSelectedSources: initialSelectedSources, + initialSelectedCountryIsoCodes: initialSelectedCountryIsoCodes, + initialSelectedSourceTypes: initialSelectedSourceTypes, ), ), child: const _SourceFilterView(), @@ -63,7 +79,12 @@ class _SourceFilterView extends StatelessWidget { (s) => state.finallySelectedSourceIds.contains(s.id), ) .toList(); - Navigator.of(context).pop(selectedSources); + // Pop with a map containing all relevant filter state + Navigator.of(context).pop({ + keySelectedSources: selectedSources, + keySelectedCountryIsoCodes: state.selectedCountryIsoCodes, + keySelectedSourceTypes: state.selectedSourceTypes, + }); }, ), ], @@ -91,25 +112,29 @@ class _SourceFilterView extends StatelessWidget { message: state.errorMessage ?? l10n.headlinesFeedFilterErrorCriteria, onRetry: () { context.read().add( - LoadSourceFilterData( - initialSelectedSources: - ModalRoute.of(context)?.settings.arguments as List? ?? - const [], - ), + // When retrying, we don't have initial capsule states from arguments + // So, we pass empty sets, BLoC will load all sources and countries. + // User can then re-apply capsule filters if needed. + // Or, we could try to persist/retrieve the last known good capsule state. + // For now, simple retry reloads all. + const LoadSourceFilterData(), ); }, ); } - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _buildCountryCapsules(context, state, l10n), - const SizedBox(height: AppSpacing.lg), - _buildSourceTypeCapsules(context, state, l10n), - const SizedBox(height: AppSpacing.lg), - Expanded(child: _buildSourcesList(context, state, l10n)), - ], + return Padding( + padding: const EdgeInsets.only(top: AppSpacing.md), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildCountryCapsules(context, state, l10n), + const SizedBox(height: AppSpacing.lg), + _buildSourceTypeCapsules(context, state, l10n), + const SizedBox(height: AppSpacing.lg), + Expanded(child: _buildSourcesList(context, state, l10n)), + ], + ), ); } diff --git a/lib/router/router.dart b/lib/router/router.dart index 4c85defe..92473519 100644 --- a/lib/router/router.dart +++ b/lib/router/router.dart @@ -400,7 +400,34 @@ GoRouter createRouter({ context .read>(), ), - child: const SourceFilterPage(), + // Pass initialSelectedSources, country ISO codes, and source types from state.extra + child: Builder( + builder: (context) { + final extraData = + state.extra as Map? ?? + const {}; + final initialSources = + extraData[keySelectedSources] + as List? ?? + const []; + final initialCountryIsoCodes = + extraData[keySelectedCountryIsoCodes] + as Set? ?? + const {}; + final initialSourceTypes = + extraData[keySelectedSourceTypes] + as Set? ?? + const {}; + + return SourceFilterPage( + initialSelectedSources: initialSources, + initialSelectedCountryIsoCodes: + initialCountryIsoCodes, + initialSelectedSourceTypes: + initialSourceTypes, + ); + }, + ), ), ), // Sub-route for country selection REMOVED