Skip to content

Commit 03b539f

Browse files
committed
refactor(country-filter): update logic and remove BLoC listener
- Replace CountriesFilterBloc with AppBloc for followed countries data - Remove BlocListener and move logic to BlocBuilder - Update UI and snackbar behavior for empty followed countries list - Simplify loading and error state handling
1 parent 087bf6f commit 03b539f

File tree

1 file changed

+115
-179
lines changed

1 file changed

+115
-179
lines changed

lib/headlines-feed/view/country_filter_page.dart

Lines changed: 115 additions & 179 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import 'package:core/core.dart';
22
import 'package:flutter/material.dart';
33
import 'package:flutter_bloc/flutter_bloc.dart';
4+
import 'package:flutter_news_app_mobile_client_full_source_code/app/bloc/app_bloc.dart';
45
import 'package:flutter_news_app_mobile_client_full_source_code/headlines-feed/bloc/countries_filter_bloc.dart';
56
import 'package:flutter_news_app_mobile_client_full_source_code/l10n/l10n.dart';
67
import 'package:go_router/go_router.dart';
@@ -96,34 +97,35 @@ class _CountryFilterPageState extends State<CountryFilterPage> {
9697
),
9798
actions: [
9899
// Apply My Followed Countries Button
99-
BlocBuilder<CountriesFilterBloc, CountriesFilterState>(
100-
builder: (context, state) {
101-
// Determine if the "Apply My Followed" icon should be filled
102-
final followedCountriesSet = state.followedCountries.toSet();
103-
final isFollowedFilterActive =
104-
followedCountriesSet.isNotEmpty &&
105-
_pageSelectedCountries.length ==
106-
followedCountriesSet.length &&
107-
_pageSelectedCountries.containsAll(followedCountriesSet);
100+
BlocBuilder<AppBloc, AppState>(
101+
builder: (context, appState) {
102+
final followedCountries =
103+
appState.userContentPreferences?.followedCountries ?? [];
104+
final isFollowedFilterActive = followedCountries.isNotEmpty &&
105+
_pageSelectedCountries.length == followedCountries.length &&
106+
_pageSelectedCountries.containsAll(followedCountries);
108107

109108
return IconButton(
110109
icon: isFollowedFilterActive
111110
? const Icon(Icons.favorite)
112111
: const Icon(Icons.favorite_border),
113-
color: isFollowedFilterActive
114-
? theme.colorScheme.primary
115-
: null,
112+
color: isFollowedFilterActive ? theme.colorScheme.primary : null,
116113
tooltip: l10n.headlinesFeedFilterApplyFollowedLabel,
117-
onPressed:
118-
state.followedCountriesStatus ==
119-
CountriesFilterStatus.loading
120-
? null // Disable while loading
121-
: () {
122-
// Dispatch event to BLoC to fetch and apply followed countries
123-
_countriesFilterBloc.add(
124-
CountriesFilterApplyFollowedRequested(),
125-
);
126-
},
114+
onPressed: () {
115+
setState(() {
116+
_pageSelectedCountries = Set.from(followedCountries);
117+
});
118+
if (followedCountries.isEmpty) {
119+
ScaffoldMessenger.of(context)
120+
..hideCurrentSnackBar()
121+
..showSnackBar(
122+
SnackBar(
123+
content: Text(l10n.noFollowedItemsForFilterSnackbar),
124+
duration: const Duration(seconds: 3),
125+
),
126+
);
127+
}
128+
},
127129
);
128130
},
129131
),
@@ -140,171 +142,105 @@ class _CountryFilterPageState extends State<CountryFilterPage> {
140142
),
141143
],
142144
),
143-
// Use BlocListener to react to state changes from CountriesFilterBloc
144-
body: BlocListener<CountriesFilterBloc, CountriesFilterState>(
145-
listenWhen: (previous, current) =>
146-
previous.followedCountriesStatus !=
147-
current.followedCountriesStatus ||
148-
previous.followedCountries != current.followedCountries,
149-
listener: (context, state) {
150-
if (state.followedCountriesStatus == CountriesFilterStatus.success) {
151-
// Update local state with followed countries from BLoC
152-
setState(() {
153-
_pageSelectedCountries = Set.from(state.followedCountries);
154-
});
155-
if (state.followedCountries.isEmpty) {
156-
ScaffoldMessenger.of(context)
157-
..hideCurrentSnackBar()
158-
..showSnackBar(
159-
SnackBar(
160-
content: Text(l10n.noFollowedItemsForFilterSnackbar),
161-
duration: const Duration(seconds: 3),
162-
),
163-
);
164-
}
165-
} else if (state.followedCountriesStatus ==
166-
CountriesFilterStatus.failure) {
167-
// Show error message if fetching followed countries failed
168-
ScaffoldMessenger.of(context)
169-
..hideCurrentSnackBar()
170-
..showSnackBar(
171-
SnackBar(
172-
content: Text(state.error?.message ?? l10n.unknownError),
173-
duration: const Duration(seconds: 3),
174-
),
175-
);
145+
body: BlocBuilder<CountriesFilterBloc, CountriesFilterState>(
146+
builder: (context, state) {
147+
// Determine overall loading status for the main list
148+
final isLoadingMainList =
149+
state.status == CountriesFilterStatus.loading;
150+
151+
// Handle initial loading state
152+
if (isLoadingMainList) {
153+
return LoadingStateWidget(
154+
icon: Icons.public_outlined,
155+
headline: l10n.countryFilterLoadingHeadline,
156+
subheadline: l10n.countryFilterLoadingSubheadline,
157+
);
176158
}
177-
},
178-
child: BlocBuilder<CountriesFilterBloc, CountriesFilterState>(
179-
builder: (context, state) {
180-
// Determine overall loading status for the main list
181-
final isLoadingMainList =
182-
state.status == CountriesFilterStatus.loading;
183-
184-
// Determine if followed countries are currently loading
185-
final isLoadingFollowedCountries =
186-
state.followedCountriesStatus == CountriesFilterStatus.loading;
187159

188-
// Handle initial loading state
189-
if (isLoadingMainList) {
190-
return LoadingStateWidget(
191-
icon: Icons.public_outlined,
192-
headline: l10n.countryFilterLoadingHeadline,
193-
subheadline: l10n.countryFilterLoadingSubheadline,
194-
);
195-
}
196-
197-
// Handle failure state (show error and retry button)
198-
if (state.status == CountriesFilterStatus.failure &&
199-
state.countries.isEmpty) {
200-
return FailureStateWidget(
201-
exception:
202-
state.error ?? const UnknownException('Unknown error'),
203-
onRetry: () => _countriesFilterBloc.add(
204-
CountriesFilterRequested(usage: widget.filter),
205-
),
206-
);
207-
}
208-
209-
// Handle empty state (after successful load but no countries found)
210-
if (state.status == CountriesFilterStatus.success &&
211-
state.countries.isEmpty) {
212-
return InitialStateWidget(
213-
icon: Icons.flag_circle_outlined,
214-
headline: l10n.countryFilterEmptyHeadline,
215-
subheadline: l10n.countryFilterEmptySubheadline,
216-
);
217-
}
160+
// Handle failure state (show error and retry button)
161+
if (state.status == CountriesFilterStatus.failure &&
162+
state.countries.isEmpty) {
163+
return FailureStateWidget(
164+
exception:
165+
state.error ?? const UnknownException('Unknown error'),
166+
onRetry: () => _countriesFilterBloc.add(
167+
CountriesFilterRequested(usage: widget.filter),
168+
),
169+
);
170+
}
218171

219-
// Handle loaded state (success)
220-
return Stack(
221-
children: [
222-
ListView.builder(
223-
padding: const EdgeInsets.symmetric(
224-
vertical: AppSpacing.paddingSmall,
225-
).copyWith(bottom: AppSpacing.xxl),
226-
itemCount: state.countries.length,
227-
itemBuilder: (context, index) {
228-
final country = state.countries[index];
229-
final isSelected = _pageSelectedCountries.contains(country);
172+
// Handle empty state (after successful load but no countries found)
173+
if (state.status == CountriesFilterStatus.success &&
174+
state.countries.isEmpty) {
175+
return InitialStateWidget(
176+
icon: Icons.flag_circle_outlined,
177+
headline: l10n.countryFilterEmptyHeadline,
178+
subheadline: l10n.countryFilterEmptySubheadline,
179+
);
180+
}
230181

231-
return CheckboxListTile(
232-
title: Text(country.name, style: textTheme.titleMedium),
233-
secondary: SizedBox(
234-
width: AppSpacing.xl + AppSpacing.xs,
235-
height: AppSpacing.lg + AppSpacing.sm,
236-
child: ClipRRect(
237-
// Clip the image for rounded corners if desired
238-
borderRadius: BorderRadius.circular(
239-
AppSpacing.xs / 2,
240-
),
241-
child: Image.network(
242-
country.flagUrl,
243-
fit: BoxFit.cover,
244-
errorBuilder: (context, error, stackTrace) => Icon(
245-
Icons.flag_outlined,
246-
color: theme.colorScheme.onSurfaceVariant,
247-
size: AppSpacing.lg,
248-
),
249-
loadingBuilder: (context, child, loadingProgress) {
250-
if (loadingProgress == null) return child;
251-
return Center(
252-
child: CircularProgressIndicator(
253-
strokeWidth: 2,
254-
value:
255-
loadingProgress.expectedTotalBytes != null
256-
? loadingProgress.cumulativeBytesLoaded /
257-
loadingProgress.expectedTotalBytes!
258-
: null,
259-
),
260-
);
261-
},
262-
),
263-
),
182+
// Handle loaded state (success)
183+
return ListView.builder(
184+
padding: const EdgeInsets.symmetric(
185+
vertical: AppSpacing.paddingSmall,
186+
).copyWith(bottom: AppSpacing.xxl),
187+
itemCount: state.countries.length,
188+
itemBuilder: (context, index) {
189+
final country = state.countries[index];
190+
final isSelected = _pageSelectedCountries.contains(country);
191+
192+
return CheckboxListTile(
193+
title: Text(country.name, style: textTheme.titleMedium),
194+
secondary: SizedBox(
195+
width: AppSpacing.xl + AppSpacing.xs,
196+
height: AppSpacing.lg + AppSpacing.sm,
197+
child: ClipRRect(
198+
// Clip the image for rounded corners if desired
199+
borderRadius: BorderRadius.circular(
200+
AppSpacing.xs / 2,
201+
),
202+
child: Image.network(
203+
country.flagUrl,
204+
fit: BoxFit.cover,
205+
errorBuilder: (context, error, stackTrace) => Icon(
206+
Icons.flag_outlined,
207+
color: theme.colorScheme.onSurfaceVariant,
208+
size: AppSpacing.lg,
264209
),
265-
value: isSelected,
266-
onChanged: (bool? value) {
267-
setState(() {
268-
if (value == true) {
269-
_pageSelectedCountries.add(country);
270-
} else {
271-
_pageSelectedCountries.remove(country);
272-
}
273-
});
210+
loadingBuilder: (context, child, loadingProgress) {
211+
if (loadingProgress == null) return child;
212+
return Center(
213+
child: CircularProgressIndicator(
214+
strokeWidth: 2,
215+
value:
216+
loadingProgress.expectedTotalBytes != null
217+
? loadingProgress.cumulativeBytesLoaded /
218+
loadingProgress.expectedTotalBytes!
219+
: null,
220+
),
221+
);
274222
},
275-
controlAffinity: ListTileControlAffinity.leading,
276-
contentPadding: const EdgeInsets.symmetric(
277-
horizontal: AppSpacing.paddingMedium,
278-
),
279-
);
280-
},
281-
),
282-
// Show loading overlay if followed countries are being fetched
283-
if (isLoadingFollowedCountries)
284-
Positioned.fill(
285-
child: ColoredBox(
286-
color: Colors.black54, // Semi-transparent overlay
287-
child: Center(
288-
child: Column(
289-
mainAxisAlignment: MainAxisAlignment.center,
290-
children: [
291-
const CircularProgressIndicator(),
292-
const SizedBox(height: AppSpacing.md),
293-
Text(
294-
l10n.headlinesFeedLoadingHeadline,
295-
style: theme.textTheme.titleMedium?.copyWith(
296-
color: Colors.white,
297-
),
298-
),
299-
],
300-
),
301-
),
302223
),
303224
),
304-
],
305-
);
306-
},
307-
),
225+
),
226+
value: isSelected,
227+
onChanged: (bool? value) {
228+
setState(() {
229+
if (value == true) {
230+
_pageSelectedCountries.add(country);
231+
} else {
232+
_pageSelectedCountries.remove(country);
233+
}
234+
});
235+
},
236+
controlAffinity: ListTileControlAffinity.leading,
237+
contentPadding: const EdgeInsets.symmetric(
238+
horizontal: AppSpacing.paddingMedium,
239+
),
240+
);
241+
},
242+
);
243+
},
308244
),
309245
);
310246
}

0 commit comments

Comments
 (0)