@@ -2,80 +2,23 @@ import 'package:core/core.dart';
22import 'package:flutter/material.dart' ;
33import 'package:flutter_bloc/flutter_bloc.dart' ;
44import 'package:flutter_news_app_mobile_client_full_source_code/app/bloc/app_bloc.dart' ;
5- import 'package:flutter_news_app_mobile_client_full_source_code/headlines-feed/bloc/countries_filter_bloc .dart' ;
5+ import 'package:flutter_news_app_mobile_client_full_source_code/headlines-feed/bloc/headlines_filter_bloc .dart' ;
66import 'package:flutter_news_app_mobile_client_full_source_code/l10n/l10n.dart' ;
7- import 'package:go_router/go_router.dart' ;
87import 'package:ui_kit/ui_kit.dart' ;
98
10- enum CountryFilterUsage {
11- // countries with active sources
12- hasActiveSources,
13-
14- // countries with active headlines
15- hasActiveHeadlines,
16- }
17-
189/// {@template country_filter_page}
1910/// A page dedicated to selecting event countries for filtering headlines.
2011///
21- /// Uses [CountriesFilterBloc] to fetch countries paginatively, allows multiple
22- /// selections, and returns the selected list via `context.pop` when the user
23- /// applies the changes.
12+ /// This page now interacts with the centralized [HeadlinesFilterBloc]
13+ /// to manage the list of available countries and the user's selections.
2414/// {@endtemplate}
25- class CountryFilterPage extends StatefulWidget {
15+ class CountryFilterPage extends StatelessWidget {
2616 /// {@macro country_filter_page}
27- const CountryFilterPage ({required this .title, this .filter, super .key});
17+ const CountryFilterPage ({required this .title, super .key});
2818
2919 /// The title to display in the app bar for this filter page.
3020 final String title;
3121
32- /// The usage context for filtering countries (e.g., 'hasActiveSources', 'hasActiveHeadlines').
33- /// If null, fetches all countries (though this is not the primary use case for this page).
34- final CountryFilterUsage ? filter;
35-
36- @override
37- State <CountryFilterPage > createState () => _CountryFilterPageState ();
38- }
39-
40- /// State for the [CountryFilterPage] .
41- ///
42- /// Manages the local selection state ([_pageSelectedCountries] ) and interacts
43- /// with [CountriesFilterBloc] for data fetching.
44- class _CountryFilterPageState extends State <CountryFilterPage > {
45- /// Stores the countries selected by the user *on this specific page*.
46- /// This state is local to the `CountryFilterPage` lifecycle.
47- /// It's initialized in `initState` using the list of previously selected
48- /// countries passed via the `extra` parameter during navigation from
49- /// `HeadlinesFilterPage` . This ensures the checkboxes reflect the state
50- /// from the main filter page when this page loads.
51- late Set <Country > _pageSelectedCountries;
52-
53- @override
54- void initState () {
55- super .initState ();
56-
57- // Initialization needs to happen after the first frame to safely access
58- // GoRouterState.of(context).
59- WidgetsBinding .instance.addPostFrameCallback ((_) {
60- // 1. Retrieve the list of countries that were already selected on the
61- // previous page (HeadlinesFilterPage). This list is passed dynamically
62- // via the `extra` parameter in the `context.pushNamed` call.
63- final initialSelection =
64- GoRouterState .of (context).extra as List <Country >? ;
65-
66- // 2. Initialize the local selection state (`_pageSelectedCountries`) for this
67- // page. Use a Set for efficient add/remove/contains operations.
68- // This ensures the checkboxes on this page are initially checked
69- // correctly based on the selections made previously.
70- _pageSelectedCountries = Set .from (initialSelection ?? []);
71- });
72- }
73-
74- @override
75- void dispose () {
76- super .dispose ();
77- }
78-
7922 @override
8023 Widget build (BuildContext context) {
8124 final l10n = AppLocalizationsX (context).l10n;
@@ -85,19 +28,22 @@ class _CountryFilterPageState extends State<CountryFilterPage> {
8528 return Scaffold (
8629 appBar: AppBar (
8730 title: Text (
88- widget. title, // Use the dynamic title
31+ title, // Use the dynamic title
8932 style: textTheme.titleLarge,
9033 ),
9134 actions: [
9235 // Apply My Followed Countries Button
93- BlocBuilder <AppBloc , AppState >(
94- builder: (context, appState) {
36+ BlocBuilder <HeadlinesFilterBloc , HeadlinesFilterState >(
37+ builder: (context, filterState) {
38+ final appState = context.watch <AppBloc >().state;
9539 final followedCountries =
9640 appState.userContentPreferences? .followedCountries ?? [];
97- final isFollowedFilterActive =
98- followedCountries.isNotEmpty &&
99- _pageSelectedCountries.length == followedCountries.length &&
100- _pageSelectedCountries.containsAll (followedCountries);
41+
42+ // Determine if the current selection matches the followed countries
43+ final isFollowedFilterActive = followedCountries.isNotEmpty &&
44+ filterState.selectedCountries.length ==
45+ followedCountries.length &&
46+ filterState.selectedCountries.containsAll (followedCountries);
10147
10248 return IconButton (
10349 icon: isFollowedFilterActive
@@ -108,9 +54,6 @@ class _CountryFilterPageState extends State<CountryFilterPage> {
10854 : null ,
10955 tooltip: l10n.headlinesFeedFilterApplyFollowedLabel,
11056 onPressed: () {
111- setState (() {
112- _pageSelectedCountries = Set .from (followedCountries);
113- });
11457 if (followedCountries.isEmpty) {
11558 ScaffoldMessenger .of (context)
11659 ..hideCurrentSnackBar ()
@@ -120,29 +63,35 @@ class _CountryFilterPageState extends State<CountryFilterPage> {
12063 duration: const Duration (seconds: 3 ),
12164 ),
12265 );
66+ } else {
67+ // Toggle the followed items filter in the HeadlinesFilterBloc
68+ context.read <HeadlinesFilterBloc >().add (
69+ FollowedItemsFilterToggled (
70+ isUsingFollowedItems: ! isFollowedFilterActive,
71+ ),
72+ );
12373 }
12474 },
12575 );
12676 },
12777 ),
78+ // Apply Filters Button (now just pops, as state is managed centrally)
12879 IconButton (
12980 icon: const Icon (Icons .check),
13081 tooltip: l10n.headlinesFeedFilterApplyButton,
13182 onPressed: () {
132- // When the user taps 'Apply' (checkmark), pop the current route
133- // and return the final list of selected countries (`_pageSelectedCountries`)
134- // from this page back to the previous page (`HeadlinesFilterPage`).
135- // `HeadlinesFilterPage` receives this list in its `onResult` callback.
136- context.pop (_pageSelectedCountries.toList ());
83+ // The selections are already managed by HeadlinesFilterBloc.
84+ // Just pop the page.
85+ Navigator .of (context).pop ();
13786 },
13887 ),
13988 ],
14089 ),
141- body: BlocBuilder <CountriesFilterBloc , CountriesFilterState >(
142- builder: (context, state ) {
90+ body: BlocBuilder <HeadlinesFilterBloc , HeadlinesFilterState >(
91+ builder: (context, filterState ) {
14392 // Determine overall loading status for the main list
14493 final isLoadingMainList =
145- state .status == CountriesFilterStatus .loading;
94+ filterState .status == HeadlinesFilterStatus .loading;
14695
14796 // Handle initial loading state
14897 if (isLoadingMainList) {
@@ -154,19 +103,24 @@ class _CountryFilterPageState extends State<CountryFilterPage> {
154103 }
155104
156105 // Handle failure state (show error and retry button)
157- if (state .status == CountriesFilterStatus .failure &&
158- state.countries .isEmpty) {
106+ if (filterState .status == HeadlinesFilterStatus .failure &&
107+ filterState.allCountries .isEmpty) {
159108 return FailureStateWidget (
160- exception: state.error ?? const UnknownException ('Unknown error' ),
161- onRetry: () => context.read <CountriesFilterBloc >().add (
162- CountriesFilterRequested (usage: widget.filter),
163- ),
109+ exception:
110+ filterState.error ?? const UnknownException ('Unknown error' ),
111+ onRetry: () => context.read <HeadlinesFilterBloc >().add (
112+ FilterDataLoaded (
113+ initialSelectedTopics: filterState.selectedTopics.toList (),
114+ initialSelectedSources: filterState.selectedSources.toList (),
115+ initialSelectedCountries: filterState.selectedCountries.toList (),
116+ isUsingFollowedItems: filterState.isUsingFollowedItems,
117+ ),
118+ ),
164119 );
165120 }
166121
167122 // Handle empty state (after successful load but no countries found)
168- if (state.status == CountriesFilterStatus .success &&
169- state.countries.isEmpty) {
123+ if (filterState.allCountries.isEmpty) {
170124 return InitialStateWidget (
171125 icon: Icons .flag_circle_outlined,
172126 headline: l10n.countryFilterEmptyHeadline,
@@ -179,10 +133,10 @@ class _CountryFilterPageState extends State<CountryFilterPage> {
179133 padding: const EdgeInsets .symmetric (
180134 vertical: AppSpacing .paddingSmall,
181135 ).copyWith (bottom: AppSpacing .xxl),
182- itemCount: state.countries .length,
136+ itemCount: filterState.allCountries .length,
183137 itemBuilder: (context, index) {
184- final country = state.countries [index];
185- final isSelected = _pageSelectedCountries .contains (country);
138+ final country = filterState.allCountries [index];
139+ final isSelected = filterState.selectedCountries .contains (country);
186140
187141 return CheckboxListTile (
188142 title: Text (country.name, style: textTheme.titleMedium),
@@ -217,13 +171,14 @@ class _CountryFilterPageState extends State<CountryFilterPage> {
217171 ),
218172 value: isSelected,
219173 onChanged: (bool ? value) {
220- setState (() {
221- if (value == true ) {
222- _pageSelectedCountries.add (country);
223- } else {
224- _pageSelectedCountries.remove (country);
225- }
226- });
174+ if (value != null ) {
175+ context.read <HeadlinesFilterBloc >().add (
176+ FilterCountryToggled (
177+ country: country,
178+ isSelected: value,
179+ ),
180+ );
181+ }
227182 },
228183 controlAffinity: ListTileControlAffinity .leading,
229184 contentPadding: const EdgeInsets .symmetric (
0 commit comments