Skip to content

Commit c2a8406

Browse files
committed
refactor(headlines-feed): update topic filter page logic
- Replace TopicsFilterBloc with AppBloc for followed topics data - Simplify logic for applying followed topics filter - Remove unnecessary loading state and error handling for followed topics - Optimize UI updates and snackbar messages
1 parent 7b94785 commit c2a8406

File tree

1 file changed

+83
-142
lines changed

1 file changed

+83
-142
lines changed

lib/headlines-feed/view/topic_filter_page.dart

Lines changed: 83 additions & 142 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import 'package:core/core.dart';
22
import 'package:flutter/material.dart';
3+
import 'package:flutter/material.dart';
34
import 'package:flutter_bloc/flutter_bloc.dart';
5+
import 'package:flutter_news_app_mobile_client_full_source_code/app/bloc/app_bloc.dart';
46
import 'package:flutter_news_app_mobile_client_full_source_code/headlines-feed/bloc/topics_filter_bloc.dart';
57
import 'package:flutter_news_app_mobile_client_full_source_code/l10n/l10n.dart';
68
import 'package:go_router/go_router.dart';
@@ -68,16 +70,13 @@ class _TopicFilterPageState extends State<TopicFilterPage> {
6870
),
6971
actions: [
7072
// Apply My Followed Topics Button
71-
BlocBuilder<TopicsFilterBloc, TopicsFilterState>(
72-
builder: (context, state) {
73-
// Determine if the "Apply My Followed" icon should be filled
74-
// This logic checks if all currently selected topics are
75-
// also present in the fetched followed topics list.
76-
final followedTopicsSet = state.followedTopics.toSet();
77-
final isFollowedFilterActive =
78-
followedTopicsSet.isNotEmpty &&
79-
_pageSelectedTopics.length == followedTopicsSet.length &&
80-
_pageSelectedTopics.containsAll(followedTopicsSet);
73+
BlocBuilder<AppBloc, AppState>(
74+
builder: (context, appState) {
75+
final followedTopics =
76+
appState.userContentPreferences?.followedTopics ?? [];
77+
final isFollowedFilterActive = followedTopics.isNotEmpty &&
78+
_pageSelectedTopics.length == followedTopics.length &&
79+
_pageSelectedTopics.containsAll(followedTopics);
8180

8281
return IconButton(
8382
icon: isFollowedFilterActive
@@ -87,15 +86,21 @@ class _TopicFilterPageState extends State<TopicFilterPage> {
8786
? theme.colorScheme.primary
8887
: null,
8988
tooltip: l10n.headlinesFeedFilterApplyFollowedLabel,
90-
onPressed:
91-
state.followedTopicsStatus == TopicsFilterStatus.loading
92-
? null // Disable while loading
93-
: () {
94-
// Dispatch event to BLoC to fetch and apply followed topics
95-
_topicsFilterBloc.add(
96-
TopicsFilterApplyFollowedRequested(),
97-
);
98-
},
89+
onPressed: () {
90+
setState(() {
91+
_pageSelectedTopics = Set.from(followedTopics);
92+
});
93+
if (followedTopics.isEmpty) {
94+
ScaffoldMessenger.of(context)
95+
..hideCurrentSnackBar()
96+
..showSnackBar(
97+
SnackBar(
98+
content: Text(l10n.noFollowedItemsForFilterSnackbar),
99+
duration: const Duration(seconds: 3),
100+
),
101+
);
102+
}
103+
},
99104
);
100105
},
101106
),
@@ -109,134 +114,70 @@ class _TopicFilterPageState extends State<TopicFilterPage> {
109114
),
110115
],
111116
),
112-
body: BlocListener<TopicsFilterBloc, TopicsFilterState>(
113-
// Listen for changes in followedTopicsStatus or followedTopics
114-
listenWhen: (previous, current) =>
115-
previous.followedTopicsStatus != current.followedTopicsStatus ||
116-
previous.followedTopics != current.followedTopics,
117-
listener: (context, state) {
118-
if (state.followedTopicsStatus == TopicsFilterStatus.success) {
119-
// Update local state with followed topics from BLoC
120-
setState(() {
121-
_pageSelectedTopics = Set.from(state.followedTopics);
122-
});
123-
if (state.followedTopics.isEmpty) {
124-
ScaffoldMessenger.of(context)
125-
..hideCurrentSnackBar()
126-
..showSnackBar(
127-
SnackBar(
128-
content: Text(l10n.noFollowedItemsForFilterSnackbar),
129-
duration: const Duration(seconds: 3),
130-
),
131-
);
132-
}
133-
} else if (state.followedTopicsStatus == TopicsFilterStatus.failure) {
134-
// Show error message if fetching followed topics failed
135-
ScaffoldMessenger.of(context)
136-
..hideCurrentSnackBar()
137-
..showSnackBar(
138-
SnackBar(
139-
content: Text(state.error?.message ?? l10n.unknownError),
140-
duration: const Duration(seconds: 3),
141-
),
142-
);
117+
body: BlocBuilder<TopicsFilterBloc, TopicsFilterState>(
118+
builder: (context, state) {
119+
// Determine overall loading status for the main list
120+
final isLoadingMainList =
121+
state.status == TopicsFilterStatus.initial ||
122+
state.status == TopicsFilterStatus.loading;
123+
124+
if (isLoadingMainList) {
125+
return LoadingStateWidget(
126+
icon: Icons.category_outlined,
127+
headline: l10n.topicFilterLoadingHeadline,
128+
subheadline: l10n.pleaseWait,
129+
);
143130
}
144-
},
145-
child: BlocBuilder<TopicsFilterBloc, TopicsFilterState>(
146-
builder: (context, state) {
147-
// Determine overall loading status for the main list
148-
final isLoadingMainList =
149-
state.status == TopicsFilterStatus.initial ||
150-
state.status == TopicsFilterStatus.loading;
151-
152-
// Determine if followed topics are currently loading
153-
final isLoadingFollowedTopics =
154-
state.followedTopicsStatus == TopicsFilterStatus.loading;
155131

156-
if (isLoadingMainList) {
157-
return LoadingStateWidget(
158-
icon: Icons.category_outlined,
159-
headline: l10n.topicFilterLoadingHeadline,
160-
subheadline: l10n.pleaseWait,
161-
);
162-
}
163-
164-
if (state.status == TopicsFilterStatus.failure &&
165-
state.topics.isEmpty) {
166-
return Center(
167-
child: FailureStateWidget(
168-
exception:
169-
state.error ??
170-
const UnknownException(
171-
'An unknown error occurred while fetching topics.',
172-
),
173-
onRetry: () => _topicsFilterBloc.add(TopicsFilterRequested()),
174-
),
175-
);
176-
}
132+
if (state.status == TopicsFilterStatus.failure &&
133+
state.topics.isEmpty) {
134+
return Center(
135+
child: FailureStateWidget(
136+
exception:
137+
state.error ??
138+
const UnknownException(
139+
'An unknown error occurred while fetching topics.',
140+
),
141+
onRetry: () => _topicsFilterBloc.add(TopicsFilterRequested()),
142+
),
143+
);
144+
}
177145

178-
if (state.topics.isEmpty) {
179-
return InitialStateWidget(
180-
icon: Icons.category_outlined,
181-
headline: l10n.topicFilterEmptyHeadline,
182-
subheadline: l10n.topicFilterEmptySubheadline,
183-
);
184-
}
146+
if (state.topics.isEmpty) {
147+
return InitialStateWidget(
148+
icon: Icons.category_outlined,
149+
headline: l10n.topicFilterEmptyHeadline,
150+
subheadline: l10n.topicFilterEmptySubheadline,
151+
);
152+
}
185153

186-
return Stack(
187-
children: [
188-
ListView.builder(
189-
controller: _scrollController,
190-
itemCount: state.hasMore
191-
? state.topics.length + 1
192-
: state.topics.length,
193-
itemBuilder: (context, index) {
194-
if (index >= state.topics.length) {
195-
return const Center(child: CircularProgressIndicator());
154+
return ListView.builder(
155+
controller: _scrollController,
156+
itemCount: state.hasMore
157+
? state.topics.length + 1
158+
: state.topics.length,
159+
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);
165+
return CheckboxListTile(
166+
title: Text(topic.name),
167+
value: isSelected,
168+
onChanged: (bool? value) {
169+
setState(() {
170+
if (value == true) {
171+
_pageSelectedTopics.add(topic);
172+
} else {
173+
_pageSelectedTopics.remove(topic);
196174
}
197-
final topic = state.topics[index];
198-
final isSelected = _pageSelectedTopics.contains(topic);
199-
return CheckboxListTile(
200-
title: Text(topic.name),
201-
value: isSelected,
202-
onChanged: (bool? value) {
203-
setState(() {
204-
if (value == true) {
205-
_pageSelectedTopics.add(topic);
206-
} else {
207-
_pageSelectedTopics.remove(topic);
208-
}
209-
});
210-
},
211-
);
212-
},
213-
),
214-
// Show loading overlay if followed topics are being fetched
215-
if (isLoadingFollowedTopics)
216-
Positioned.fill(
217-
child: ColoredBox(
218-
color: Colors.black54, // Semi-transparent overlay
219-
child: Center(
220-
child: Column(
221-
mainAxisAlignment: MainAxisAlignment.center,
222-
children: [
223-
const CircularProgressIndicator(),
224-
const SizedBox(height: AppSpacing.md),
225-
Text(
226-
l10n.headlinesFeedLoadingHeadline,
227-
style: theme.textTheme.titleMedium?.copyWith(
228-
color: Colors.white,
229-
),
230-
),
231-
],
232-
),
233-
),
234-
),
235-
),
236-
],
237-
);
238-
},
239-
),
175+
});
176+
},
177+
);
178+
},
179+
);
180+
},
240181
),
241182
);
242183
}

0 commit comments

Comments
 (0)