diff --git a/lib/blocs/stories/stories_bloc.dart b/lib/blocs/stories/stories_bloc.dart index a3c4252d..063e7b35 100644 --- a/lib/blocs/stories/stories_bloc.dart +++ b/lib/blocs/stories/stories_bloc.dart @@ -14,7 +14,6 @@ import 'package:responsive_builder/responsive_builder.dart'; import 'package:rxdart/rxdart.dart'; part 'stories_event.dart'; - part 'stories_state.dart'; class StoriesBloc extends Bloc with Loggable { @@ -55,6 +54,7 @@ class StoriesBloc extends Bloc with Loggable { on(onEnterOfflineMode); on(onExitOfflineMode); on(onClearAllReadStories); + on(onUpdateMaxOfflineStoriesCount); _preferenceSubscription = _preferenceCubit.stream .distinct((PreferenceState lhs, PreferenceState rhs) { @@ -350,12 +350,16 @@ class StoriesBloc extends Bloc with Loggable { await _offlineRepository.deleteAllComments(); await _offlineRepository.deleteAllWebPages(); - final Set prioritizedIds = {}; + List prioritizedIds = []; /// Prioritizing all types of stories except StoryType.latest since /// new stories tend to have less or no comment at all. - final List prioritizedTypes = [...StoryType.values] - ..remove(StoryType.latest); + final List prioritizedTypes = [ + StoryType.top, + StoryType.best, + StoryType.ask, + StoryType.show, + ]; for (final StoryType type in prioritizedTypes) { final List ids = @@ -364,6 +368,14 @@ class StoriesBloc extends Bloc with Loggable { prioritizedIds.addAll(ids); } + prioritizedIds = prioritizedIds.toSet().toList().sublist( + 0, + min( + state.maxOfflineStoriesCount?.count ?? prioritizedIds.length, + prioritizedIds.length, + ), + ); + emit( state.copyWith( storiesDownloaded: 0, @@ -539,6 +551,13 @@ class StoriesBloc extends Bloc with Loggable { add(StoriesInitialize()); } + Future onUpdateMaxOfflineStoriesCount( + UpdateMaxOfflineStoriesCount event, + Emitter emit, + ) async { + emit(state.copyWith(maxOfflineStoriesCount: event.count)); + } + Future onStoryRead( StoryRead event, Emitter emit, diff --git a/lib/blocs/stories/stories_event.dart b/lib/blocs/stories/stories_event.dart index 8a2663eb..b11ddb15 100644 --- a/lib/blocs/stories/stories_event.dart +++ b/lib/blocs/stories/stories_event.dart @@ -104,6 +104,15 @@ class StoriesEnterOfflineMode extends StoriesEvent { List get props => []; } +class UpdateMaxOfflineStoriesCount extends StoriesEvent { + UpdateMaxOfflineStoriesCount({required this.count}); + + final MaxOfflineStoriesCount count; + + @override + List get props => [count]; +} + class StoryLoaded extends StoriesEvent { StoryLoaded({required this.story, required this.type}); diff --git a/lib/blocs/stories/stories_state.dart b/lib/blocs/stories/stories_state.dart index 7b237d6a..9d33dfdf 100644 --- a/lib/blocs/stories/stories_state.dart +++ b/lib/blocs/stories/stories_state.dart @@ -19,6 +19,7 @@ class StoriesState extends Equatable { required this.downloadStatus, required this.storiesDownloaded, required this.storiesToBeDownloaded, + required this.maxOfflineStoriesCount, required this.dataSource, }); @@ -56,6 +57,7 @@ class StoriesState extends Equatable { readStoriesIds = const {}, storiesDownloaded = 0, storiesToBeDownloaded = 0, + maxOfflineStoriesCount = null, dataSource = null; final Map> storiesByType; @@ -67,6 +69,7 @@ class StoriesState extends Equatable { final bool isOfflineReading; final int storiesDownloaded; final int storiesToBeDownloaded; + final MaxOfflineStoriesCount? maxOfflineStoriesCount; final HackerNewsDataSource? dataSource; StoriesState copyWith({ @@ -79,6 +82,7 @@ class StoriesState extends Equatable { bool? isOfflineReading, int? storiesDownloaded, int? storiesToBeDownloaded, + MaxOfflineStoriesCount? maxOfflineStoriesCount, HackerNewsDataSource? dataSource, }) { return StoriesState( @@ -93,6 +97,8 @@ class StoriesState extends Equatable { storiesToBeDownloaded: storiesToBeDownloaded ?? this.storiesToBeDownloaded, dataSource: dataSource ?? this.dataSource, + maxOfflineStoriesCount: + maxOfflineStoriesCount ?? this.maxOfflineStoriesCount, ); } @@ -182,5 +188,6 @@ class StoriesState extends Equatable { storiesDownloaded, storiesToBeDownloaded, dataSource, + maxOfflineStoriesCount, ]; } diff --git a/lib/main.dart b/lib/main.dart index 0637eac6..60e06e29 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -28,15 +28,6 @@ import 'package:rxdart/rxdart.dart' show BehaviorSubject; import 'package:visibility_detector/visibility_detector.dart'; import 'package:workmanager/workmanager.dart'; -class MyHttpOverrides extends HttpOverrides { - @override - HttpClient createHttpClient(SecurityContext? context) { - return super.createHttpClient(context) - ..badCertificateCallback = - (X509Certificate cert, String host, int port) => true; - } -} - // For receiving payload event from local notifications. final BehaviorSubject selectNotificationSubject = BehaviorSubject(); @@ -52,7 +43,6 @@ void notificationReceiver(NotificationResponse details) => Future main({bool testing = false}) async { WidgetsFlutterBinding.ensureInitialized(); - HttpOverrides.global = MyHttpOverrides(); await initializeDateFormatting(Platform.localeName); diff --git a/lib/models/max_offline_stories_count.dart b/lib/models/max_offline_stories_count.dart new file mode 100644 index 00000000..739cf528 --- /dev/null +++ b/lib/models/max_offline_stories_count.dart @@ -0,0 +1,12 @@ +enum MaxOfflineStoriesCount { + ten(20, '20'), + fifty(50, '50'), + hundred(100, '100'), + twoHundred(200, '200'), + all(null, 'All'); + + const MaxOfflineStoriesCount(this.count, this.label); + + final int? count; + final String label; +} diff --git a/lib/models/models.dart b/lib/models/models.dart index c8e98d52..8075cb39 100644 --- a/lib/models/models.dart +++ b/lib/models/models.dart @@ -8,6 +8,7 @@ export 'font.dart'; export 'font_size.dart'; export 'hacker_news_data_source.dart'; export 'item/item.dart'; +export 'max_offline_stories_count.dart'; export 'post_data.dart'; export 'preference.dart'; export 'search_params.dart'; diff --git a/lib/screens/profile/widgets/offline_list_tile.dart b/lib/screens/profile/widgets/offline_list_tile.dart index f347db99..d11aebe6 100644 --- a/lib/screens/profile/widgets/offline_list_tile.dart +++ b/lib/screens/profile/widgets/offline_list_tile.dart @@ -3,8 +3,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; import 'package:hacki/blocs/blocs.dart'; +import 'package:hacki/models/models.dart'; import 'package:hacki/screens/widgets/widgets.dart'; import 'package:hacki/styles/styles.dart'; +import 'package:hacki/utils/haptic_feedback_util.dart'; import 'package:wakelock_plus/wakelock_plus.dart'; class OfflineListTile extends StatelessWidget { @@ -98,37 +100,57 @@ class OfflineListTile extends StatelessWidget { .checkConnectivity() .then((List res) { if (!res.contains(ConnectivityResult.none) && context.mounted) { - showDialog( + showModalBottomSheet( context: context, - builder: (BuildContext context) => AlertDialog( - title: const Text('Download web pages as well?'), - content: const Text('It will take longer time.'), - actions: [ - TextButton( - onPressed: () => context.pop(), - child: const Text('Cancel'), - ), - TextButton( - onPressed: () => context.pop(false), - child: const Text('No'), - ), - TextButton( - onPressed: () => context.pop(true), - child: const Text('Yes'), - ), - ], - ), - ).then((bool? includeWebPage) { - if (includeWebPage != null) { - WakelockPlus.enable(); + builder: (BuildContext context) { + return BlocSelector( + selector: (StoriesState state) => + state.maxOfflineStoriesCount, + builder: ( + BuildContext c, + MaxOfflineStoriesCount? maxStories, + ) { + return SafeArea( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBoxes.pt12, + const Text( + 'How many stories do you want to download?', + ), + for (final MaxOfflineStoriesCount count + in MaxOfflineStoriesCount.values) + RadioListTile( + value: count, + groupValue: state.maxOfflineStoriesCount, + title: Text(count.label), + onChanged: (MaxOfflineStoriesCount? val) { + HapticFeedbackUtil.selection(); - if (context.mounted) { - context.read().add( - StoriesDownload(includingWebPage: includeWebPage), - ); - } - } - }); + if (val != null) { + context.pop(); + final StoriesBloc storiesBloc = + context.read() + ..add( + UpdateMaxOfflineStoriesCount( + count: val, + ), + ); + showConfirmationDialog( + context, + storiesBloc, + ); + } + }, + ), + ], + ), + ); + }, + ); + }, + ); } }); } @@ -137,4 +159,38 @@ class OfflineListTile extends StatelessWidget { }, ); } + + void showConfirmationDialog(BuildContext context, StoriesBloc storiesBloc) { + showDialog( + context: context, + builder: (BuildContext context) => AlertDialog( + title: const Text('Download web pages as well?'), + content: const Text('It will take longer time.'), + actions: [ + TextButton( + onPressed: () => context.pop(), + child: const Text('Cancel'), + ), + TextButton( + onPressed: () => context.pop(false), + child: const Text('No'), + ), + TextButton( + onPressed: () => context.pop(true), + child: const Text('Yes'), + ), + ], + ), + ).then((bool? includeWebPage) { + if (includeWebPage != null) { + WakelockPlus.enable(); + + storiesBloc.add( + StoriesDownload( + includingWebPage: includeWebPage, + ), + ); + } + }); + } } diff --git a/lib/styles/sized_boxes.dart b/lib/styles/sized_boxes.dart new file mode 100644 index 00000000..02c4d848 --- /dev/null +++ b/lib/styles/sized_boxes.dart @@ -0,0 +1,49 @@ +import 'package:flutter/material.dart'; +import 'package:hacki/styles/dimens.dart'; + +final class SizedBoxes { + static const SizedBox pt2 = SizedBox( + height: Dimens.pt2, + width: Dimens.pt2, + ); + static const SizedBox pt4 = SizedBox( + height: Dimens.pt4, + width: Dimens.pt4, + ); + static const SizedBox pt6 = SizedBox( + height: Dimens.pt6, + width: Dimens.pt6, + ); + static const SizedBox pt8 = SizedBox( + height: Dimens.pt8, + width: Dimens.pt8, + ); + static const SizedBox pt12 = SizedBox( + height: Dimens.pt12, + width: Dimens.pt12, + ); + static const SizedBox pt16 = SizedBox( + height: Dimens.pt16, + width: Dimens.pt16, + ); + static const SizedBox pt18 = SizedBox( + height: Dimens.pt18, + width: Dimens.pt18, + ); + static const SizedBox pt20 = SizedBox( + height: Dimens.pt20, + width: Dimens.pt20, + ); + static const SizedBox pt24 = SizedBox( + height: Dimens.pt24, + width: Dimens.pt24, + ); + static const SizedBox pt36 = SizedBox( + height: Dimens.pt36, + width: Dimens.pt36, + ); + static const SizedBox pt48 = SizedBox( + height: Dimens.pt48, + width: Dimens.pt48, + ); +} diff --git a/lib/styles/styles.dart b/lib/styles/styles.dart index 3eff279a..dfb0d3aa 100644 --- a/lib/styles/styles.dart +++ b/lib/styles/styles.dart @@ -1,4 +1,5 @@ export 'dimens.dart'; export 'media_query.dart'; export 'palette.dart'; +export 'sized_boxes.dart'; export 'theme.dart';