diff --git a/lib/blocs/stories/stories_bloc.dart b/lib/blocs/stories/stories_bloc.dart index 10e04db3..2aa6d0df 100644 --- a/lib/blocs/stories/stories_bloc.dart +++ b/lib/blocs/stories/stories_bloc.dart @@ -37,14 +37,14 @@ class StoriesBloc extends Bloc with Loggable { super(const StoriesState.init()) { on( onLoadStories, - transformer: sequential(), + transformer: concurrent(), ); on(onInitialize); on(onRefresh); on(onLoadMore); on( onStoryLoaded, - transformer: concurrent(), + transformer: sequential(), ); on(onStoryRead); on(onStoryUnread); @@ -109,11 +109,11 @@ class StoriesBloc extends Bloc with Loggable { emit( state .copyWithStoryIdsUpdated(type: type, to: ids) - .copyWithCurrentPageUpdated(type: type, to: 0) + .copyWithCurrentPageUpdated(type: type, to: 1) .copyWithStatusUpdated(type: type, to: Status.inProgress), ); _offlineRepository - .getCachedStoriesStream(ids: ids) + .getCachedStoriesStream(ids: ids.sublist(0, apiPageSize)) .listen((Story story) => add(StoryLoaded(story: story, type: type))) .onDone(() => add(StoryLoadingCompleted(type: type))); } else if (event.useApi || state.dataSource == HackerNewsDataSource.api) { @@ -191,57 +191,73 @@ class StoriesBloc extends Bloc with Loggable { StoriesLoadMore event, Emitter emit, ) async { - if (state.statusByType[event.type] == Status.inProgress) return; + final StoryType type = event.type; + + if (state.statusByType[type] == Status.inProgress) return; emit( state.copyWithStatusUpdated( - type: event.type, + type: type, to: Status.inProgress, ), ); - final int currentPage = state.currentPageByType[event.type]! + 1; + final int currentPage = state.currentPageByType[type]! + 1; emit( - state.copyWithCurrentPageUpdated(type: event.type, to: currentPage), + state.copyWithCurrentPageUpdated(type: type, to: currentPage), ); if (state.isOfflineReading) { - emit( - state.copyWithStatusUpdated( - type: event.type, - to: Status.success, - ), + final List? ids = state.storyIdsByType[type]; + if (ids == null) { + logError('ids should not be null.'); + emit( + state.copyWithStatusUpdated( + type: type, + to: Status.failure, + ), + ); + return; + } + final int length = ids.length; + final int lower = min(length, apiPageSize * (currentPage - 1)); + final int upper = min(length, lower + apiPageSize); + final List idsForCurrentPage = ids.sublist( + lower, + upper, ); + _offlineRepository + .getCachedStoriesStream(ids: idsForCurrentPage) + .listen((Story story) => add(StoryLoaded(story: story, type: type))) + .onDone(() => add(StoryLoadingCompleted(type: type))); } else if (event.useApi || state.dataSource == HackerNewsDataSource.api) { late final int length; - final List? ids = state.storyIdsByType[event.type]; + List? ids = state.storyIdsByType[type]; - if (ids?.isEmpty ?? true) { - final List ids = - await _hackerNewsRepository.fetchStoryIds(type: event.type); + if (ids == null || ids.isEmpty) { + ids = await _hackerNewsRepository.fetchStoryIds(type: type); length = ids.length; - emit(state.copyWith()); + emit(state.copyWithStoryIdsUpdated(type: type, to: ids)); } else { - length = ids!.length; + length = ids.length; } final int lower = min(length, apiPageSize * (currentPage - 1)); final int upper = min(length, lower + apiPageSize); + final List idsForCurrentPage = ids.sublist( + lower, + upper, + ); _hackerNewsRepository - .fetchStoriesStream( - ids: state.storyIdsByType[event.type]!.sublist( - lower, - upper, - ), - ) + .fetchStoriesStream(ids: idsForCurrentPage) .listen( - (Story story) => add(StoryLoaded(story: story, type: event.type)), + (Story story) => add(StoryLoaded(story: story, type: type)), ) - .onDone(() => add(StoryLoadingCompleted(type: event.type))); + .onDone(() => add(StoryLoadingCompleted(type: type))); } else { _hackerNewsWebRepository - .fetchStoriesStream(event.type, page: currentPage) + .fetchStoriesStream(type, page: currentPage) .handleError((dynamic e) { logError('error loading more stories $e'); @@ -254,16 +270,16 @@ class StoriesBloc extends Bloc with Loggable { add(event.copyWith(useApi: true)); emit( state.copyWithCurrentPageUpdated( - type: event.type, + type: type, to: currentPage - 1, ), ); } }) .listen( - (Story story) => add(StoryLoaded(story: story, type: event.type)), + (Story story) => add(StoryLoaded(story: story, type: type)), ) - .onDone(() => add(StoryLoadingCompleted(type: event.type))); + .onDone(() => add(StoryLoadingCompleted(type: type))); } } diff --git a/lib/screens/profile/widgets/enter_offline_mode_list_tile.dart b/lib/screens/profile/widgets/enter_offline_mode_list_tile.dart index 3e1e1c86..55d467f3 100644 --- a/lib/screens/profile/widgets/enter_offline_mode_list_tile.dart +++ b/lib/screens/profile/widgets/enter_offline_mode_list_tile.dart @@ -14,6 +14,7 @@ class EnterOfflineModeListTile extends StatelessWidget { builder: (BuildContext context, StoriesState state) { return SwitchListTile( value: state.isOfflineReading, + activeColor: Theme.of(context).colorScheme.primary, title: const Text('Offline Mode'), onChanged: (bool value) { HapticFeedbackUtil.light(); diff --git a/lib/screens/profile/widgets/settings.dart b/lib/screens/profile/widgets/settings.dart index 999c1fd6..e033cbb0 100644 --- a/lib/screens/profile/widgets/settings.dart +++ b/lib/screens/profile/widgets/settings.dart @@ -211,37 +211,42 @@ class _SettingsState extends State with ItemActionMixin, Loggable { const Text( 'Data source', ), - DropdownMenu( - /// Make sure no stories are being fetched before - /// switching data source. - enabled: !context - .read() - .state - .statusByType - .values - .any( - (Status status) => - status == Status.inProgress, - ), - initialSelection: preferenceState.dataSource, - dropdownMenuEntries: HackerNewsDataSource.values - .map( - (HackerNewsDataSource val) => - DropdownMenuEntry( - value: val, - label: val.description, - ), - ) - .toList(), - onSelected: (HackerNewsDataSource? source) { - if (source != null) { - HapticFeedbackUtil.selection(); - context.read().update( - HackerNewsDataSourcePreference( - val: source.index, - ), - ); - } + BlocSelector( + selector: (StoriesState state) => + state.statusByType.values.any( + (Status status) => status == Status.inProgress, + ), + builder: ( + BuildContext context, + bool isInProgress, + ) { + return DropdownMenu( + /// Make sure no stories are being fetched + /// before switching data source. + enabled: !isInProgress, + initialSelection: preferenceState.dataSource, + dropdownMenuEntries: + HackerNewsDataSource.values + .map( + (HackerNewsDataSource val) => + DropdownMenuEntry< + HackerNewsDataSource>( + value: val, + label: val.description, + ), + ) + .toList(), + onSelected: (HackerNewsDataSource? source) { + if (source != null) { + HapticFeedbackUtil.selection(); + context.read().update( + HackerNewsDataSourcePreference( + val: source.index, + ), + ); + } + }, + ); }, ), ], diff --git a/lib/screens/widgets/stories_list_view.dart b/lib/screens/widgets/stories_list_view.dart index 51b383de..8535033f 100644 --- a/lib/screens/widgets/stories_list_view.dart +++ b/lib/screens/widgets/stories_list_view.dart @@ -244,8 +244,6 @@ class _StoriesListViewState extends State } } - void loadMoreStories() { - HapticFeedbackUtil.light(); - context.read().add(StoriesLoadMore(type: widget.storyType)); - } + void loadMoreStories() => + context.read().add(StoriesLoadMore(type: widget.storyType)); }