Skip to content

Commit cf4592b

Browse files
authored
Merge pull request #69 from flutter-news-app-full-source-code/headlines-feed-sorted-by-date
Headlines feed sorted by date
2 parents 30d6a6e + 4c62d44 commit cf4592b

File tree

10 files changed

+66
-16
lines changed

10 files changed

+66
-16
lines changed

lib/account/bloc/account_bloc.dart

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ class AccountBloc extends Bloc<AccountEvent, AccountState> {
7575
emit(
7676
state.copyWith(
7777
status: AccountStatus.success,
78-
preferences: preferences,
78+
preferences: _sortPreferences(preferences),
7979
clearError: true,
8080
),
8181
);
@@ -98,7 +98,7 @@ class AccountBloc extends Bloc<AccountEvent, AccountState> {
9898
emit(
9999
state.copyWith(
100100
status: AccountStatus.success,
101-
preferences: migratedPreferences,
101+
preferences: _sortPreferences(migratedPreferences),
102102
clearError: true,
103103
),
104104
);
@@ -127,7 +127,7 @@ class AccountBloc extends Bloc<AccountEvent, AccountState> {
127127
);
128128
emit(
129129
state.copyWith(
130-
preferences: defaultPreferences,
130+
preferences: _sortPreferences(defaultPreferences),
131131
clearError: true,
132132
status: AccountStatus.success,
133133
),
@@ -146,7 +146,7 @@ class AccountBloc extends Bloc<AccountEvent, AccountState> {
146146
emit(
147147
state.copyWith(
148148
status: AccountStatus.success,
149-
preferences: existingPreferences,
149+
preferences: _sortPreferences(existingPreferences),
150150
clearError: true,
151151
),
152152
);
@@ -216,15 +216,16 @@ class AccountBloc extends Bloc<AccountEvent, AccountState> {
216216
);
217217

218218
try {
219+
final sortedPrefs = _sortPreferences(updatedPrefs);
219220
await _userContentPreferencesRepository.update(
220221
id: state.user!.id,
221-
item: updatedPrefs,
222+
item: sortedPrefs,
222223
userId: state.user!.id,
223224
);
224225
emit(
225226
state.copyWith(
226227
status: AccountStatus.success,
227-
preferences: updatedPrefs,
228+
preferences: sortedPrefs,
228229
clearError: true,
229230
),
230231
);
@@ -273,15 +274,16 @@ class AccountBloc extends Bloc<AccountEvent, AccountState> {
273274
);
274275

275276
try {
277+
final sortedPrefs = _sortPreferences(updatedPrefs);
276278
await _userContentPreferencesRepository.update(
277279
id: state.user!.id,
278-
item: updatedPrefs,
280+
item: sortedPrefs,
279281
userId: state.user!.id,
280282
);
281283
emit(
282284
state.copyWith(
283285
status: AccountStatus.success,
284-
preferences: updatedPrefs,
286+
preferences: sortedPrefs,
285287
clearError: true,
286288
),
287289
);
@@ -331,15 +333,16 @@ class AccountBloc extends Bloc<AccountEvent, AccountState> {
331333
);
332334

333335
try {
336+
final sortedPrefs = _sortPreferences(updatedPrefs);
334337
await _userContentPreferencesRepository.update(
335338
id: state.user!.id,
336-
item: updatedPrefs,
339+
item: sortedPrefs,
337340
userId: state.user!.id,
338341
);
339342
emit(
340343
state.copyWith(
341344
status: AccountStatus.success,
342-
preferences: updatedPrefs,
345+
preferences: sortedPrefs,
343346
clearError: true,
344347
),
345348
);
@@ -413,6 +416,32 @@ class AccountBloc extends Bloc<AccountEvent, AccountState> {
413416
}
414417
}
415418

419+
/// Sorts the lists within UserContentPreferences locally.
420+
///
421+
/// This client-side sorting is necessary due to a backend limitation that
422+
/// does not support sorting for saved or followed content lists. This
423+
/// approach remains efficient as these lists are fetched all at once and
424+
/// are kept small by user account-type limits.
425+
UserContentPreferences _sortPreferences(UserContentPreferences preferences) {
426+
// Sort saved headlines by updatedAt descending (newest first)
427+
final sortedHeadlines = List<Headline>.from(preferences.savedHeadlines)
428+
..sort((a, b) => b.updatedAt.compareTo(a.updatedAt));
429+
430+
// Sort followed topics by name ascending
431+
final sortedTopics = List<Topic>.from(preferences.followedTopics)
432+
..sort((a, b) => a.name.compareTo(b.name));
433+
434+
// Sort followed sources by name ascending
435+
final sortedSources = List<Source>.from(preferences.followedSources)
436+
..sort((a, b) => a.name.compareTo(b.name));
437+
438+
return preferences.copyWith(
439+
savedHeadlines: sortedHeadlines,
440+
followedTopics: sortedTopics,
441+
followedSources: sortedSources,
442+
);
443+
}
444+
416445
@override
417446
Future<void> close() {
418447
_userSubscription.cancel();

lib/account/bloc/available_sources_bloc.dart

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import 'dart:async';
22

33
import 'package:bloc/bloc.dart';
4-
import 'package:core/core.dart' show HttpException, Source;
4+
import 'package:core/core.dart';
55
import 'package:data_repository/data_repository.dart';
66
import 'package:equatable/equatable.dart';
77

@@ -33,9 +33,9 @@ class AvailableSourcesBloc
3333
emit(state.copyWith(status: AvailableSourcesStatus.loading));
3434
try {
3535
// Assuming readAll without parameters fetches all items.
36-
// Add pagination if necessary for very large datasets.
36+
// TODO(fulleni): Add pagination if necessary for very large datasets.
3737
final response = await _sourcesRepository.readAll(
38-
// limit: _sourcesLimit,
38+
sort: [const SortOption('name')],
3939
);
4040
emit(
4141
state.copyWith(

lib/account/bloc/available_topics_bloc.dart

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,10 @@ class AvailableTopicsBloc
2828
}
2929
emit(state.copyWith(status: AvailableTopicsStatus.loading));
3030
try {
31-
final response = await _topicsRepository.readAll();
31+
// TODO(fulleni): Add pagination if necessary for very large datasets.
32+
final response = await _topicsRepository.readAll(
33+
sort: [const SortOption('name')],
34+
);
3235
emit(
3336
state.copyWith(
3437
status: AvailableTopicsStatus.success,

lib/entity_details/bloc/entity_details_bloc.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ class EntityDetailsBloc extends Bloc<EntityDetailsEvent, EntityDetailsState> {
9595
final headlineResponse = await _headlinesRepository.readAll(
9696
filter: filter,
9797
pagination: const PaginationOptions(limit: _headlinesLimit),
98+
sort: [const SortOption('updatedAt', SortOrder.desc)],
9899
);
99100

100101
final currentUser = _appBloc.state.user;
@@ -207,6 +208,7 @@ class EntityDetailsBloc extends Bloc<EntityDetailsEvent, EntityDetailsState> {
207208
limit: _headlinesLimit,
208209
cursor: state.headlinesCursor,
209210
),
211+
sort: [const SortOption('updatedAt', SortOrder.desc)],
210212
);
211213

212214
final currentUser = _appBloc.state.user;

lib/headline-details/bloc/similar_headlines_bloc.dart

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import 'dart:async';
22

33
import 'package:bloc/bloc.dart';
4-
import 'package:core/core.dart' show Headline, HttpException, PaginationOptions;
4+
import 'package:core/core.dart'
5+
show Headline, HttpException, PaginationOptions, SortOption, SortOrder;
56
import 'package:data_repository/data_repository.dart';
67
import 'package:equatable/equatable.dart';
78

@@ -31,6 +32,7 @@ class SimilarHeadlinesBloc
3132

3233
final response = await _headlinesRepository.readAll(
3334
filter: filter,
35+
sort: [const SortOption('updatedAt', SortOrder.desc)],
3436
pagination: const PaginationOptions(
3537
limit:
3638
_similarHeadlinesLimit +

lib/headlines-feed/bloc/countries_filter_bloc.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ class CountriesFilterBloc
5353
try {
5454
final response = await _countriesRepository.readAll(
5555
pagination: const PaginationOptions(limit: _countriesLimit),
56+
sort: [const SortOption('name', SortOrder.desc)],
5657
);
5758
emit(
5859
state.copyWith(
@@ -85,6 +86,7 @@ class CountriesFilterBloc
8586
limit: _countriesLimit,
8687
cursor: state.cursor,
8788
),
89+
sort: [const SortOption('updatedAt', SortOrder.desc)],
8890
);
8991
emit(
9092
state.copyWith(

lib/headlines-feed/bloc/headlines_feed_bloc.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ class HeadlinesFeedBloc extends Bloc<HeadlinesFeedEvent, HeadlinesFeedState> {
9494
limit: _headlinesFetchLimit,
9595
cursor: state.cursor,
9696
),
97+
sort: [const SortOption('updatedAt', SortOrder.desc)],
9798
);
9899

99100
final newProcessedFeedItems = _feedInjectorService.injectItems(
@@ -147,6 +148,7 @@ class HeadlinesFeedBloc extends Bloc<HeadlinesFeedEvent, HeadlinesFeedState> {
147148
final headlineResponse = await _headlinesRepository.readAll(
148149
filter: _buildFilter(state.filter),
149150
pagination: const PaginationOptions(limit: _headlinesFetchLimit),
151+
sort: [const SortOption('updatedAt', SortOrder.desc)],
150152
);
151153

152154
final processedFeedItems = _feedInjectorService.injectItems(
@@ -209,6 +211,7 @@ class HeadlinesFeedBloc extends Bloc<HeadlinesFeedEvent, HeadlinesFeedState> {
209211
final headlineResponse = await _headlinesRepository.readAll(
210212
filter: _buildFilter(event.filter),
211213
pagination: const PaginationOptions(limit: _headlinesFetchLimit),
214+
sort: [const SortOption('updatedAt', SortOrder.desc)],
212215
);
213216

214217
final processedFeedItems = _feedInjectorService.injectItems(
@@ -269,6 +272,7 @@ class HeadlinesFeedBloc extends Bloc<HeadlinesFeedEvent, HeadlinesFeedState> {
269272

270273
final headlineResponse = await _headlinesRepository.readAll(
271274
pagination: const PaginationOptions(limit: _headlinesFetchLimit),
275+
sort: [const SortOption('updatedAt', SortOrder.desc)],
272276
);
273277

274278
final processedFeedItems = _feedInjectorService.injectItems(

lib/headlines-feed/bloc/topics_filter_bloc.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ class TopicsFilterBloc extends Bloc<TopicsFilterEvent, TopicsFilterState> {
5454
try {
5555
final response = await _topicsRepository.readAll(
5656
pagination: const PaginationOptions(limit: _topicsLimit),
57+
sort: [const SortOption('name', SortOrder.desc)],
5758
);
5859
emit(
5960
state.copyWith(
@@ -87,6 +88,7 @@ class TopicsFilterBloc extends Bloc<TopicsFilterEvent, TopicsFilterState> {
8788
limit: _topicsLimit,
8889
cursor: state.cursor,
8990
),
91+
sort: [const SortOption('name', SortOrder.desc)],
9092
);
9193
emit(
9294
state.copyWith(

lib/headlines-search/bloc/headlines_search_bloc.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ class HeadlinesSearchBloc
9292
limit: _limit,
9393
cursor: successState.cursor,
9494
),
95+
sort: [const SortOption('updatedAt', SortOrder.desc)],
9596
);
9697
// Cast to List<Headline> for the injector
9798
final headlines = response.items.cast<Headline>();
@@ -140,6 +141,7 @@ class HeadlinesSearchBloc
140141
limit: _limit,
141142
cursor: successState.cursor,
142143
),
144+
sort: [const SortOption('name', SortOrder.asc)],
143145
);
144146
emit(
145147
successState.copyWith(
@@ -157,6 +159,7 @@ class HeadlinesSearchBloc
157159
limit: _limit,
158160
cursor: successState.cursor,
159161
),
162+
sort: [const SortOption('name', SortOrder.asc)],
160163
);
161164
emit(
162165
successState.copyWith(
@@ -202,6 +205,7 @@ class HeadlinesSearchBloc
202205
rawResponse = await _headlinesRepository.readAll(
203206
filter: {'q': searchTerm},
204207
pagination: const PaginationOptions(limit: _limit),
208+
sort: [const SortOption('updatedAt', SortOrder.desc)],
205209
);
206210
final headlines = rawResponse.items.cast<Headline>();
207211
final currentUser = _appBloc.state.user;
@@ -226,12 +230,14 @@ class HeadlinesSearchBloc
226230
rawResponse = await _topicRepository.readAll(
227231
filter: {'q': searchTerm},
228232
pagination: const PaginationOptions(limit: _limit),
233+
sort: [const SortOption('name', SortOrder.asc)],
229234
);
230235
processedItems = rawResponse.items.cast<FeedItem>();
231236
case ContentType.source:
232237
rawResponse = await _sourceRepository.readAll(
233238
filter: {'q': searchTerm},
234239
pagination: const PaginationOptions(limit: _limit),
240+
sort: [const SortOption('name', SortOrder.asc)],
235241
);
236242
processedItems = rawResponse.items.cast<FeedItem>();
237243
default:

lib/main.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import 'package:flutter_news_app_mobile_client_full_source_code/app/services/spl
66
import 'package:flutter_news_app_mobile_client_full_source_code/bootstrap.dart';
77

88
// Define the current application environment (production/development/demo).
9-
const appEnvironment = AppEnvironment.demo;
9+
const appEnvironment = AppEnvironment.development;
1010

1111
void main() async {
1212
final appConfig = switch (appEnvironment) {

0 commit comments

Comments
 (0)