Skip to content

Commit f51c656

Browse files
authored
Merge pull request #95 from flutter-news-app-full-source-code/Refine-Main-Filter-Page-And-Implement-Apply-Favorites-in-Sub-Pages
Refine main filter page and implement apply favorites in sub pages
2 parents 6cb7032 + f9eaa16 commit f51c656

14 files changed

+871
-276
lines changed

lib/headlines-feed/bloc/countries_filter_bloc.dart

Lines changed: 69 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import 'package:bloc_concurrency/bloc_concurrency.dart';
55
import 'package:core/core.dart';
66
import 'package:data_repository/data_repository.dart';
77
import 'package:equatable/equatable.dart';
8+
import 'package:flutter_news_app_mobile_client_full_source_code/app/bloc/app_bloc.dart'; // Import AppBloc
89

910
part 'countries_filter_event.dart';
1011
part 'countries_filter_state.dart';
@@ -20,16 +21,29 @@ class CountriesFilterBloc
2021
/// {@macro countries_filter_bloc}
2122
///
2223
/// Requires a [DataRepository<Country>] to interact with the data layer.
23-
CountriesFilterBloc({required DataRepository<Country> countriesRepository})
24-
: _countriesRepository = countriesRepository,
25-
super(const CountriesFilterState()) {
24+
CountriesFilterBloc({
25+
required DataRepository<Country> countriesRepository,
26+
required DataRepository<UserContentPreferences>
27+
userContentPreferencesRepository, // Inject UserContentPreferencesRepository
28+
required AppBloc appBloc, // Inject AppBloc
29+
}) : _countriesRepository = countriesRepository,
30+
_userContentPreferencesRepository = userContentPreferencesRepository,
31+
_appBloc = appBloc,
32+
super(const CountriesFilterState()) {
2633
on<CountriesFilterRequested>(
2734
_onCountriesFilterRequested,
2835
transformer: restartable(),
2936
);
37+
on<CountriesFilterApplyFollowedRequested>(
38+
_onCountriesFilterApplyFollowedRequested,
39+
transformer: restartable(),
40+
); // Register new event handler
3041
}
3142

3243
final DataRepository<Country> _countriesRepository;
44+
final DataRepository<UserContentPreferences>
45+
_userContentPreferencesRepository;
46+
final AppBloc _appBloc;
3347

3448
/// Handles the request to fetch countries based on a specific usage.
3549
///
@@ -74,4 +88,56 @@ class CountriesFilterBloc
7488
emit(state.copyWith(status: CountriesFilterStatus.failure, error: e));
7589
}
7690
}
91+
92+
/// Handles the request to apply the user's followed countries as filters.
93+
Future<void> _onCountriesFilterApplyFollowedRequested(
94+
CountriesFilterApplyFollowedRequested event,
95+
Emitter<CountriesFilterState> emit,
96+
) async {
97+
emit(
98+
state.copyWith(followedCountriesStatus: CountriesFilterStatus.loading),
99+
);
100+
101+
final currentUser = _appBloc.state.user!;
102+
103+
try {
104+
final preferences = await _userContentPreferencesRepository.read(
105+
id: currentUser.id,
106+
userId: currentUser.id,
107+
);
108+
109+
if (preferences.followedCountries.isEmpty) {
110+
emit(
111+
state.copyWith(
112+
followedCountriesStatus: CountriesFilterStatus.success,
113+
followedCountries: const [],
114+
clearError: true,
115+
),
116+
);
117+
return;
118+
}
119+
120+
emit(
121+
state.copyWith(
122+
followedCountriesStatus: CountriesFilterStatus.success,
123+
followedCountries: preferences.followedCountries,
124+
clearFollowedCountriesError: true,
125+
),
126+
);
127+
} on HttpException catch (e) {
128+
emit(
129+
state.copyWith(
130+
followedCountriesStatus: CountriesFilterStatus.failure,
131+
error: e,
132+
),
133+
);
134+
} catch (e) {
135+
emit(
136+
state.copyWith(
137+
followedCountriesStatus: CountriesFilterStatus.failure,
138+
error: UnknownException(e.toString()),
139+
),
140+
);
141+
}
142+
}
77143
}

lib/headlines-feed/bloc/countries_filter_event.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,9 @@ final class CountriesFilterRequested extends CountriesFilterEvent {
2727
@override
2828
List<Object?> get props => [usage];
2929
}
30+
31+
/// {@template countries_filter_apply_followed_requested}
32+
/// Event triggered to request applying the user's followed countries as filters.
33+
/// {@endtemplate}
34+
final class CountriesFilterApplyFollowedRequested
35+
extends CountriesFilterEvent {}

lib/headlines-feed/bloc/countries_filter_state.dart

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ final class CountriesFilterState extends Equatable {
3434
this.hasMore = true,
3535
this.cursor,
3636
this.error,
37+
this.followedCountriesStatus = CountriesFilterStatus.initial,
38+
this.followedCountries = const [],
3739
});
3840

3941
/// The current status of fetching countries.
@@ -52,15 +54,24 @@ final class CountriesFilterState extends Equatable {
5254
/// An optional error object if the status is [CountriesFilterStatus.failure].
5355
final HttpException? error;
5456

57+
/// The current status of fetching followed countries.
58+
final CountriesFilterStatus followedCountriesStatus;
59+
60+
/// The list of [Country] objects representing the user's followed countries.
61+
final List<Country> followedCountries;
62+
5563
/// Creates a copy of this state with the given fields replaced.
5664
CountriesFilterState copyWith({
5765
CountriesFilterStatus? status,
5866
List<Country>? countries,
5967
bool? hasMore,
6068
String? cursor,
6169
HttpException? error,
70+
CountriesFilterStatus? followedCountriesStatus,
71+
List<Country>? followedCountries,
6272
bool clearError = false,
6373
bool clearCursor = false,
74+
bool clearFollowedCountriesError = false,
6475
}) {
6576
return CountriesFilterState(
6677
status: status ?? this.status,
@@ -70,9 +81,20 @@ final class CountriesFilterState extends Equatable {
7081
cursor: clearCursor ? null : (cursor ?? this.cursor),
7182
// Clear error if requested, otherwise keep existing or use new one
7283
error: clearError ? null : error ?? this.error,
84+
followedCountriesStatus:
85+
followedCountriesStatus ?? this.followedCountriesStatus,
86+
followedCountries: followedCountries ?? this.followedCountries,
7387
);
7488
}
7589

7690
@override
77-
List<Object?> get props => [status, countries, hasMore, cursor, error];
91+
List<Object?> get props => [
92+
status,
93+
countries,
94+
hasMore,
95+
cursor,
96+
error,
97+
followedCountriesStatus,
98+
followedCountries,
99+
];
78100
}

lib/headlines-feed/bloc/sources_filter_bloc.dart

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import 'package:bloc/bloc.dart';
44
import 'package:core/core.dart';
55
import 'package:data_repository/data_repository.dart';
66
import 'package:equatable/equatable.dart';
7+
import 'package:flutter_news_app_mobile_client_full_source_code/app/bloc/app_bloc.dart'; // Import AppBloc
78

89
part 'sources_filter_event.dart';
910
part 'sources_filter_state.dart';
@@ -12,20 +13,30 @@ class SourcesFilterBloc extends Bloc<SourcesFilterEvent, SourcesFilterState> {
1213
SourcesFilterBloc({
1314
required DataRepository<Source> sourcesRepository,
1415
required DataRepository<Country> countriesRepository,
16+
required DataRepository<UserContentPreferences>
17+
userContentPreferencesRepository,
18+
required AppBloc appBloc,
1519
}) : _sourcesRepository = sourcesRepository,
1620
_countriesRepository = countriesRepository,
21+
_userContentPreferencesRepository = userContentPreferencesRepository,
22+
_appBloc = appBloc,
1723
super(const SourcesFilterState()) {
1824
on<LoadSourceFilterData>(_onLoadSourceFilterData);
1925
on<CountryCapsuleToggled>(_onCountryCapsuleToggled);
2026
on<AllSourceTypesCapsuleToggled>(_onAllSourceTypesCapsuleToggled);
2127
on<SourceTypeCapsuleToggled>(_onSourceTypeCapsuleToggled);
2228
on<SourceCheckboxToggled>(_onSourceCheckboxToggled);
2329
on<ClearSourceFiltersRequested>(_onClearSourceFiltersRequested);
24-
// Removed _FetchFilteredSourcesRequested event listener
30+
on<SourcesFilterApplyFollowedRequested>(
31+
_onSourcesFilterApplyFollowedRequested,
32+
);
2533
}
2634

2735
final DataRepository<Source> _sourcesRepository;
2836
final DataRepository<Country> _countriesRepository;
37+
final DataRepository<UserContentPreferences>
38+
_userContentPreferencesRepository;
39+
final AppBloc _appBloc;
2940

3041
Future<void> _onLoadSourceFilterData(
3142
LoadSourceFilterData event,
@@ -182,6 +193,63 @@ class SourcesFilterBloc extends Bloc<SourcesFilterEvent, SourcesFilterState> {
182193
);
183194
}
184195

196+
/// Handles the request to apply the user's followed sources as filters.
197+
Future<void> _onSourcesFilterApplyFollowedRequested(
198+
SourcesFilterApplyFollowedRequested event,
199+
Emitter<SourcesFilterState> emit,
200+
) async {
201+
emit(
202+
state.copyWith(
203+
followedSourcesStatus: SourceFilterDataLoadingStatus.loading,
204+
),
205+
);
206+
207+
final currentUser = _appBloc.state.user!;
208+
209+
try {
210+
final preferences = await _userContentPreferencesRepository.read(
211+
id: currentUser.id,
212+
userId: currentUser.id,
213+
);
214+
215+
if (preferences.followedSources.isEmpty) {
216+
emit(
217+
state.copyWith(
218+
followedSourcesStatus: SourceFilterDataLoadingStatus.success,
219+
followedSources: const [],
220+
clearErrorMessage: true,
221+
),
222+
);
223+
return;
224+
}
225+
226+
emit(
227+
state.copyWith(
228+
followedSourcesStatus: SourceFilterDataLoadingStatus.success,
229+
followedSources: preferences.followedSources,
230+
finallySelectedSourceIds: preferences.followedSources
231+
.map((s) => s.id)
232+
.toSet(),
233+
clearFollowedSourcesError: true,
234+
),
235+
);
236+
} on HttpException catch (e) {
237+
emit(
238+
state.copyWith(
239+
followedSourcesStatus: SourceFilterDataLoadingStatus.failure,
240+
error: e,
241+
),
242+
);
243+
} catch (e) {
244+
emit(
245+
state.copyWith(
246+
followedSourcesStatus: SourceFilterDataLoadingStatus.failure,
247+
error: UnknownException(e.toString()),
248+
),
249+
);
250+
}
251+
}
252+
185253
// Helper method to filter sources based on selected countries and types
186254
List<Source> _getFilteredSources({
187255
required List<Source> allSources,

lib/headlines-feed/bloc/sources_filter_event.dart

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,13 @@ abstract class SourcesFilterEvent extends Equatable {
2020
/// {@endtemplate}
2121
class LoadSourceFilterData extends SourcesFilterEvent {
2222
/// {@macro load_source_filter_data}
23-
const LoadSourceFilterData({
24-
this.initialSelectedSources = const [],
25-
});
23+
const LoadSourceFilterData({this.initialSelectedSources = const []});
2624

2725
/// The list of sources that were initially selected on the previous page.
2826
final List<Source> initialSelectedSources;
2927

3028
@override
31-
List<Object?> get props => [
32-
initialSelectedSources,
33-
];
29+
List<Object?> get props => [initialSelectedSources];
3430
}
3531

3632
class CountryCapsuleToggled extends SourcesFilterEvent {
@@ -69,3 +65,8 @@ class SourceCheckboxToggled extends SourcesFilterEvent {
6965
class ClearSourceFiltersRequested extends SourcesFilterEvent {
7066
const ClearSourceFiltersRequested();
7167
}
68+
69+
/// {@template sources_filter_apply_followed_requested}
70+
/// Event triggered to request applying the user's followed sources as filters.
71+
/// {@endtemplate}
72+
final class SourcesFilterApplyFollowedRequested extends SourcesFilterEvent {}

lib/headlines-feed/bloc/sources_filter_state.dart

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ class SourcesFilterState extends Equatable {
1515
this.finallySelectedSourceIds = const {},
1616
this.dataLoadingStatus = SourceFilterDataLoadingStatus.initial,
1717
this.error,
18+
this.followedSourcesStatus = SourceFilterDataLoadingStatus.initial,
19+
this.followedSources = const [],
1820
});
1921

2022
final List<Country> availableCountries;
@@ -27,6 +29,12 @@ class SourcesFilterState extends Equatable {
2729
final SourceFilterDataLoadingStatus dataLoadingStatus;
2830
final HttpException? error;
2931

32+
/// The current status of fetching followed sources.
33+
final SourceFilterDataLoadingStatus followedSourcesStatus;
34+
35+
/// The list of [Source] objects representing the user's followed sources.
36+
final List<Source> followedSources;
37+
3038
SourcesFilterState copyWith({
3139
List<Country>? availableCountries,
3240
Set<String>? selectedCountryIsoCodes,
@@ -37,7 +45,10 @@ class SourcesFilterState extends Equatable {
3745
Set<String>? finallySelectedSourceIds,
3846
SourceFilterDataLoadingStatus? dataLoadingStatus,
3947
HttpException? error,
48+
SourceFilterDataLoadingStatus? followedSourcesStatus,
49+
List<Source>? followedSources,
4050
bool clearErrorMessage = false,
51+
bool clearFollowedSourcesError = false,
4152
}) {
4253
return SourcesFilterState(
4354
availableCountries: availableCountries ?? this.availableCountries,
@@ -51,6 +62,9 @@ class SourcesFilterState extends Equatable {
5162
finallySelectedSourceIds ?? this.finallySelectedSourceIds,
5263
dataLoadingStatus: dataLoadingStatus ?? this.dataLoadingStatus,
5364
error: clearErrorMessage ? null : error ?? this.error,
65+
followedSourcesStatus:
66+
followedSourcesStatus ?? this.followedSourcesStatus,
67+
followedSources: followedSources ?? this.followedSources,
5468
);
5569
}
5670

@@ -65,5 +79,7 @@ class SourcesFilterState extends Equatable {
6579
finallySelectedSourceIds,
6680
dataLoadingStatus,
6781
error,
82+
followedSourcesStatus,
83+
followedSources,
6884
];
6985
}

0 commit comments

Comments
 (0)