diff --git a/lib/blocs/stories/stories_bloc.dart b/lib/blocs/stories/stories_bloc.dart index 1dd5027e..10e04db3 100644 --- a/lib/blocs/stories/stories_bloc.dart +++ b/lib/blocs/stories/stories_bloc.dart @@ -44,7 +44,7 @@ class StoriesBloc extends Bloc with Loggable { on(onLoadMore); on( onStoryLoaded, - transformer: sequential(), + transformer: concurrent(), ); on(onStoryRead); on(onStoryUnread); @@ -53,8 +53,14 @@ class StoriesBloc extends Bloc with Loggable { on(onStoryDownloaded); on(onEnterOfflineMode); on(onExitOfflineMode); - on(onPageSizeChanged); on(onClearAllReadStories); + + _preferenceSubscription = _preferenceCubit.stream + .distinct((PreferenceState lhs, PreferenceState rhs) { + return lhs.dataSource == rhs.dataSource; + }).listen((PreferenceState prefState) { + add(StoriesInitialize()); + }); } final PreferenceCubit _preferenceCubit; @@ -71,15 +77,6 @@ class StoriesBloc extends Bloc with Loggable { StoriesInitialize event, Emitter emit, ) async { - _preferenceSubscription = _preferenceCubit.stream - .distinct( - (PreferenceState lhs, PreferenceState rhs) => - lhs.dataSource == rhs.dataSource, - ) - .listen((PreferenceState prefState) { - add(StoriesInitialize()); - }); - final HackerNewsDataSource dataSource = _preferenceCubit.state.dataSource; emit( @@ -138,6 +135,7 @@ class StoriesBloc extends Bloc with Loggable { .listen((Story story) { add(StoryLoaded(story: story, type: type)); }).asFuture(); + add(StoryLoadingCompleted(type: type)); } else { emit( state @@ -223,6 +221,7 @@ class StoriesBloc extends Bloc with Loggable { final List ids = await _hackerNewsRepository.fetchStoryIds(type: event.type); length = ids.length; + emit(state.copyWith()); } else { length = ids!.length; } @@ -492,13 +491,6 @@ class StoriesBloc extends Bloc with Loggable { } } - Future onPageSizeChanged( - StoriesPageSizeChanged event, - Emitter emit, - ) async { - add(StoriesInitialize()); - } - Future onExitOfflineMode( StoriesExitOfflineMode event, Emitter emit, diff --git a/lib/blocs/stories/stories_event.dart b/lib/blocs/stories/stories_event.dart index 4211164a..1b7164d6 100644 --- a/lib/blocs/stories/stories_event.dart +++ b/lib/blocs/stories/stories_event.dart @@ -98,15 +98,6 @@ class StoriesEnterOfflineMode extends StoriesEvent { List get props => []; } -class StoriesPageSizeChanged extends StoriesEvent { - StoriesPageSizeChanged({required this.pageSize}); - - final int pageSize; - - @override - List get props => [pageSize]; -} - class StoryLoaded extends StoriesEvent { StoryLoaded({required this.story, required this.type}); diff --git a/lib/models/item/story.dart b/lib/models/item/story.dart index 0b2a03f5..7921bb69 100644 --- a/lib/models/item/story.dart +++ b/lib/models/item/story.dart @@ -62,7 +62,7 @@ class Story extends Item { } String get metadata => - '''$score point${score > 1 ? 's' : ''} by $by $timeAgo | $descendants comment${descendants > 1 ? 's' : ''}'''; + '''$score point${score > 1 ? 's' : ''}${by.isNotEmpty ? ' $by ' : ' '}$timeAgo | $descendants comment${descendants > 1 ? 's' : ''}'''; String get screenReaderLabel => '''$title, at $readableUrl, by $by $timeAgo. This story has $score point${score > 1 ? 's' : ''} and $descendants comment${descendants > 1 ? 's' : ''}'''; diff --git a/lib/repositories/hacker_news_web_repository.dart b/lib/repositories/hacker_news_web_repository.dart index 146c63ac..71880e9b 100644 --- a/lib/repositories/hacker_news_web_repository.dart +++ b/lib/repositories/hacker_news_web_repository.dart @@ -201,6 +201,9 @@ class HackerNewsWebRepository { parts: const [], ); + /// If it is a story about launching or from ask section, then + /// we need to fetch it from API since the html doesn't contain + /// too much info. if (timestamp == null || url.isEmpty || url.contains('item?id=') || @@ -214,7 +217,7 @@ class HackerNewsWebRepository { } } - /// Duplicate comment means we are done fetching all the comments. + /// Duplicate story means we are done fetching all the stories. if (fetchedStoryIds.contains(story.id)) return; fetchedStoryIds.add(story.id); diff --git a/lib/screens/profile/widgets/settings.dart b/lib/screens/profile/widgets/settings.dart index 29fdc3fe..999c1fd6 100644 --- a/lib/screens/profile/widgets/settings.dart +++ b/lib/screens/profile/widgets/settings.dart @@ -83,159 +83,169 @@ class _SettingsState extends State with ItemActionMixin, Loggable { const SizedBox( height: Dimens.pt8, ), - Flex( - direction: Axis.horizontal, - mainAxisAlignment: MainAxisAlignment.center, - mainAxisSize: MainAxisSize.min, + OverflowBar( + alignment: MainAxisAlignment.spaceBetween, + overflowSpacing: Dimens.pt12, children: [ - Flexible( - child: Row( + Padding( + padding: const EdgeInsets.only( + left: Dimens.pt16, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - const SizedBox( - width: Dimens.pt16, - ), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Text('Default fetch mode'), - DropdownMenu( - initialSelection: preferenceState.fetchMode, - dropdownMenuEntries: FetchMode.values - .map( - (FetchMode val) => - DropdownMenuEntry( - value: val, - label: val.description, + const Text('Default fetch mode'), + DropdownMenu( + initialSelection: preferenceState.fetchMode, + dropdownMenuEntries: FetchMode.values + .map( + (FetchMode val) => + DropdownMenuEntry( + value: val, + label: val.description, + ), + ) + .toList(), + onSelected: (FetchMode? fetchMode) { + if (fetchMode != null) { + HapticFeedbackUtil.selection(); + context.read().update( + FetchModePreference( + val: fetchMode.index, ), - ) - .toList(), - onSelected: (FetchMode? fetchMode) { - if (fetchMode != null) { - HapticFeedbackUtil.selection(); - context.read().update( - FetchModePreference( - val: fetchMode.index, - ), - ); - } - }, - ), - ], + ); + } + }, ), ], ), ), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Text('Default comments order'), - DropdownMenu( - initialSelection: preferenceState.order, - dropdownMenuEntries: CommentsOrder.values - .map( - (CommentsOrder val) => - DropdownMenuEntry( - value: val, - label: val.description, - ), - ) - .toList(), - onSelected: (CommentsOrder? order) { - if (order != null) { - HapticFeedbackUtil.selection(); - context.read().update( - CommentsOrderPreference( - val: order.index, - ), - ); - } - }, - ), - ], - ), - const SizedBox( - width: Dimens.pt16, - ), - ], - ), - const SizedBox( - height: Dimens.pt12, - ), - Row( - children: [ - const SizedBox( - width: Dimens.pt16, - ), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Text( - 'Data source', - ), - DropdownMenu( - 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, - ), - ); - } - }, - ), - ], + Padding( + padding: const EdgeInsets.only( + left: Dimens.pt16, + right: Dimens.pt16, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text('Default comments order'), + DropdownMenu( + initialSelection: preferenceState.order, + dropdownMenuEntries: CommentsOrder.values + .map( + (CommentsOrder val) => + DropdownMenuEntry( + value: val, + label: val.description, + ), + ) + .toList(), + onSelected: (CommentsOrder? order) { + if (order != null) { + HapticFeedbackUtil.selection(); + context.read().update( + CommentsOrderPreference( + val: order.index, + ), + ); + } + }, + ), + ], + ), ), ], ), const SizedBox( height: Dimens.pt12, ), - Row( + OverflowBar( + alignment: MainAxisAlignment.spaceBetween, + overflowSpacing: Dimens.pt12, children: [ - const SizedBox( - width: Dimens.pt16, + Padding( + padding: const EdgeInsets.only( + left: Dimens.pt16, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Date time display of comments', + ), + DropdownMenu( + initialSelection: + preferenceState.displayDateFormat, + dropdownMenuEntries: DateDisplayFormat.values + .map( + (DateDisplayFormat val) => + DropdownMenuEntry( + value: val, + label: val.description, + ), + ) + .toList(), + onSelected: (DateDisplayFormat? order) { + if (order != null) { + HapticFeedbackUtil.selection(); + context.read().update( + DateFormatPreference( + val: order.index, + ), + ); + DateDisplayFormat.clearCache(); + } + }, + ), + ], + ), ), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Text( - 'Date time display of comments', - ), - DropdownMenu( - initialSelection: preferenceState.displayDateFormat, - dropdownMenuEntries: DateDisplayFormat.values - .map( - (DateDisplayFormat val) => - DropdownMenuEntry( - value: val, - label: val.description, + Padding( + padding: const EdgeInsets.only( + left: Dimens.pt16, + right: Dimens.pt16, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + 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, ), - ) - .toList(), - onSelected: (DateDisplayFormat? order) { - if (order != null) { - HapticFeedbackUtil.selection(); - context.read().update( - DateFormatPreference( - val: order.index, - ), - ); - DateDisplayFormat.clearCache(); - } - }, - ), - ], + 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, + ), + ); + } + }, + ), + ], + ), ), ], ), diff --git a/lib/screens/widgets/stories_list_view.dart b/lib/screens/widgets/stories_list_view.dart index 1f152de4..51b383de 100644 --- a/lib/screens/widgets/stories_list_view.dart +++ b/lib/screens/widgets/stories_list_view.dart @@ -66,8 +66,8 @@ class _StoriesListViewState extends State } }, buildWhen: (StoriesState previous, StoriesState current) => - (current.currentPageByType[storyType] == 0 && - previous.currentPageByType[storyType] == 0) || + (current.currentPageByType[storyType] == 1 && + previous.currentPageByType[storyType] == 1) || (previous.storiesByType[storyType]!.length != current.storiesByType[storyType]!.length) || (previous.readStoriesIds.length != @@ -106,7 +106,8 @@ class _StoriesListViewState extends State onPinned: context.read().pinStory, header: state.isOfflineReading ? null : header, loadStyle: LoadStyle.HideAlways, - footer: preferenceState.isManualPaginationEnabled + footer: preferenceState.isManualPaginationEnabled && + state.statusByType[widget.storyType] == Status.success ? Center( child: Padding( padding: const EdgeInsets.only( @@ -125,16 +126,9 @@ class _StoriesListViewState extends State (_) => Theme.of(context).colorScheme.onSurface, ), ), - child: state.statusByType[widget.storyType] == - Status.success - ? Text( - '''Load Page ${(state.currentPageByType[widget.storyType] ?? 0) + 1}''', - ) - : const SizedBox( - height: Dimens.pt6, - width: Dimens.pt6, - child: CustomCircularProgressIndicator(), - ), + child: Text( + '''Load Page ${(state.currentPageByType[widget.storyType] ?? 0) + 1}''', + ), ), ), ) @@ -250,6 +244,8 @@ class _StoriesListViewState extends State } } - void loadMoreStories() => - context.read().add(StoriesLoadMore(type: widget.storyType)); + void loadMoreStories() { + HapticFeedbackUtil.light(); + context.read().add(StoriesLoadMore(type: widget.storyType)); + } }