diff --git a/lib/account/bloc/account_bloc.dart b/lib/account/bloc/account_bloc.dart index fce99f9..ec0ec35 100644 --- a/lib/account/bloc/account_bloc.dart +++ b/lib/account/bloc/account_bloc.dart @@ -75,7 +75,7 @@ class AccountBloc extends Bloc { emit( state.copyWith( status: AccountStatus.success, - preferences: preferences, + preferences: _sortPreferences(preferences), clearError: true, ), ); @@ -98,7 +98,7 @@ class AccountBloc extends Bloc { emit( state.copyWith( status: AccountStatus.success, - preferences: migratedPreferences, + preferences: _sortPreferences(migratedPreferences), clearError: true, ), ); @@ -127,7 +127,7 @@ class AccountBloc extends Bloc { ); emit( state.copyWith( - preferences: defaultPreferences, + preferences: _sortPreferences(defaultPreferences), clearError: true, status: AccountStatus.success, ), @@ -146,7 +146,7 @@ class AccountBloc extends Bloc { emit( state.copyWith( status: AccountStatus.success, - preferences: existingPreferences, + preferences: _sortPreferences(existingPreferences), clearError: true, ), ); @@ -216,15 +216,16 @@ class AccountBloc extends Bloc { ); try { + final sortedPrefs = _sortPreferences(updatedPrefs); await _userContentPreferencesRepository.update( id: state.user!.id, - item: updatedPrefs, + item: sortedPrefs, userId: state.user!.id, ); emit( state.copyWith( status: AccountStatus.success, - preferences: updatedPrefs, + preferences: sortedPrefs, clearError: true, ), ); @@ -273,15 +274,16 @@ class AccountBloc extends Bloc { ); try { + final sortedPrefs = _sortPreferences(updatedPrefs); await _userContentPreferencesRepository.update( id: state.user!.id, - item: updatedPrefs, + item: sortedPrefs, userId: state.user!.id, ); emit( state.copyWith( status: AccountStatus.success, - preferences: updatedPrefs, + preferences: sortedPrefs, clearError: true, ), ); @@ -331,15 +333,16 @@ class AccountBloc extends Bloc { ); try { + final sortedPrefs = _sortPreferences(updatedPrefs); await _userContentPreferencesRepository.update( id: state.user!.id, - item: updatedPrefs, + item: sortedPrefs, userId: state.user!.id, ); emit( state.copyWith( status: AccountStatus.success, - preferences: updatedPrefs, + preferences: sortedPrefs, clearError: true, ), ); @@ -413,6 +416,32 @@ class AccountBloc extends Bloc { } } + /// Sorts the lists within UserContentPreferences locally. + /// + /// This client-side sorting is necessary due to a backend limitation that + /// does not support sorting for saved or followed content lists. This + /// approach remains efficient as these lists are fetched all at once and + /// are kept small by user account-type limits. + UserContentPreferences _sortPreferences(UserContentPreferences preferences) { + // Sort saved headlines by updatedAt descending (newest first) + final sortedHeadlines = List.from(preferences.savedHeadlines) + ..sort((a, b) => b.updatedAt.compareTo(a.updatedAt)); + + // Sort followed topics by name ascending + final sortedTopics = List.from(preferences.followedTopics) + ..sort((a, b) => a.name.compareTo(b.name)); + + // Sort followed sources by name ascending + final sortedSources = List.from(preferences.followedSources) + ..sort((a, b) => a.name.compareTo(b.name)); + + return preferences.copyWith( + savedHeadlines: sortedHeadlines, + followedTopics: sortedTopics, + followedSources: sortedSources, + ); + } + @override Future close() { _userSubscription.cancel(); diff --git a/lib/account/bloc/available_sources_bloc.dart b/lib/account/bloc/available_sources_bloc.dart index ed9666b..1f0713a 100644 --- a/lib/account/bloc/available_sources_bloc.dart +++ b/lib/account/bloc/available_sources_bloc.dart @@ -1,7 +1,7 @@ import 'dart:async'; import 'package:bloc/bloc.dart'; -import 'package:core/core.dart' show HttpException, Source; +import 'package:core/core.dart'; import 'package:data_repository/data_repository.dart'; import 'package:equatable/equatable.dart'; @@ -33,9 +33,9 @@ class AvailableSourcesBloc emit(state.copyWith(status: AvailableSourcesStatus.loading)); try { // Assuming readAll without parameters fetches all items. - // Add pagination if necessary for very large datasets. + // TODO(fulleni): Add pagination if necessary for very large datasets. final response = await _sourcesRepository.readAll( - // limit: _sourcesLimit, + sort: [const SortOption('name')], ); emit( state.copyWith( diff --git a/lib/account/bloc/available_topics_bloc.dart b/lib/account/bloc/available_topics_bloc.dart index d84b756..c39f01c 100644 --- a/lib/account/bloc/available_topics_bloc.dart +++ b/lib/account/bloc/available_topics_bloc.dart @@ -28,7 +28,10 @@ class AvailableTopicsBloc } emit(state.copyWith(status: AvailableTopicsStatus.loading)); try { - final response = await _topicsRepository.readAll(); + // TODO(fulleni): Add pagination if necessary for very large datasets. + final response = await _topicsRepository.readAll( + sort: [const SortOption('name')], + ); emit( state.copyWith( status: AvailableTopicsStatus.success, diff --git a/lib/entity_details/bloc/entity_details_bloc.dart b/lib/entity_details/bloc/entity_details_bloc.dart index 4efb756..b7b6147 100644 --- a/lib/entity_details/bloc/entity_details_bloc.dart +++ b/lib/entity_details/bloc/entity_details_bloc.dart @@ -95,6 +95,7 @@ class EntityDetailsBloc extends Bloc { final headlineResponse = await _headlinesRepository.readAll( filter: filter, pagination: const PaginationOptions(limit: _headlinesLimit), + sort: [const SortOption('updatedAt', SortOrder.desc)], ); final currentUser = _appBloc.state.user; @@ -207,6 +208,7 @@ class EntityDetailsBloc extends Bloc { limit: _headlinesLimit, cursor: state.headlinesCursor, ), + sort: [const SortOption('updatedAt', SortOrder.desc)], ); final currentUser = _appBloc.state.user; diff --git a/lib/headline-details/bloc/similar_headlines_bloc.dart b/lib/headline-details/bloc/similar_headlines_bloc.dart index bd785f7..83126c3 100644 --- a/lib/headline-details/bloc/similar_headlines_bloc.dart +++ b/lib/headline-details/bloc/similar_headlines_bloc.dart @@ -1,7 +1,8 @@ import 'dart:async'; import 'package:bloc/bloc.dart'; -import 'package:core/core.dart' show Headline, HttpException, PaginationOptions; +import 'package:core/core.dart' + show Headline, HttpException, PaginationOptions, SortOption, SortOrder; import 'package:data_repository/data_repository.dart'; import 'package:equatable/equatable.dart'; @@ -31,6 +32,7 @@ class SimilarHeadlinesBloc final response = await _headlinesRepository.readAll( filter: filter, + sort: [const SortOption('updatedAt', SortOrder.desc)], pagination: const PaginationOptions( limit: _similarHeadlinesLimit + diff --git a/lib/headlines-feed/bloc/countries_filter_bloc.dart b/lib/headlines-feed/bloc/countries_filter_bloc.dart index 0e2a101..1b554de 100644 --- a/lib/headlines-feed/bloc/countries_filter_bloc.dart +++ b/lib/headlines-feed/bloc/countries_filter_bloc.dart @@ -53,6 +53,7 @@ class CountriesFilterBloc try { final response = await _countriesRepository.readAll( pagination: const PaginationOptions(limit: _countriesLimit), + sort: [const SortOption('name', SortOrder.desc)], ); emit( state.copyWith( @@ -85,6 +86,7 @@ class CountriesFilterBloc limit: _countriesLimit, cursor: state.cursor, ), + sort: [const SortOption('updatedAt', SortOrder.desc)], ); emit( state.copyWith( diff --git a/lib/headlines-feed/bloc/headlines_feed_bloc.dart b/lib/headlines-feed/bloc/headlines_feed_bloc.dart index 900e2bf..bc76930 100644 --- a/lib/headlines-feed/bloc/headlines_feed_bloc.dart +++ b/lib/headlines-feed/bloc/headlines_feed_bloc.dart @@ -94,6 +94,7 @@ class HeadlinesFeedBloc extends Bloc { limit: _headlinesFetchLimit, cursor: state.cursor, ), + sort: [const SortOption('updatedAt', SortOrder.desc)], ); final newProcessedFeedItems = _feedInjectorService.injectItems( @@ -147,6 +148,7 @@ class HeadlinesFeedBloc extends Bloc { final headlineResponse = await _headlinesRepository.readAll( filter: _buildFilter(state.filter), pagination: const PaginationOptions(limit: _headlinesFetchLimit), + sort: [const SortOption('updatedAt', SortOrder.desc)], ); final processedFeedItems = _feedInjectorService.injectItems( @@ -209,6 +211,7 @@ class HeadlinesFeedBloc extends Bloc { final headlineResponse = await _headlinesRepository.readAll( filter: _buildFilter(event.filter), pagination: const PaginationOptions(limit: _headlinesFetchLimit), + sort: [const SortOption('updatedAt', SortOrder.desc)], ); final processedFeedItems = _feedInjectorService.injectItems( @@ -269,6 +272,7 @@ class HeadlinesFeedBloc extends Bloc { final headlineResponse = await _headlinesRepository.readAll( pagination: const PaginationOptions(limit: _headlinesFetchLimit), + sort: [const SortOption('updatedAt', SortOrder.desc)], ); final processedFeedItems = _feedInjectorService.injectItems( diff --git a/lib/headlines-feed/bloc/topics_filter_bloc.dart b/lib/headlines-feed/bloc/topics_filter_bloc.dart index 9f3fbd3..1f30b4c 100644 --- a/lib/headlines-feed/bloc/topics_filter_bloc.dart +++ b/lib/headlines-feed/bloc/topics_filter_bloc.dart @@ -54,6 +54,7 @@ class TopicsFilterBloc extends Bloc { try { final response = await _topicsRepository.readAll( pagination: const PaginationOptions(limit: _topicsLimit), + sort: [const SortOption('name', SortOrder.desc)], ); emit( state.copyWith( @@ -87,6 +88,7 @@ class TopicsFilterBloc extends Bloc { limit: _topicsLimit, cursor: state.cursor, ), + sort: [const SortOption('name', SortOrder.desc)], ); emit( state.copyWith( diff --git a/lib/headlines-search/bloc/headlines_search_bloc.dart b/lib/headlines-search/bloc/headlines_search_bloc.dart index bc68748..ee269a9 100644 --- a/lib/headlines-search/bloc/headlines_search_bloc.dart +++ b/lib/headlines-search/bloc/headlines_search_bloc.dart @@ -92,6 +92,7 @@ class HeadlinesSearchBloc limit: _limit, cursor: successState.cursor, ), + sort: [const SortOption('updatedAt', SortOrder.desc)], ); // Cast to List for the injector final headlines = response.items.cast(); @@ -140,6 +141,7 @@ class HeadlinesSearchBloc limit: _limit, cursor: successState.cursor, ), + sort: [const SortOption('name', SortOrder.asc)], ); emit( successState.copyWith( @@ -157,6 +159,7 @@ class HeadlinesSearchBloc limit: _limit, cursor: successState.cursor, ), + sort: [const SortOption('name', SortOrder.asc)], ); emit( successState.copyWith( @@ -202,6 +205,7 @@ class HeadlinesSearchBloc rawResponse = await _headlinesRepository.readAll( filter: {'q': searchTerm}, pagination: const PaginationOptions(limit: _limit), + sort: [const SortOption('updatedAt', SortOrder.desc)], ); final headlines = rawResponse.items.cast(); final currentUser = _appBloc.state.user; @@ -226,12 +230,14 @@ class HeadlinesSearchBloc rawResponse = await _topicRepository.readAll( filter: {'q': searchTerm}, pagination: const PaginationOptions(limit: _limit), + sort: [const SortOption('name', SortOrder.asc)], ); processedItems = rawResponse.items.cast(); case ContentType.source: rawResponse = await _sourceRepository.readAll( filter: {'q': searchTerm}, pagination: const PaginationOptions(limit: _limit), + sort: [const SortOption('name', SortOrder.asc)], ); processedItems = rawResponse.items.cast(); default: diff --git a/lib/main.dart b/lib/main.dart index 3c5641e..9e1b3c1 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -6,7 +6,7 @@ import 'package:flutter_news_app_mobile_client_full_source_code/app/services/spl import 'package:flutter_news_app_mobile_client_full_source_code/bootstrap.dart'; // Define the current application environment (production/development/demo). -const appEnvironment = AppEnvironment.demo; +const appEnvironment = AppEnvironment.development; void main() async { final appConfig = switch (appEnvironment) {