Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 23 additions & 4 deletions lib/blocs/stories/stories_bloc.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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<StoriesEvent, StoriesState> with Loggable {
Expand Down Expand Up @@ -55,6 +54,7 @@ class StoriesBloc extends Bloc<StoriesEvent, StoriesState> with Loggable {
on<StoriesEnterOfflineMode>(onEnterOfflineMode);
on<StoriesExitOfflineMode>(onExitOfflineMode);
on<ClearAllReadStories>(onClearAllReadStories);
on<UpdateMaxOfflineStoriesCount>(onUpdateMaxOfflineStoriesCount);

_preferenceSubscription = _preferenceCubit.stream
.distinct((PreferenceState lhs, PreferenceState rhs) {
Expand Down Expand Up @@ -350,12 +350,16 @@ class StoriesBloc extends Bloc<StoriesEvent, StoriesState> with Loggable {
await _offlineRepository.deleteAllComments();
await _offlineRepository.deleteAllWebPages();

final Set<int> prioritizedIds = <int>{};
List<int> prioritizedIds = <int>[];

/// Prioritizing all types of stories except StoryType.latest since
/// new stories tend to have less or no comment at all.
final List<StoryType> prioritizedTypes = <StoryType>[...StoryType.values]
..remove(StoryType.latest);
final List<StoryType> prioritizedTypes = <StoryType>[
StoryType.top,
StoryType.best,
StoryType.ask,
StoryType.show,
];

for (final StoryType type in prioritizedTypes) {
final List<int> ids =
Expand All @@ -364,6 +368,14 @@ class StoriesBloc extends Bloc<StoriesEvent, StoriesState> with Loggable {
prioritizedIds.addAll(ids);
}

prioritizedIds = prioritizedIds.toSet().toList().sublist(
0,
min(
state.maxOfflineStoriesCount?.count ?? prioritizedIds.length,
prioritizedIds.length,
),
);

emit(
state.copyWith(
storiesDownloaded: 0,
Expand Down Expand Up @@ -539,6 +551,13 @@ class StoriesBloc extends Bloc<StoriesEvent, StoriesState> with Loggable {
add(StoriesInitialize());
}

Future<void> onUpdateMaxOfflineStoriesCount(
UpdateMaxOfflineStoriesCount event,
Emitter<StoriesState> emit,
) async {
emit(state.copyWith(maxOfflineStoriesCount: event.count));
}

Future<void> onStoryRead(
StoryRead event,
Emitter<StoriesState> emit,
Expand Down
9 changes: 9 additions & 0 deletions lib/blocs/stories/stories_event.dart
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,15 @@ class StoriesEnterOfflineMode extends StoriesEvent {
List<Object?> get props => <Object?>[];
}

class UpdateMaxOfflineStoriesCount extends StoriesEvent {
UpdateMaxOfflineStoriesCount({required this.count});

final MaxOfflineStoriesCount count;

@override
List<Object?> get props => <Object?>[count];
}

class StoryLoaded extends StoriesEvent {
StoryLoaded({required this.story, required this.type});

Expand Down
7 changes: 7 additions & 0 deletions lib/blocs/stories/stories_state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class StoriesState extends Equatable {
required this.downloadStatus,
required this.storiesDownloaded,
required this.storiesToBeDownloaded,
required this.maxOfflineStoriesCount,
required this.dataSource,
});

Expand Down Expand Up @@ -56,6 +57,7 @@ class StoriesState extends Equatable {
readStoriesIds = const <int>{},
storiesDownloaded = 0,
storiesToBeDownloaded = 0,
maxOfflineStoriesCount = null,
dataSource = null;

final Map<StoryType, List<Story>> storiesByType;
Expand All @@ -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({
Expand All @@ -79,6 +82,7 @@ class StoriesState extends Equatable {
bool? isOfflineReading,
int? storiesDownloaded,
int? storiesToBeDownloaded,
MaxOfflineStoriesCount? maxOfflineStoriesCount,
HackerNewsDataSource? dataSource,
}) {
return StoriesState(
Expand All @@ -93,6 +97,8 @@ class StoriesState extends Equatable {
storiesToBeDownloaded:
storiesToBeDownloaded ?? this.storiesToBeDownloaded,
dataSource: dataSource ?? this.dataSource,
maxOfflineStoriesCount:
maxOfflineStoriesCount ?? this.maxOfflineStoriesCount,
);
}

Expand Down Expand Up @@ -182,5 +188,6 @@ class StoriesState extends Equatable {
storiesDownloaded,
storiesToBeDownloaded,
dataSource,
maxOfflineStoriesCount,
];
}
10 changes: 0 additions & 10 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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<String?> selectNotificationSubject =
BehaviorSubject<String?>();
Expand All @@ -52,7 +43,6 @@ void notificationReceiver(NotificationResponse details) =>

Future<void> main({bool testing = false}) async {
WidgetsFlutterBinding.ensureInitialized();
HttpOverrides.global = MyHttpOverrides();

await initializeDateFormatting(Platform.localeName);

Expand Down
12 changes: 12 additions & 0 deletions lib/models/max_offline_stories_count.dart
Original file line number Diff line number Diff line change
@@ -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;
}
1 change: 1 addition & 0 deletions lib/models/models.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
114 changes: 85 additions & 29 deletions lib/screens/profile/widgets/offline_list_tile.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -98,37 +100,57 @@ class OfflineListTile extends StatelessWidget {
.checkConnectivity()
.then((List<ConnectivityResult> res) {
if (!res.contains(ConnectivityResult.none) && context.mounted) {
showDialog<bool>(
showModalBottomSheet<void>(
context: context,
builder: (BuildContext context) => AlertDialog(
title: const Text('Download web pages as well?'),
content: const Text('It will take longer time.'),
actions: <Widget>[
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<StoriesBloc, StoriesState,
MaxOfflineStoriesCount?>(
selector: (StoriesState state) =>
state.maxOfflineStoriesCount,
builder: (
BuildContext c,
MaxOfflineStoriesCount? maxStories,
) {
return SafeArea(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
SizedBoxes.pt12,
const Text(
'How many stories do you want to download?',
),
for (final MaxOfflineStoriesCount count
in MaxOfflineStoriesCount.values)
RadioListTile<MaxOfflineStoriesCount>(
value: count,
groupValue: state.maxOfflineStoriesCount,
title: Text(count.label),
onChanged: (MaxOfflineStoriesCount? val) {
HapticFeedbackUtil.selection();

if (context.mounted) {
context.read<StoriesBloc>().add(
StoriesDownload(includingWebPage: includeWebPage),
);
}
}
});
if (val != null) {
context.pop();
final StoriesBloc storiesBloc =
context.read<StoriesBloc>()
..add(
UpdateMaxOfflineStoriesCount(
count: val,
),
);
showConfirmationDialog(
context,
storiesBloc,
);
}
},
),
],
),
);
},
);
},
);
}
});
}
Expand All @@ -137,4 +159,38 @@ class OfflineListTile extends StatelessWidget {
},
);
}

void showConfirmationDialog(BuildContext context, StoriesBloc storiesBloc) {
showDialog<bool>(
context: context,
builder: (BuildContext context) => AlertDialog(
title: const Text('Download web pages as well?'),
content: const Text('It will take longer time.'),
actions: <Widget>[
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,
),
);
}
});
}
}
49 changes: 49 additions & 0 deletions lib/styles/sized_boxes.dart
Original file line number Diff line number Diff line change
@@ -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,
);
}
1 change: 1 addition & 0 deletions lib/styles/styles.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export 'dimens.dart';
export 'media_query.dart';
export 'palette.dart';
export 'sized_boxes.dart';
export 'theme.dart';