diff --git a/lib/blocs/stories/stories_bloc.dart b/lib/blocs/stories/stories_bloc.dart index 442ed80..998983b 100644 --- a/lib/blocs/stories/stories_bloc.dart +++ b/lib/blocs/stories/stories_bloc.dart @@ -13,6 +13,7 @@ import 'package:responsive_builder/responsive_builder.dart'; import 'package:rxdart/rxdart.dart'; part 'stories_event.dart'; + part 'stories_state.dart'; class StoriesBloc extends Bloc { @@ -95,7 +96,8 @@ class StoriesBloc extends Bloc { storiesToBeDownloaded: state.storiesToBeDownloaded, ), ); - for (final StoryType type in StoryType.values) { + + for (final StoryType type in _preferenceCubit.state.tabs) { add(LoadStories(type: type)); } } @@ -111,28 +113,44 @@ class StoriesBloc extends Bloc { emit( state .copyWithStoryIdsUpdated(type: type, to: ids) - .copyWithCurrentPageUpdated(type: type, to: 0), + .copyWithCurrentPageUpdated(type: type, to: 0) + .copyWithStatusUpdated(type: type, to: Status.inProgress), ); _offlineRepository .getCachedStoriesStream( - ids: ids.sublist(0, min(ids.length, state.currentPageSize)), - ) - .listen((Story story) { - add(StoryLoaded(story: story, type: type)); - }).onDone(() { - add(StoriesLoaded(type: type)); - }); + ids: ids.sublist(0, min(ids.length, state.currentPageSize)), + ) + .listen((Story story) => add(StoryLoaded(story: story, type: type))) + .onDone(() => add(StoriesLoaded(type: type))); } else { final List ids = await _hackerNewsRepository.fetchStoryIds(type: type); emit( state .copyWithStoryIdsUpdated(type: type, to: ids) - .copyWithCurrentPageUpdated(type: type, to: 0), + .copyWithCurrentPageUpdated(type: type, to: 0) + .copyWithStatusUpdated(type: type, to: Status.inProgress), ); + bool hasFirstArrived = false; await _hackerNewsRepository - .fetchStoriesStream(ids: ids.sublist(0, state.currentPageSize)) + .fetchStoriesStream( + ids: ids.sublist(0, state.currentPageSize), + sequential: !event.isRefreshing, + ) .listen((Story story) { + /// When it's refreshing, only flush + /// previous stories when new stories has been fetched. + if (event.isRefreshing && !hasFirstArrived) { + hasFirstArrived = true; + final Map> newStoriesMap = + Map>.from(state.storiesByType); + newStoriesMap[type] = []; + emit( + state.copyWith( + storiesByType: newStoriesMap, + ), + ); + } add(StoryLoaded(story: story, type: type)); }).asFuture(); add(StoriesLoaded(type: type)); @@ -161,11 +179,13 @@ class StoriesBloc extends Bloc { ); } else { emit(state.copyWithRefreshed(type: event.type)); - add(LoadStories(type: event.type)); + add(LoadStories(type: event.type, isRefreshing: true)); } } void onLoadMore(StoriesLoadMore event, Emitter emit) { + if (state.statusByType[event.type] == Status.inProgress) return; + emit( state.copyWithStatusUpdated( type: event.type, @@ -190,39 +210,27 @@ class StoriesBloc extends Bloc { if (state.isOfflineReading) { _offlineRepository .getCachedStoriesStream( - ids: state.storyIdsByType[event.type]!.sublist( - lower, - upper, - ), - ) - .listen((Story story) { - add( - StoryLoaded( - story: story, - type: event.type, - ), - ); - }).onDone(() { - add(StoriesLoaded(type: event.type)); - }); + ids: state.storyIdsByType[event.type]!.sublist( + lower, + upper, + ), + ) + .listen( + (Story story) => add(StoryLoaded(story: story, type: event.type)), + ) + .onDone(() => add(StoriesLoaded(type: event.type))); } else { _hackerNewsRepository .fetchStoriesStream( - ids: state.storyIdsByType[event.type]!.sublist( - lower, - upper, - ), - ) - .listen((Story story) { - add( - StoryLoaded( - story: story, - type: event.type, - ), - ); - }).onDone(() { - add(StoriesLoaded(type: event.type)); - }); + ids: state.storyIdsByType[event.type]!.sublist( + lower, + upper, + ), + ) + .listen( + (Story story) => add(StoryLoaded(story: story, type: event.type)), + ) + .onDone(() => add(StoriesLoaded(type: event.type))); } } else { emit( diff --git a/lib/blocs/stories/stories_event.dart b/lib/blocs/stories/stories_event.dart index f0a5c6a..94fc26b 100644 --- a/lib/blocs/stories/stories_event.dart +++ b/lib/blocs/stories/stories_event.dart @@ -6,12 +6,16 @@ abstract class StoriesEvent extends Equatable { } class LoadStories extends StoriesEvent { - LoadStories({required this.type}); + LoadStories({required this.type, this.isRefreshing = false}); final StoryType type; + final bool isRefreshing; @override - List get props => [type]; + List get props => [ + type, + isRefreshing, + ]; } class StoriesInitialize extends StoriesEvent { diff --git a/lib/blocs/stories/stories_state.dart b/lib/blocs/stories/stories_state.dart index 4826404..10f4070 100644 --- a/lib/blocs/stories/stories_state.dart +++ b/lib/blocs/stories/stories_state.dart @@ -150,12 +150,6 @@ class StoriesState extends Equatable { } StoriesState copyWithRefreshed({required StoryType type}) { - final Map> newStoriesMap = - Map>.from(storiesByType); - newStoriesMap[type] = []; - final Map> newStoryIdsMap = - Map>.from(storyIdsByType); - newStoryIdsMap[type] = []; final Map newStatusMap = Map.from(statusByType); newStatusMap[type] = Status.inProgress; @@ -163,8 +157,6 @@ class StoriesState extends Equatable { Map.from(currentPageByType); newCurrentPageMap[type] = 0; return copyWith( - storiesByType: newStoriesMap, - storyIdsByType: newStoryIdsMap, statusByType: newStatusMap, currentPageByType: newCurrentPageMap, ); diff --git a/lib/main.dart b/lib/main.dart index 9bc49a5..b9dae1f 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -281,13 +281,21 @@ class HackiApp extends StatelessWidget { .instance.platformDispatcher.platformBrightness, mode, ); - final bool isDarkModeEnabled = mode == null || - mode == AdaptiveThemeMode.dark || - (mode == AdaptiveThemeMode.system && - View.of(context) - .platformDispatcher - .platformBrightness == - Brightness.dark); + final bool isDarkModeEnabled = () { + if (mode == null) { + return View.of(context) + .platformDispatcher + .platformBrightness == + Brightness.dark; + } else { + return mode == AdaptiveThemeMode.dark || + (mode == AdaptiveThemeMode.system && + View.of(context) + .platformDispatcher + .platformBrightness == + Brightness.dark); + } + }(); final ColorScheme colorScheme = ColorScheme.fromSeed( brightness: isDarkModeEnabled ? Brightness.dark : Brightness.light, diff --git a/lib/repositories/hacker_news_repository.dart b/lib/repositories/hacker_news_repository.dart index 4256e45..799c90b 100644 --- a/lib/repositories/hacker_news_repository.dart +++ b/lib/repositories/hacker_news_repository.dart @@ -329,16 +329,32 @@ class HackerNewsRepository { /// Fetch a list of [Story] based on ids and return results /// using a stream. - Stream fetchStoriesStream({required List ids}) async* { - for (final int id in ids) { - final Story? story = - await _fetchItemJson(id).then((Map? json) async { - if (json == null) return null; - final Story story = Story.fromJson(json); - return story; - }); + Stream fetchStoriesStream({ + required List ids, + bool sequential = false, + }) async* { + if (sequential) { + for (final int id in ids) { + final Story? story = + await _fetchItemJson(id).then((Map? json) async { + if (json == null) return null; + final Story story = Story.fromJson(json); + return story; + }); - if (story != null) { + if (story != null) { + yield story; + } + } + } else { + final List?> responses = await Future.wait( + ?>>[ + ...ids.map(_fetchItemJson), + ], + ); + for (final Map? json in responses) { + if (json == null) continue; + final Story story = Story.fromJson(json); yield story; } } diff --git a/lib/screens/widgets/story_tile.dart b/lib/screens/widgets/story_tile.dart index 58876ae..bd0dbe2 100644 --- a/lib/screens/widgets/story_tile.dart +++ b/lib/screens/widgets/story_tile.dart @@ -3,10 +3,12 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_fadein/flutter_fadein.dart'; import 'package:hacki/blocs/blocs.dart'; import 'package:hacki/config/constants.dart'; +import 'package:hacki/cubits/cubits.dart'; import 'package:hacki/extensions/extensions.dart'; import 'package:hacki/models/models.dart'; import 'package:hacki/screens/widgets/widgets.dart'; import 'package:hacki/styles/styles.dart'; +import 'package:hacki/utils/utils.dart'; import 'package:shimmer/shimmer.dart'; class StoryTile extends StatelessWidget { @@ -123,6 +125,17 @@ class StoryTile extends StatelessWidget { excludeSemantics: true, child: InkWell( onTap: onTap, + onLongPress: () { + if (story.url.isNotEmpty) { + LinkUtil.launch( + story.url, + context, + useReader: context.read().state.readerEnabled, + offlineReading: + context.read().state.isOfflineReading, + ); + } + }, child: Padding( padding: const EdgeInsets.only(left: Dimens.pt12), child: Column(