@@ -42,11 +42,8 @@ class _HeadlinesFilterPageState extends State<HeadlinesFilterPage> {
4242 late List <Source > _tempSelectedSources;
4343 late List <Country > _tempSelectedEventCountries;
4444
45- // New state variables for the "Apply my followed items" feature
45+ /// Flag to indicate if the "Apply my followed items" filter is active.
4646 bool _useFollowedFilters = false ;
47- bool _isLoadingFollowedFilters = false ;
48- String ? _loadFollowedFiltersError;
49- UserContentPreferences ? _currentUserPreferences;
5047
5148 @override
5249 void initState () {
@@ -61,125 +58,6 @@ class _HeadlinesFilterPageState extends State<HeadlinesFilterPage> {
6158 _tempSelectedEventCountries = List .from (currentFilter.eventCountries ?? []);
6259
6360 _useFollowedFilters = currentFilter.isFromFollowedItems;
64- _isLoadingFollowedFilters = false ;
65- _loadFollowedFiltersError = null ;
66- _currentUserPreferences = null ;
67-
68- // If the "Apply my followed items" feature is initially active,
69- // fetch the followed items to populate the temporary filter lists.
70- if (_useFollowedFilters) {
71- WidgetsBinding .instance.addPostFrameCallback ((_) {
72- if (mounted) {
73- _fetchAndApplyFollowedFilters ();
74- }
75- });
76- }
77- }
78-
79- /// Fetches the user's followed items (topics, sources, countries) and
80- /// applies them to the temporary filter state.
81- ///
82- /// This method is called when the "Apply my followed items" toggle is
83- /// activated. It handles loading states, errors, and updates the UI.
84- Future <void > _fetchAndApplyFollowedFilters () async {
85- setState (() {
86- _isLoadingFollowedFilters = true ;
87- _loadFollowedFiltersError = null ;
88- });
89-
90- final appState = context.read <AppBloc >().state;
91- final currentUser = appState.user! ;
92-
93- try {
94- final preferencesRepo = context
95- .read <DataRepository <UserContentPreferences >>();
96- final preferences = await preferencesRepo.read (
97- id: currentUser.id,
98- userId: currentUser.id,
99- );
100-
101- // Check if followed items are empty across all categories
102- if (preferences.followedTopics.isEmpty &&
103- preferences.followedSources.isEmpty &&
104- preferences.followedCountries.isEmpty) {
105- setState (() {
106- _isLoadingFollowedFilters = false ;
107- _useFollowedFilters = false ;
108- _tempSelectedTopics = [];
109- _tempSelectedSources = [];
110- _tempSelectedEventCountries = [];
111- });
112- if (mounted) {
113- ScaffoldMessenger .of (context)
114- ..hideCurrentSnackBar ()
115- ..showSnackBar (
116- SnackBar (
117- content: Text (
118- AppLocalizationsX (
119- context,
120- ).l10n.noFollowedItemsForFilterSnackbar,
121- ),
122- duration: const Duration (seconds: 3 ),
123- ),
124- );
125- }
126- return ;
127- } else {
128- setState (() {
129- _currentUserPreferences = preferences;
130- _tempSelectedTopics = List .from (preferences.followedTopics);
131- _tempSelectedSources = List .from (preferences.followedSources);
132- _tempSelectedEventCountries = List .from (
133- preferences.followedCountries,
134- );
135- _isLoadingFollowedFilters = false ;
136- });
137- }
138- } on NotFoundException {
139- // If user preferences are not found, treat as empty followed items.
140- setState (() {
141- _currentUserPreferences = UserContentPreferences (
142- id: currentUser.id,
143- followedTopics: const [],
144- followedSources: const [],
145- followedCountries: const [],
146- savedHeadlines: const [],
147- );
148- _tempSelectedTopics = [];
149- _tempSelectedSources = [];
150- _tempSelectedEventCountries = [];
151- _isLoadingFollowedFilters = false ;
152- _useFollowedFilters = false ;
153- });
154- if (mounted) {
155- ScaffoldMessenger .of (context)
156- ..hideCurrentSnackBar ()
157- ..showSnackBar (
158- SnackBar (
159- content: Text (
160- AppLocalizationsX (
161- context,
162- ).l10n.noFollowedItemsForFilterSnackbar,
163- ),
164- duration: const Duration (seconds: 3 ),
165- ),
166- );
167- }
168- } on HttpException catch (e) {
169- setState (() {
170- _isLoadingFollowedFilters = false ;
171- _useFollowedFilters = false ;
172- _loadFollowedFiltersError = e.message;
173- });
174- } catch (e) {
175- setState (() {
176- _isLoadingFollowedFilters = false ;
177- _useFollowedFilters = false ;
178- _loadFollowedFiltersError = AppLocalizationsX (
179- context,
180- ).l10n.unknownError;
181- });
182- }
18361 }
18462
18563 /// Clears all temporary filter selections.
@@ -243,10 +121,9 @@ class _HeadlinesFilterPageState extends State<HeadlinesFilterPage> {
243121 final l10n = AppLocalizationsX (context).l10n;
244122 final theme = Theme .of (context);
245123
246- // Determine if the "Apply my followed items" feature is active and loading .
124+ // Determine if the "Apply my followed items" feature is active.
247125 // This will disable the individual filter tiles.
248- final isFollowedFilterActiveOrLoading =
249- _useFollowedFilters || _isLoadingFollowedFilters;
126+ final isFollowedFilterActive = _useFollowedFilters;
250127
251128 return Scaffold (
252129 appBar: AppBar (
@@ -270,34 +147,59 @@ class _HeadlinesFilterPageState extends State<HeadlinesFilterPage> {
270147 // Also reset local state for the "Apply my followed items"
271148 setState (() {
272149 _useFollowedFilters = false ;
273- _isLoadingFollowedFilters = false ;
274- _loadFollowedFiltersError = null ;
275150 _clearTemporaryFilters ();
276151 });
277152 context.pop ();
278153 },
279154 ),
280155 // Apply My Followed Items Button
281- IconButton (
282- icon: _useFollowedFilters
283- ? const Icon (Icons .favorite)
284- : const Icon (Icons .favorite_border),
285- color: _useFollowedFilters ? theme.colorScheme.primary : null ,
286- tooltip: l10n.headlinesFeedFilterApplyFollowedLabel,
287- onPressed: _isLoadingFollowedFilters
288- ? null // Disable while loading
289- : () {
290- setState (() {
291- _useFollowedFilters = ! _useFollowedFilters;
292- if (_useFollowedFilters) {
293- _fetchAndApplyFollowedFilters ();
294- } else {
295- _isLoadingFollowedFilters = false ;
296- _loadFollowedFiltersError = null ;
297- _clearTemporaryFilters ();
156+ BlocBuilder <AppBloc , AppState >(
157+ builder: (context, appState) {
158+ final followedTopics =
159+ appState.userContentPreferences? .followedTopics ?? [];
160+ final followedSources =
161+ appState.userContentPreferences? .followedSources ?? [];
162+ final followedCountries =
163+ appState.userContentPreferences? .followedCountries ?? [];
164+
165+ final hasFollowedItems = followedTopics.isNotEmpty ||
166+ followedSources.isNotEmpty ||
167+ followedCountries.isNotEmpty;
168+
169+ return IconButton (
170+ icon: _useFollowedFilters
171+ ? const Icon (Icons .favorite)
172+ : const Icon (Icons .favorite_border),
173+ color: _useFollowedFilters ? theme.colorScheme.primary : null ,
174+ tooltip: l10n.headlinesFeedFilterApplyFollowedLabel,
175+ onPressed: hasFollowedItems
176+ ? () {
177+ setState (() {
178+ _useFollowedFilters = ! _useFollowedFilters;
179+ if (_useFollowedFilters) {
180+ _tempSelectedTopics = List .from (followedTopics);
181+ _tempSelectedSources = List .from (followedSources);
182+ _tempSelectedEventCountries =
183+ List .from (followedCountries);
184+ } else {
185+ _clearTemporaryFilters ();
186+ }
187+ });
298188 }
299- });
300- },
189+ : () {
190+ ScaffoldMessenger .of (context)
191+ ..hideCurrentSnackBar ()
192+ ..showSnackBar (
193+ SnackBar (
194+ content: Text (
195+ l10n.noFollowedItemsForFilterSnackbar,
196+ ),
197+ duration: const Duration (seconds: 3 ),
198+ ),
199+ );
200+ },
201+ );
202+ },
301203 ),
302204 // Apply Filters Button
303205 IconButton (
@@ -330,41 +232,11 @@ class _HeadlinesFilterPageState extends State<HeadlinesFilterPage> {
330232 body: ListView (
331233 padding: const EdgeInsets .symmetric (vertical: AppSpacing .md),
332234 children: [
333- if (_isLoadingFollowedFilters)
334- Padding (
335- padding: const EdgeInsets .symmetric (
336- horizontal: AppSpacing .paddingLarge,
337- vertical: AppSpacing .sm,
338- ),
339- child: Row (
340- mainAxisAlignment: MainAxisAlignment .center,
341- children: [
342- const SizedBox (
343- width: 24 ,
344- height: 24 ,
345- child: CircularProgressIndicator (strokeWidth: 2 ),
346- ),
347- const SizedBox (width: AppSpacing .md),
348- Text (l10n.headlinesFeedLoadingHeadline),
349- ],
350- ),
351- ),
352- if (_loadFollowedFiltersError != null )
353- Padding (
354- padding: const EdgeInsets .symmetric (
355- horizontal: AppSpacing .paddingLarge,
356- vertical: AppSpacing .sm,
357- ),
358- child: Text (
359- _loadFollowedFiltersError! ,
360- style: TextStyle (color: theme.colorScheme.error),
361- ),
362- ),
363235 const Divider (),
364236 _buildFilterTile <Topic >(
365237 context: context,
366238 title: l10n.headlinesFeedFilterTopicLabel,
367- enabled: ! isFollowedFilterActiveOrLoading ,
239+ enabled: ! isFollowedFilterActive ,
368240 selectedCount: _tempSelectedTopics.length,
369241 routeName: Routes .feedFilterTopicsName,
370242 currentSelectionData: _tempSelectedTopics,
@@ -375,7 +247,7 @@ class _HeadlinesFilterPageState extends State<HeadlinesFilterPage> {
375247 _buildFilterTile <Source >(
376248 context: context,
377249 title: l10n.headlinesFeedFilterSourceLabel,
378- enabled: ! isFollowedFilterActiveOrLoading ,
250+ enabled: ! isFollowedFilterActive ,
379251 selectedCount: _tempSelectedSources.length,
380252 routeName: Routes .feedFilterSourcesName,
381253 currentSelectionData: _tempSelectedSources,
@@ -386,7 +258,7 @@ class _HeadlinesFilterPageState extends State<HeadlinesFilterPage> {
386258 _buildFilterTile <Country >(
387259 context: context,
388260 title: l10n.headlinesFeedFilterEventCountryLabel,
389- enabled: ! isFollowedFilterActiveOrLoading ,
261+ enabled: ! isFollowedFilterActive ,
390262 selectedCount: _tempSelectedEventCountries.length,
391263 routeName: Routes .feedFilterEventCountriesName,
392264 currentSelectionData: _tempSelectedEventCountries,
0 commit comments