Skip to content

Commit 78ae116

Browse files
committed
refactor(headlines-feed): improve source filter page logic and UI
- Remove unnecessary listener and state checks - Optimize followed sources logic and UI feedback - Simplify loading and error handling - Adjust UI layout and remove overlay for followed sources loading
1 parent 03b539f commit 78ae116

File tree

1 file changed

+86
-141
lines changed

1 file changed

+86
-141
lines changed

lib/headlines-feed/view/source_filter_page.dart

Lines changed: 86 additions & 141 deletions
Original file line numberDiff line numberDiff line change
@@ -30,18 +30,15 @@ class SourceFilterPage extends StatelessWidget {
3030
@override
3131
Widget build(BuildContext context) {
3232
return BlocProvider(
33-
create: (context) =>
34-
SourcesFilterBloc(
35-
sourcesRepository: context.read<DataRepository<Source>>(),
36-
countriesRepository: context.read<DataRepository<Country>>(),
37-
userContentPreferencesRepository: context
38-
.read<DataRepository<UserContentPreferences>>(),
39-
appBloc: context.read<AppBloc>(),
40-
)..add(
41-
LoadSourceFilterData(
42-
initialSelectedSources: initialSelectedSources,
43-
),
33+
create: (context) => SourcesFilterBloc(
34+
sourcesRepository: context.read<DataRepository<Source>>(),
35+
countriesRepository: context.read<DataRepository<Country>>(),
36+
appBloc: context.read<AppBloc>(),
37+
)..add(
38+
LoadSourceFilterData(
39+
initialSelectedSources: initialSelectedSources,
4440
),
41+
),
4542
child: const _SourceFilterView(),
4643
);
4744
}
@@ -65,16 +62,18 @@ class _SourceFilterView extends StatelessWidget {
6562
),
6663
actions: [
6764
// Apply My Followed Sources Button
68-
BlocBuilder<SourcesFilterBloc, SourcesFilterState>(
69-
builder: (context, state) {
70-
// Determine if the "Apply My Followed" icon should be filled
71-
final isFollowedFilterActive =
72-
state.followedSources.isNotEmpty &&
73-
state.finallySelectedSourceIds.length ==
74-
state.followedSources.length &&
75-
state.followedSources.every(
76-
(source) =>
77-
state.finallySelectedSourceIds.contains(source.id),
65+
BlocBuilder<AppBloc, AppState>(
66+
builder: (context, appState) {
67+
final followedSources =
68+
appState.userContentPreferences?.followedSources ?? [];
69+
final sourcesFilterBloc = context.read<SourcesFilterBloc>();
70+
71+
final isFollowedFilterActive = followedSources.isNotEmpty &&
72+
sourcesFilterBloc.state.finallySelectedSourceIds.length ==
73+
followedSources.length &&
74+
followedSources.every(
75+
(source) => sourcesFilterBloc.state.finallySelectedSourceIds
76+
.contains(source.id),
7877
);
7978

8079
return IconButton(
@@ -85,16 +84,23 @@ class _SourceFilterView extends StatelessWidget {
8584
? theme.colorScheme.primary
8685
: null,
8786
tooltip: l10n.headlinesFeedFilterApplyFollowedLabel,
88-
onPressed:
89-
state.followedSourcesStatus ==
90-
SourceFilterDataLoadingStatus.loading
91-
? null // Disable while loading
92-
: () {
93-
// Dispatch event to BLoC to fetch and apply followed sources
94-
context.read<SourcesFilterBloc>().add(
95-
SourcesFilterApplyFollowedRequested(),
96-
);
97-
},
87+
onPressed: () {
88+
sourcesFilterBloc.add(
89+
SourceFilterFollowedApplied(
90+
followedSources: followedSources,
91+
),
92+
);
93+
if (followedSources.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+
},
98104
);
99105
},
100106
),
@@ -112,121 +118,60 @@ class _SourceFilterView extends StatelessWidget {
112118
),
113119
],
114120
),
115-
body: BlocListener<SourcesFilterBloc, SourcesFilterState>(
116-
listenWhen: (previous, current) =>
117-
previous.followedSourcesStatus != current.followedSourcesStatus ||
118-
previous.followedSources != current.followedSources,
119-
listener: (context, state) {
120-
if (state.followedSourcesStatus ==
121-
SourceFilterDataLoadingStatus.success) {
122-
if (state.followedSources.isEmpty) {
123-
ScaffoldMessenger.of(context)
124-
..hideCurrentSnackBar()
125-
..showSnackBar(
126-
SnackBar(
127-
content: Text(l10n.noFollowedItemsForFilterSnackbar),
128-
duration: const Duration(seconds: 3),
121+
body: BlocBuilder<SourcesFilterBloc, SourcesFilterState>(
122+
builder: (context, state) {
123+
final isLoadingMainList =
124+
state.dataLoadingStatus ==
125+
SourceFilterDataLoadingStatus.loading &&
126+
state.allAvailableSources.isEmpty;
127+
128+
if (isLoadingMainList) {
129+
return LoadingStateWidget(
130+
icon: Icons.source_outlined,
131+
headline: l10n.sourceFilterLoadingHeadline,
132+
subheadline: l10n.sourceFilterLoadingSubheadline,
133+
);
134+
}
135+
if (state.dataLoadingStatus ==
136+
SourceFilterDataLoadingStatus.failure &&
137+
state.allAvailableSources.isEmpty) {
138+
return FailureStateWidget(
139+
exception:
140+
state.error ??
141+
const UnknownException(
142+
'Failed to load source filter data.',
129143
),
130-
);
131-
}
132-
} else if (state.followedSourcesStatus ==
133-
SourceFilterDataLoadingStatus.failure) {
134-
ScaffoldMessenger.of(context)
135-
..hideCurrentSnackBar()
136-
..showSnackBar(
137-
SnackBar(
138-
content: Text(state.error?.message ?? l10n.unknownError),
139-
duration: const Duration(seconds: 3),
140-
),
141-
);
144+
onRetry: () {
145+
context.read<SourcesFilterBloc>().add(const LoadSourceFilterData());
146+
},
147+
);
142148
}
143-
},
144-
child: BlocBuilder<SourcesFilterBloc, SourcesFilterState>(
145-
builder: (context, state) {
146-
final isLoadingMainList =
147-
state.dataLoadingStatus ==
148-
SourceFilterDataLoadingStatus.loading &&
149-
state.allAvailableSources.isEmpty;
150-
final isLoadingFollowedSources =
151-
state.followedSourcesStatus ==
152-
SourceFilterDataLoadingStatus.loading;
153149

154-
if (isLoadingMainList) {
155-
return LoadingStateWidget(
156-
icon: Icons.source_outlined,
157-
headline: l10n.sourceFilterLoadingHeadline,
158-
subheadline: l10n.sourceFilterLoadingSubheadline,
159-
);
160-
}
161-
if (state.dataLoadingStatus ==
162-
SourceFilterDataLoadingStatus.failure &&
163-
state.allAvailableSources.isEmpty) {
164-
return FailureStateWidget(
165-
exception:
166-
state.error ??
167-
const UnknownException(
168-
'Failed to load source filter data.',
169-
),
170-
onRetry: () {
171-
context.read<SourcesFilterBloc>().add(
172-
const LoadSourceFilterData(),
173-
);
174-
},
175-
);
176-
}
177-
178-
return Stack(
179-
children: [
180-
Column(
181-
crossAxisAlignment: CrossAxisAlignment.start,
182-
children: [
183-
_buildCountryCapsules(context, state, l10n, textTheme),
184-
const SizedBox(height: AppSpacing.md),
185-
_buildSourceTypeCapsules(context, state, l10n, textTheme),
186-
const SizedBox(height: AppSpacing.md),
187-
Padding(
188-
padding: const EdgeInsets.symmetric(
189-
horizontal: AppSpacing.paddingMedium,
190-
),
191-
child: Text(
192-
l10n.headlinesFeedFilterSourceLabel,
193-
style: textTheme.titleMedium?.copyWith(
194-
fontWeight: FontWeight.bold,
195-
),
196-
),
197-
),
198-
const SizedBox(height: AppSpacing.sm),
199-
Expanded(
200-
child: _buildSourcesList(context, state, l10n, textTheme),
201-
),
202-
],
150+
return Column(
151+
crossAxisAlignment: CrossAxisAlignment.start,
152+
children: [
153+
_buildCountryCapsules(context, state, l10n, textTheme),
154+
const SizedBox(height: AppSpacing.md),
155+
_buildSourceTypeCapsules(context, state, l10n, textTheme),
156+
const SizedBox(height: AppSpacing.md),
157+
Padding(
158+
padding: const EdgeInsets.symmetric(
159+
horizontal: AppSpacing.paddingMedium,
203160
),
204-
// Show loading overlay if followed sources are being fetched
205-
if (isLoadingFollowedSources)
206-
Positioned.fill(
207-
child: ColoredBox(
208-
color: Colors.black54, // Semi-transparent overlay
209-
child: Center(
210-
child: Column(
211-
mainAxisAlignment: MainAxisAlignment.center,
212-
children: [
213-
const CircularProgressIndicator(),
214-
const SizedBox(height: AppSpacing.md),
215-
Text(
216-
l10n.headlinesFeedLoadingHeadline,
217-
style: theme.textTheme.titleMedium?.copyWith(
218-
color: Colors.white,
219-
),
220-
),
221-
],
222-
),
223-
),
224-
),
161+
child: Text(
162+
l10n.headlinesFeedFilterSourceLabel,
163+
style: textTheme.titleMedium?.copyWith(
164+
fontWeight: FontWeight.bold,
225165
),
226-
],
227-
);
228-
},
229-
),
166+
),
167+
),
168+
const SizedBox(height: AppSpacing.sm),
169+
Expanded(
170+
child: _buildSourcesList(context, state, l10n, textTheme),
171+
),
172+
],
173+
);
174+
},
230175
),
231176
);
232177
}

0 commit comments

Comments
 (0)