11import 'package:core/core.dart' ;
22import 'package:flutter/material.dart' ;
3- import 'package:flutter/material.dart' ;
43import 'package:flutter_bloc/flutter_bloc.dart' ;
54import 'package:flutter_news_app_mobile_client_full_source_code/app/bloc/app_bloc.dart' ;
6- import 'package:flutter_news_app_mobile_client_full_source_code/headlines-feed/bloc/topics_filter_bloc .dart' ;
5+ import 'package:flutter_news_app_mobile_client_full_source_code/headlines-feed/bloc/headlines_filter_bloc .dart' ;
76import 'package:flutter_news_app_mobile_client_full_source_code/l10n/l10n.dart' ;
8- import 'package:go_router/go_router.dart' ;
97import 'package:ui_kit/ui_kit.dart' ;
108
119/// {@template topic_filter_page}
1210/// A page dedicated to selecting news topics for filtering headlines.
11+ ///
12+ /// This page now interacts with the centralized [HeadlinesFilterBloc]
13+ /// to manage the list of available topics and the user's selections.
1314/// {@endtemplate}
14- class TopicFilterPage extends StatefulWidget {
15+ class TopicFilterPage extends StatelessWidget {
1516 /// {@macro topic_filter_page}
1617 const TopicFilterPage ({super .key});
1718
18- @override
19- State <TopicFilterPage > createState () => _TopicFilterPageState ();
20- }
21-
22- class _TopicFilterPageState extends State <TopicFilterPage > {
23- final _scrollController = ScrollController ();
24- late final TopicsFilterBloc _topicsFilterBloc;
25- late Set <Topic > _pageSelectedTopics;
26-
27- @override
28- void initState () {
29- super .initState ();
30- _scrollController.addListener (_onScroll);
31- _topicsFilterBloc = context.read <TopicsFilterBloc >()
32- ..add (TopicsFilterRequested ());
33-
34- WidgetsBinding .instance.addPostFrameCallback ((_) {
35- // Initialize local selection from GoRouter extra parameter
36- final initialSelection = GoRouterState .of (context).extra as List <Topic >? ;
37- _pageSelectedTopics = Set .from (initialSelection ?? []);
38- });
39- }
40-
41- @override
42- void dispose () {
43- _scrollController.dispose ();
44- super .dispose ();
45- }
46-
47- void _onScroll () {
48- if (_isBottom) {
49- _topicsFilterBloc.add (TopicsFilterLoadMoreRequested ());
50- }
51- }
52-
53- bool get _isBottom {
54- if (! _scrollController.hasClients) return false ;
55- final maxScroll = _scrollController.position.maxScrollExtent;
56- final currentScroll = _scrollController.offset;
57- return currentScroll >= (maxScroll * 0.9 );
58- }
59-
6019 @override
6120 Widget build (BuildContext context) {
6221 final l10n = AppLocalizationsX (context).l10n;
@@ -70,13 +29,16 @@ class _TopicFilterPageState extends State<TopicFilterPage> {
7029 ),
7130 actions: [
7231 // Apply My Followed Topics Button
73- BlocBuilder <AppBloc , AppState >(
74- builder: (context, appState) {
32+ BlocBuilder <HeadlinesFilterBloc , HeadlinesFilterState >(
33+ builder: (context, filterState) {
34+ final appState = context.watch <AppBloc >().state;
7535 final followedTopics =
7636 appState.userContentPreferences? .followedTopics ?? [];
37+
38+ // Determine if the current selection matches the followed topics
7739 final isFollowedFilterActive = followedTopics.isNotEmpty &&
78- _pageSelectedTopics .length == followedTopics.length &&
79- _pageSelectedTopics .containsAll (followedTopics);
40+ filterState.selectedTopics .length == followedTopics.length &&
41+ filterState.selectedTopics .containsAll (followedTopics);
8042
8143 return IconButton (
8244 icon: isFollowedFilterActive
@@ -87,9 +49,6 @@ class _TopicFilterPageState extends State<TopicFilterPage> {
8749 : null ,
8850 tooltip: l10n.headlinesFeedFilterApplyFollowedLabel,
8951 onPressed: () {
90- setState (() {
91- _pageSelectedTopics = Set .from (followedTopics);
92- });
9352 if (followedTopics.isEmpty) {
9453 ScaffoldMessenger .of (context)
9554 ..hideCurrentSnackBar ()
@@ -99,27 +58,35 @@ class _TopicFilterPageState extends State<TopicFilterPage> {
9958 duration: const Duration (seconds: 3 ),
10059 ),
10160 );
61+ } else {
62+ // Toggle the followed items filter in the HeadlinesFilterBloc
63+ context.read <HeadlinesFilterBloc >().add (
64+ FollowedItemsFilterToggled (
65+ isUsingFollowedItems: ! isFollowedFilterActive,
66+ ),
67+ );
10268 }
10369 },
10470 );
10571 },
10672 ),
107- // Apply Filters Button
73+ // Apply Filters Button (now just pops, as state is managed centrally)
10874 IconButton (
10975 icon: const Icon (Icons .check),
11076 tooltip: l10n.headlinesFeedFilterApplyButton,
11177 onPressed: () {
112- context.pop (_pageSelectedTopics.toList ());
78+ // The selections are already managed by HeadlinesFilterBloc.
79+ // Just pop the page.
80+ Navigator .of (context).pop ();
11381 },
11482 ),
11583 ],
11684 ),
117- body: BlocBuilder <TopicsFilterBloc , TopicsFilterState >(
118- builder: (context, state ) {
85+ body: BlocBuilder <HeadlinesFilterBloc , HeadlinesFilterState >(
86+ builder: (context, filterState ) {
11987 // Determine overall loading status for the main list
12088 final isLoadingMainList =
121- state.status == TopicsFilterStatus .initial ||
122- state.status == TopicsFilterStatus .loading;
89+ filterState.status == HeadlinesFilterStatus .loading;
12390
12491 if (isLoadingMainList) {
12592 return LoadingStateWidget (
@@ -129,21 +96,27 @@ class _TopicFilterPageState extends State<TopicFilterPage> {
12996 );
13097 }
13198
132- if (state .status == TopicsFilterStatus .failure &&
133- state.topics .isEmpty) {
99+ if (filterState .status == HeadlinesFilterStatus .failure &&
100+ filterState.allTopics .isEmpty) {
134101 return Center (
135102 child: FailureStateWidget (
136- exception:
137- state.error ??
103+ exception: filterState.error ??
138104 const UnknownException (
139105 'An unknown error occurred while fetching topics.' ,
140106 ),
141- onRetry: () => _topicsFilterBloc.add (TopicsFilterRequested ()),
107+ onRetry: () => context.read <HeadlinesFilterBloc >().add (
108+ FilterDataLoaded (
109+ initialSelectedTopics: filterState.selectedTopics.toList (),
110+ initialSelectedSources: filterState.selectedSources.toList (),
111+ initialSelectedCountries: filterState.selectedCountries.toList (),
112+ isUsingFollowedItems: filterState.isUsingFollowedItems,
113+ ),
114+ ),
142115 ),
143116 );
144117 }
145118
146- if (state.topics .isEmpty) {
119+ if (filterState.allTopics .isEmpty) {
147120 return InitialStateWidget (
148121 icon: Icons .category_outlined,
149122 headline: l10n.topicFilterEmptyHeadline,
@@ -152,27 +125,19 @@ class _TopicFilterPageState extends State<TopicFilterPage> {
152125 }
153126
154127 return ListView .builder (
155- controller: _scrollController,
156- itemCount: state.hasMore
157- ? state.topics.length + 1
158- : state.topics.length,
128+ itemCount: filterState.allTopics.length,
159129 itemBuilder: (context, index) {
160- if (index >= state.topics.length) {
161- return const Center (child: CircularProgressIndicator ());
162- }
163- final topic = state.topics[index];
164- final isSelected = _pageSelectedTopics.contains (topic);
130+ final topic = filterState.allTopics[index];
131+ final isSelected = filterState.selectedTopics.contains (topic);
165132 return CheckboxListTile (
166133 title: Text (topic.name),
167134 value: isSelected,
168135 onChanged: (bool ? value) {
169- setState (() {
170- if (value == true ) {
171- _pageSelectedTopics.add (topic);
172- } else {
173- _pageSelectedTopics.remove (topic);
174- }
175- });
136+ if (value != null ) {
137+ context.read <HeadlinesFilterBloc >().add (
138+ FilterTopicToggled (topic: topic, isSelected: value),
139+ );
140+ }
176141 },
177142 );
178143 },
0 commit comments