Skip to content

Commit 7dc5dce

Browse files
committed
refactor(headlines-feed): migrate CountryFilterPage to HeadlinesFilterBloc
- Replace CountriesFilterBloc with HeadlinesFilterBloc for centralized state management - Convert CountryFilterPage from StatefulWidget to StatelessWidget - Remove local selection state and rely on HeadlinesFilterBloc for country selections - Update UI to reflect changes in state management logic - Simplify page navigation and data handling by integrating with HeadlinesFilterBloc
1 parent b381539 commit 7dc5dce

File tree

1 file changed

+53
-98
lines changed

1 file changed

+53
-98
lines changed

lib/headlines-feed/view/country_filter_page.dart

Lines changed: 53 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -2,80 +2,23 @@ import 'package:core/core.dart';
22
import 'package:flutter/material.dart';
33
import 'package:flutter_bloc/flutter_bloc.dart';
44
import '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';
66
import 'package:flutter_news_app_mobile_client_full_source_code/l10n/l10n.dart';
7-
import 'package:go_router/go_router.dart';
87
import '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

Comments
 (0)