Compare commits

..

1 Commits

Author SHA1 Message Date
f0d6cac3fd v0.2.24 (#61)
* bumped version.

* improved collapse.

* improved download speed.

* improved err handling.

* improved error handling.

* improved error handling.

* improved logging.

* improved logging.

* bumped version.
2022-06-27 00:47:22 -07:00
49 changed files with 281 additions and 266 deletions

View File

@ -1 +0,0 @@
- Bugfixes.

View File

@ -1 +0,0 @@
- Updates to UI.

View File

@ -1 +0,0 @@
- Updates to UI.

View File

@ -1 +0,0 @@
- Updates to UI.

View File

@ -1 +0,0 @@
- Tapping on comments in notification and history screen will lead you directly to the comment.

View File

@ -1,3 +0,0 @@
- Tapping on comment in notification or history screen will now lead you directly to the comment.
- Fixed the bug where reply box cannot be expanded in editing mode.
- Fixed inconsistent font size in history screen.

View File

@ -1 +0,0 @@
- Added offline mode.

View File

@ -1,2 +0,0 @@
- Added offline mode.
- Bugfixes.

View File

@ -1,2 +0,0 @@
- Added offline mode.
- Bugfixes.

View File

@ -1,2 +0,0 @@
- Added offline mode.
- Bugfixes.

View File

@ -1,3 +0,0 @@
- Pick up where you left off.
- Swipe left on comment tile to view its parents without scrolling all the way up.
- Huge performance boost.

View File

@ -1,3 +0,0 @@
- Pick up where you left off.
- Swipe left on comment tile to view its parents without scrolling all the way up.
- Huge performance boost.

View File

@ -1,3 +0,0 @@
- Pick up where you left off.
- Swipe left on comment tile to view its parents without scrolling all the way up.
- Huge performance boost.

View File

@ -1,4 +0,0 @@
- You can now participate in polls.
- Pick up where you left off.
- Swipe left on comment tile to view its parents without scrolling all the way up.
- Huge performance boost.

View File

@ -1,4 +0,0 @@
- You can now participate in polls.
- Pick up where you left off.
- Swipe left on comment tile to view its parents without scrolling all the way up.
- Huge performance boost.

View File

@ -1,4 +0,0 @@
- You can now participate in polls.
- Pick up where you left off.
- Swipe left on comment tile to view its parents without scrolling all the way up.
- Huge performance boost.

View File

@ -1,4 +0,0 @@
- You can now participate in polls.
- Pick up where you left off.
- Swipe left on comment tile to view its parents without scrolling all the way up.
- Huge performance boost.

View File

@ -1,4 +0,0 @@
- You can now participate in polls.
- Pick up where you left off.
- Swipe left on comment tile to view its parents without scrolling all the way up.
- Huge performance boost.

View File

@ -1,4 +0,0 @@
- You can now participate in polls.
- Pick up where you left off.
- Swipe left on comment tile to view its parents without scrolling all the way up.
- Huge performance boost.

View File

@ -1,4 +0,0 @@
- You can now participate in polls.
- Pick up where you left off.
- Swipe left on comment tile to view its parents without scrolling all the way up.
- Huge performance boost.

View File

@ -1,4 +0,0 @@
- You can now participate in polls.
- Pick up where you left off.
- Swipe left on comment tile to view its parents without scrolling all the way up.
- Huge performance boost.

View File

@ -1,4 +0,0 @@
- You can now participate in polls.
- Pick up where you left off.
- Swipe left on comment tile to view its parents without scrolling all the way up.
- Huge performance boost.

View File

@ -1,4 +0,0 @@
- You can now participate in polls.
- Pick up where you left off.
- Swipe left on comment tile to view its parents without scrolling all the way up.
- Huge performance boost.

View File

@ -1,4 +0,0 @@
- You can now participate in polls.
- Pick up where you left off.
- Swipe left on comment tile to view its parents without scrolling all the way up.
- Huge performance boost.

View File

@ -1,4 +0,0 @@
- You can now participate in polls.
- Pick up where you left off.
- Swipe left on comment tile to view its parents without scrolling all the way up.
- Huge performance boost.

View File

@ -1,4 +0,0 @@
- You can now participate in polls.
- Pick up where you left off.
- Swipe left on comment tile to view its parents without scrolling all the way up.
- Huge performance boost.

View File

@ -1,4 +0,0 @@
- You can now participate in polls.
- Pick up where you left off.
- Swipe left on comment tile to view its parents without scrolling all the way up.
- Huge performance boost.

View File

@ -1,4 +0,0 @@
- You can now participate in polls.
- Pick up where you left off.
- Swipe left on comment tile to view its parents without scrolling all the way up.
- Huge performance boost.

View File

@ -1,5 +0,0 @@
- You can now participate in polls.
- Pick up where you left off.
- Swipe left on comment tile to view its parents without scrolling all the way up.
- Huge performance boost.
- Bugfixes.

View File

@ -1,6 +0,0 @@
- You can now add filters for searching.
- You can now participate in polls.
- Pick up where you left off.
- Swipe left on comment tile to view its parents without scrolling all the way up.
- Huge performance boost.
- Bugfixes.

View File

@ -1,6 +0,0 @@
- You can now add filters for searching.
- You can now participate in polls.
- Pick up where you left off.
- Swipe left on comment tile to view its parents without scrolling all the way up.
- Huge performance boost.
- Bugfixes.

View File

@ -1,6 +0,0 @@
- You can now add filters for searching.
- You can now participate in polls.
- Pick up where you left off.
- Swipe left on comment tile to view its parents without scrolling all the way up.
- Huge performance boost.
- Bugfixes.

View File

@ -1,6 +0,0 @@
- You can now add filters for searching.
- You can now participate in polls.
- Pick up where you left off.
- Swipe left on comment tile to view its parents without scrolling all the way up.
- Huge performance boost.
- Bugfixes.

View File

@ -1,7 +0,0 @@
- You can share links.
- You can now add filters for searching.
- You can now participate in polls.
- Pick up where you left off.
- Swipe left on comment tile to view its parents without scrolling all the way up.
- Huge performance boost.
- Bugfixes.

View File

@ -0,0 +1,2 @@
- Offline mode now includes web pages.
- You can now sort comments in story screen.

View File

@ -568,7 +568,7 @@
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 3;
DEVELOPMENT_TEAM = QMWX3X2NF7; DEVELOPMENT_TEAM = QMWX3X2NF7;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_FILE = Runner/Info.plist;
@ -577,7 +577,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 0.2.23; MARKETING_VERSION = 0.2.24;
PRODUCT_BUNDLE_IDENTIFIER = com.jiaqi.hacki; PRODUCT_BUNDLE_IDENTIFIER = com.jiaqi.hacki;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
@ -705,7 +705,7 @@
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 3;
DEVELOPMENT_TEAM = QMWX3X2NF7; DEVELOPMENT_TEAM = QMWX3X2NF7;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_FILE = Runner/Info.plist;
@ -714,7 +714,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 0.2.23; MARKETING_VERSION = 0.2.24;
PRODUCT_BUNDLE_IDENTIFIER = com.jiaqi.hacki; PRODUCT_BUNDLE_IDENTIFIER = com.jiaqi.hacki;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
@ -736,7 +736,7 @@
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 3;
DEVELOPMENT_TEAM = QMWX3X2NF7; DEVELOPMENT_TEAM = QMWX3X2NF7;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_FILE = Runner/Info.plist;
@ -745,7 +745,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 0.2.23; MARKETING_VERSION = 0.2.24;
PRODUCT_BUNDLE_IDENTIFIER = com.jiaqi.hacki; PRODUCT_BUNDLE_IDENTIFIER = com.jiaqi.hacki;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";

View File

@ -7,7 +7,9 @@ import 'package:hacki/config/locator.dart';
import 'package:hacki/cubits/cubits.dart'; import 'package:hacki/cubits/cubits.dart';
import 'package:hacki/models/models.dart'; import 'package:hacki/models/models.dart';
import 'package:hacki/repositories/repositories.dart'; import 'package:hacki/repositories/repositories.dart';
import 'package:logger/logger.dart';
import 'package:responsive_builder/responsive_builder.dart'; import 'package:responsive_builder/responsive_builder.dart';
import 'package:rxdart/rxdart.dart';
part 'stories_event.dart'; part 'stories_event.dart';
part 'stories_state.dart'; part 'stories_state.dart';
@ -281,12 +283,6 @@ class StoriesBloc extends Bloc<StoriesEvent, StoriesState> {
includingWebPage: event.includingWebPage, includingWebPage: event.includingWebPage,
isPrioritized: false, isPrioritized: false,
); );
emit(
state.copyWith(
downloadStatus: StoriesDownloadStatus.finished,
),
);
} catch (_) { } catch (_) {
emit( emit(
state.copyWith( state.copyWith(
@ -321,41 +317,50 @@ class StoriesBloc extends Bloc<StoriesEvent, StoriesState> {
await _cacheRepository.cacheStory(story: story); await _cacheRepository.cacheStory(story: story);
if (story.url.isNotEmpty && includingWebPage) { if (story.url.isNotEmpty && includingWebPage) {
locator.get<Logger>().i('downloading ${story.url}');
await _cacheRepository.cacheUrl(url: story.url); await _cacheRepository.cacheUrl(url: story.url);
} }
final Completer<void> completer = Completer<void>();
_storiesRepository _storiesRepository
.fetchAllChildrenComments(ids: story.kids) .fetchAllChildrenComments(ids: story.kids)
.listen((Comment? comment) async { .whereType<Comment>()
if (comment != null) { .listen(
await _cacheRepository.cacheComment(comment: comment); (Comment comment) => unawaited(
} _cacheRepository.cacheComment(comment: comment),
}).onDone(() { ),
completer.complete(); )
add(StoryDownloaded(skipped: false)); .onDone(() => add(StoryDownloaded(skipped: false)));
});
await completer.future;
} }
} }
void onStoryDownloaded(StoryDownloaded event, Emitter<StoriesState> emit) { void onStoryDownloaded(StoryDownloaded event, Emitter<StoriesState> emit) {
if (event.skipped) { if (event.skipped) {
final int updatedStoriesToBeDownloaded = state.storiesToBeDownloaded - 1;
emit( emit(
state.copyWith( state.copyWith(
storiesToBeDownloaded: state.storiesToBeDownloaded - 1, storiesToBeDownloaded: updatedStoriesToBeDownloaded,
downloadStatus:
state.storiesDownloaded == updatedStoriesToBeDownloaded
? StoriesDownloadStatus.finished
: null,
), ),
); );
} else { } else {
final int updatedStoriesDownloaded = state.storiesDownloaded + 1; final int updatedStoriesDownloaded = state.storiesDownloaded + 1;
final int updatedStoriesToBeDownloaded =
updatedStoriesDownloaded > state.storiesToBeDownloaded
? state.storiesToBeDownloaded + 1
: state.storiesToBeDownloaded;
emit( emit(
state.copyWith( state.copyWith(
storiesDownloaded: updatedStoriesDownloaded, storiesDownloaded: updatedStoriesDownloaded,
storiesToBeDownloaded: storiesToBeDownloaded: updatedStoriesToBeDownloaded,
updatedStoriesDownloaded > state.storiesToBeDownloaded downloadStatus:
? state.storiesToBeDownloaded + 1 updatedStoriesDownloaded == updatedStoriesToBeDownloaded
: state.storiesToBeDownloaded, ? StoriesDownloadStatus.finished
: null,
), ),
); );
} }

View File

@ -0,0 +1,31 @@
import 'package:logger/logger.dart';
class CustomLogFilter extends LogFilter {
@override
// ignore: overridden_fields
Level? level = Level.verbose;
/// The minimal level allowed in production.
static const Level _minimalLevel = Level.info;
@override
bool shouldLog(LogEvent event) {
bool shouldLog = false;
if (event.level.index >= _minimalLevel.index) {
return true;
}
assert(
() {
if (event.level.index >= level!.index) {
shouldLog = true;
}
return true;
}(),
'',
);
return shouldLog;
}
}

View File

@ -1,5 +1,5 @@
import 'package:flutter/material.dart';
import 'package:get_it/get_it.dart'; import 'package:get_it/get_it.dart';
import 'package:hacki/config/custom_log_filter.dart';
import 'package:hacki/repositories/repositories.dart'; import 'package:hacki/repositories/repositories.dart';
import 'package:hacki/services/services.dart'; import 'package:hacki/services/services.dart';
import 'package:logger/logger.dart'; import 'package:logger/logger.dart';
@ -19,8 +19,5 @@ Future<void> setUpLocator() async {
..registerSingleton<CacheRepository>(CacheRepository()) ..registerSingleton<CacheRepository>(CacheRepository())
..registerSingleton<CacheService>(CacheService()) ..registerSingleton<CacheService>(CacheService())
..registerSingleton<LocalNotification>(LocalNotification()) ..registerSingleton<LocalNotification>(LocalNotification())
..registerSingleton<Logger>(Logger()) ..registerSingleton<Logger>(Logger(filter: CustomLogFilter()));
..registerSingleton<RouteObserver<ModalRoute<dynamic>>>(
RouteObserver<ModalRoute<dynamic>>(),
);
} }

View File

@ -221,9 +221,6 @@ class HackiApp extends StatelessWidget {
debugShowCheckedModeBanner: false, debugShowCheckedModeBanner: false,
theme: useTrueDark ? trueDarkTheme : theme, theme: useTrueDark ? trueDarkTheme : theme,
navigatorKey: navigatorKey, navigatorKey: navigatorKey,
navigatorObservers: <NavigatorObserver>[
locator.get<RouteObserver<ModalRoute<dynamic>>>(),
],
onGenerateRoute: CustomRouter.onGenerateRoute, onGenerateRoute: CustomRouter.onGenerateRoute,
initialRoute: HomeScreen.routeName, initialRoute: HomeScreen.routeName,
), ),

View File

@ -5,6 +5,7 @@ import 'package:hacki/config/locator.dart';
import 'package:hacki/models/models.dart'; import 'package:hacki/models/models.dart';
import 'package:hacki/repositories/postable_repository.dart'; import 'package:hacki/repositories/postable_repository.dart';
import 'package:hacki/repositories/repositories.dart'; import 'package:hacki/repositories/repositories.dart';
import 'package:logger/logger.dart';
class AuthRepository extends PostableRepository { class AuthRepository extends PostableRepository {
AuthRepository({ AuthRepository({
@ -44,6 +45,7 @@ class AuthRepository extends PostableRepository {
password: password, password: password,
); );
} catch (_) { } catch (_) {
locator.get<Logger>().e(_);
return false; return false;
} }
} }

View File

@ -1,7 +1,9 @@
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:hacki/config/locator.dart';
import 'package:hacki/models/models.dart'; import 'package:hacki/models/models.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:http/http.dart'; import 'package:http/http.dart';
import 'package:logger/logger.dart';
/// [CacheRepository] is for storing stories and comments for offline reading. /// [CacheRepository] is for storing stories and comments for offline reading.
/// It's using [Hive] as its database which is being stored in temp directory. /// It's using [Hive] as its database which is being stored in temp directory.
@ -9,12 +11,12 @@ class CacheRepository {
CacheRepository({ CacheRepository({
Future<Box<List<int>>>? storyIdBox, Future<Box<List<int>>>? storyIdBox,
Future<Box<Map<dynamic, dynamic>>>? storyBox, Future<Box<Map<dynamic, dynamic>>>? storyBox,
Future<Box<String>>? webPageBox, Future<LazyBox<String>>? webPageBox,
Future<LazyBox<Map<dynamic, dynamic>>>? commentBox, Future<LazyBox<Map<dynamic, dynamic>>>? commentBox,
}) : _storyIdBox = storyIdBox ?? Hive.openBox<List<int>>(_storyIdBoxName), }) : _storyIdBox = storyIdBox ?? Hive.openBox<List<int>>(_storyIdBoxName),
_storyBox = _storyBox =
storyBox ?? Hive.openBox<Map<dynamic, dynamic>>(_storyBoxName), storyBox ?? Hive.openBox<Map<dynamic, dynamic>>(_storyBoxName),
_webPageBox = webPageBox ?? Hive.openBox<String>(_webPageBoxName), _webPageBox = webPageBox ?? Hive.openLazyBox<String>(_webPageBoxName),
_commentBox = commentBox ?? _commentBox = commentBox ??
Hive.openLazyBox<Map<dynamic, dynamic>>(_commentBoxName); Hive.openLazyBox<Map<dynamic, dynamic>>(_commentBoxName);
@ -25,7 +27,7 @@ class CacheRepository {
final Future<Box<List<int>>> _storyIdBox; final Future<Box<List<int>>> _storyIdBox;
final Future<Box<Map<dynamic, dynamic>>> _storyBox; final Future<Box<Map<dynamic, dynamic>>> _storyBox;
final Future<LazyBox<Map<dynamic, dynamic>>> _commentBox; final Future<LazyBox<Map<dynamic, dynamic>>> _commentBox;
final Future<Box<String>> _webPageBox; final Future<LazyBox<String>> _webPageBox;
Future<bool> get hasCachedStories => Future<bool> get hasCachedStories =>
_storyBox.then((Box<Map<dynamic, dynamic>> box) => box.isNotEmpty); _storyBox.then((Box<Map<dynamic, dynamic>> box) => box.isNotEmpty);
@ -34,39 +36,92 @@ class CacheRepository {
required StoryType of, required StoryType of,
required List<int> ids, required List<int> ids,
}) async { }) async {
final Box<List<int>> box = await _storyIdBox; late final Box<List<int>> box;
try {
box = await _storyIdBox;
} catch (_) {
locator.get<Logger>().e(_);
await Hive.deleteBoxFromDisk(_storyIdBoxName);
box = await _storyIdBox;
}
return box.put(of.name, ids); return box.put(of.name, ids);
} }
Future<void> cacheStory({required Story story}) async { Future<void> cacheStory({required Story story}) async {
final Box<Map<dynamic, dynamic>> box = await _storyBox; late final Box<Map<dynamic, dynamic>> box;
try {
box = await _storyBox;
} catch (_) {
locator.get<Logger>().e(_);
await Hive.deleteBoxFromDisk(_storyBoxName);
box = await _storyBox;
}
return box.put(story.id.toString(), story.toJson()); return box.put(story.id.toString(), story.toJson());
} }
Future<void> cacheUrl({required String url}) async { Future<void> cacheUrl({required String url}) async {
final Box<String> box = await _webPageBox; late final LazyBox<String> box;
final String html = await compute(downloadWebPage, url);
try {
box = await _webPageBox;
} catch (_) {
locator.get<Logger>().e(_);
await Hive.deleteBoxFromDisk(_webPageBoxName);
box = await _webPageBox;
}
final String html = await compute(_downloadWebPage, url);
return box.put(url, html); return box.put(url, html);
} }
Future<String?> getHtml({required String url}) async { Future<String?> getHtml({required String url}) async {
final Box<String> box = await _webPageBox; try {
final LazyBox<String> box = await _webPageBox;
return box.get(url); return box.get(url);
} catch (_) {
locator.get<Logger>().e(_);
await Hive.deleteBoxFromDisk(_webPageBoxName);
return null;
}
} }
Future<bool> hasCachedWebPage({required String url}) async { Future<bool> hasCachedWebPage({required String url}) async {
final Box<String> box = await _webPageBox; try {
final LazyBox<String> box = await _webPageBox;
return box.containsKey(url); return box.containsKey(url);
} catch (_) {
locator.get<Logger>().e(_);
await Hive.deleteBoxFromDisk(_webPageBoxName);
return false;
}
} }
Future<List<int>> getCachedStoryIds({required StoryType of}) async { Future<List<int>> getCachedStoryIds({required StoryType of}) async {
try {
final Box<List<int>> box = await _storyIdBox; final Box<List<int>> box = await _storyIdBox;
final List<int>? ids = box.get(of.name); final List<int>? ids = box.get(of.name);
return ids ?? <int>[]; return ids ?? <int>[];
} catch (_) {
locator.get<Logger>().e(_);
await Hive.deleteBoxFromDisk(_storyIdBoxName);
return <int>[];
}
} }
Stream<Story> getCachedStoriesStream({required List<int> ids}) async* { Stream<Story> getCachedStoriesStream({required List<int> ids}) async* {
final Box<Map<dynamic, dynamic>> box = await _storyBox; late final Box<Map<dynamic, dynamic>> box;
try {
box = await _storyBox;
} catch (_) {
locator.get<Logger>().e(_);
await Hive.deleteBoxFromDisk(_storyBoxName);
return;
}
for (final int id in ids) { for (final int id in ids) {
final Map<dynamic, dynamic>? json = box.get(id.toString()); final Map<dynamic, dynamic>? json = box.get(id.toString());
@ -83,7 +138,16 @@ class CacheRepository {
} }
Future<Story?> getCachedStory({required int id}) async { Future<Story?> getCachedStory({required int id}) async {
final Box<Map<dynamic, dynamic>> box = await _storyBox; late final Box<Map<dynamic, dynamic>> box;
try {
box = await _storyBox;
} catch (_) {
locator.get<Logger>().e(_);
await Hive.deleteBoxFromDisk(_storyBoxName);
return null;
}
final Map<dynamic, dynamic>? json = box.get(id.toString()); final Map<dynamic, dynamic>? json = box.get(id.toString());
if (json == null) { if (json == null) {
return null; return null;
@ -93,11 +157,21 @@ class CacheRepository {
} }
Future<void> cacheComment({required Comment comment}) async { Future<void> cacheComment({required Comment comment}) async {
final LazyBox<Map<dynamic, dynamic>> box = await _commentBox; late final LazyBox<Map<dynamic, dynamic>> box;
try {
box = await _commentBox;
} catch (_) {
locator.get<Logger>().e(_);
await Hive.deleteBoxFromDisk(_commentBoxName);
box = await _commentBox;
}
return box.put(comment.id.toString(), comment.toJson()); return box.put(comment.id.toString(), comment.toJson());
} }
Future<Comment?> getCachedComment({required int id}) async { Future<Comment?> getCachedComment({required int id}) async {
try {
final LazyBox<Map<dynamic, dynamic>> box = await _commentBox; final LazyBox<Map<dynamic, dynamic>> box = await _commentBox;
final Map<dynamic, dynamic>? json = await box.get(id.toString()); final Map<dynamic, dynamic>? json = await box.get(id.toString());
if (json == null) { if (json == null) {
@ -105,6 +179,11 @@ class CacheRepository {
} }
final Comment comment = Comment.fromJson(json.cast<String, dynamic>()); final Comment comment = Comment.fromJson(json.cast<String, dynamic>());
return comment; return comment;
} catch (_) {
locator.get<Logger>().e(_);
await Hive.deleteBoxFromDisk(_commentBoxName);
return null;
}
} }
Stream<Comment> getCachedCommentsStream({ Stream<Comment> getCachedCommentsStream({
@ -127,23 +206,47 @@ class CacheRepository {
} }
Future<int> deleteAllStoryIds() async { Future<int> deleteAllStoryIds() async {
try {
final Box<List<int>> box = await _storyIdBox; final Box<List<int>> box = await _storyIdBox;
return box.clear(); return box.clear();
} catch (_) {
locator.get<Logger>().e(_);
await Hive.deleteBoxFromDisk(_storyIdBoxName);
return 0;
}
} }
Future<int> deleteAllStories() async { Future<int> deleteAllStories() async {
try {
final Box<Map<dynamic, dynamic>> box = await _storyBox; final Box<Map<dynamic, dynamic>> box = await _storyBox;
return box.clear(); return box.clear();
} catch (_) {
locator.get<Logger>().e(_);
await Hive.deleteBoxFromDisk(_storyBoxName);
return 0;
}
} }
Future<int> deleteAllComments() async { Future<int> deleteAllComments() async {
try {
final LazyBox<Map<dynamic, dynamic>> box = await _commentBox; final LazyBox<Map<dynamic, dynamic>> box = await _commentBox;
return box.clear(); return box.clear();
} catch (_) {
locator.get<Logger>().e(_);
await Hive.deleteBoxFromDisk(_commentBoxName);
return 0;
}
} }
Future<int> deleteAllWebPages() async { Future<int> deleteAllWebPages() async {
final Box<String> box = await _webPageBox; try {
final LazyBox<String> box = await _webPageBox;
return box.clear(); return box.clear();
} catch (_) {
locator.get<Logger>().e(_);
await Hive.deleteBoxFromDisk(_webPageBoxName);
return 0;
}
} }
Future<int> deleteAll() async { Future<int> deleteAll() async {
@ -153,12 +256,13 @@ class CacheRepository {
.whenComplete(deleteAllWebPages); .whenComplete(deleteAllWebPages);
} }
static Future<String> downloadWebPage(String link) async { static Future<String> _downloadWebPage(String link) async {
try { try {
final Client client = Client(); final Client client = Client();
final Uri url = Uri.parse(link); final Uri url = Uri.parse(link);
final Response response = await client.get(url); final Response response = await client.get(url);
final String body = response.body; final String body = response.body;
client.close();
return body; return body;
} catch (_) { } catch (_) {
return '''Web page not available.'''; return '''Web page not available.''';

View File

@ -2,6 +2,8 @@ import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:hacki/config/locator.dart';
import 'package:logger/logger.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:synced_shared_preferences/synced_shared_preferences.dart'; import 'package:synced_shared_preferences/synced_shared_preferences.dart';
@ -153,9 +155,13 @@ class PreferenceRepository {
aOptions: androidOptions, aOptions: androidOptions,
); );
} catch (_) { } catch (_) {
try {
await _secureStorage.deleteAll( await _secureStorage.deleteAll(
aOptions: androidOptions, aOptions: androidOptions,
); );
} catch (_) {
locator.get<Logger>().e(_);
}
rethrow; rethrow;
} }

View File

@ -46,7 +46,7 @@ class HomeScreen extends StatefulWidget {
} }
class _HomeScreenState extends State<HomeScreen> class _HomeScreenState extends State<HomeScreen>
with SingleTickerProviderStateMixin, RouteAware { with SingleTickerProviderStateMixin {
final CacheService cacheService = locator.get<CacheService>(); final CacheService cacheService = locator.get<CacheService>();
final Throttle featureDiscoveryDismissThrottle = Throttle( final Throttle featureDiscoveryDismissThrottle = Throttle(
delay: _throttleDelay, delay: _throttleDelay,
@ -61,18 +61,6 @@ class _HomeScreenState extends State<HomeScreen>
static const Duration _throttleDelay = Duration(seconds: 1); static const Duration _throttleDelay = Duration(seconds: 1);
@override
void didPopNext() {
super.didPopNext();
if (context.read<StoriesBloc>().deviceScreenType ==
DeviceScreenType.mobile) {
Future<void>.delayed(
const Duration(milliseconds: 500),
cacheService.resetCollapsedComments,
);
}
}
@override @override
void initState() { void initState() {
super.initState(); super.initState();
@ -100,23 +88,13 @@ class _HomeScreenState extends State<HomeScreen>
siriSuggestionSubject.stream.listen(onSiriSuggestionTapped); siriSuggestionSubject.stream.listen(onSiriSuggestionTapped);
} }
SchedulerBinding.instance SchedulerBinding.instance.addPostFrameCallback((_) {
..addPostFrameCallback((_) {
FeatureDiscovery.discoverFeatures( FeatureDiscovery.discoverFeatures(
context, context,
const <String>{ const <String>{
Constants.featureLogIn, Constants.featureLogIn,
}, },
); );
})
..addPostFrameCallback((_) {
final ModalRoute<dynamic>? route = ModalRoute.of(context);
if (route == null) return;
locator
.get<RouteObserver<ModalRoute<dynamic>>>()
.subscribe(this, route);
}); });
tabController = TabController(vsync: this, length: 6) tabController = TabController(vsync: this, length: 6)

View File

@ -66,12 +66,17 @@ class ItemScreen extends StatefulWidget {
static Route<dynamic> route(ItemScreenArgs args) { static Route<dynamic> route(ItemScreenArgs args) {
return MaterialPageRoute<ItemScreen>( return MaterialPageRoute<ItemScreen>(
settings: const RouteSettings(name: routeName), settings: const RouteSettings(name: routeName),
builder: (BuildContext context) => MultiBlocProvider( builder: (BuildContext context) => RepositoryProvider<CacheService>(
create: (BuildContext context) => CacheService(),
lazy: false,
child: MultiBlocProvider(
providers: <BlocProvider<dynamic>>[ providers: <BlocProvider<dynamic>>[
BlocProvider<CommentsCubit>( BlocProvider<CommentsCubit>(
create: (_) => CommentsCubit( create: (BuildContext context) => CommentsCubit(
offlineReading: context.read<StoriesBloc>().state.offlineReading, offlineReading:
context.read<StoriesBloc>().state.offlineReading,
item: args.item, item: args.item,
cacheService: context.read<CacheService>(),
)..init( )..init(
onlyShowTargetComment: args.onlyShowTargetComment, onlyShowTargetComment: args.onlyShowTargetComment,
targetParents: args.targetComments, targetParents: args.targetComments,
@ -87,6 +92,7 @@ class ItemScreen extends StatefulWidget {
parentComments: args.targetComments ?? <Comment>[], parentComments: args.targetComments ?? <Comment>[],
), ),
), ),
),
); );
} }
@ -100,13 +106,17 @@ class ItemScreen extends StatefulWidget {
return true; return true;
} }
}, },
child: RepositoryProvider<CacheService>(
create: (BuildContext context) => CacheService(),
child: MultiBlocProvider( child: MultiBlocProvider(
key: ValueKey<ItemScreenArgs>(args), key: ValueKey<ItemScreenArgs>(args),
providers: <BlocProvider<dynamic>>[ providers: <BlocProvider<dynamic>>[
BlocProvider<CommentsCubit>( BlocProvider<CommentsCubit>(
create: (BuildContext context) => CommentsCubit( create: (BuildContext context) => CommentsCubit(
offlineReading: context.read<StoriesBloc>().state.offlineReading, offlineReading:
context.read<StoriesBloc>().state.offlineReading,
item: args.item, item: args.item,
cacheService: context.read<CacheService>(),
)..init( )..init(
onlyShowTargetComment: args.onlyShowTargetComment, onlyShowTargetComment: args.onlyShowTargetComment,
targetParents: args.targetComments, targetParents: args.targetComments,
@ -123,6 +133,7 @@ class ItemScreen extends StatefulWidget {
splitViewEnabled: true, splitViewEnabled: true,
), ),
), ),
),
); );
} }

View File

@ -410,7 +410,7 @@ class _ProfileScreenState extends State<ProfileScreen>
showAboutDialog( showAboutDialog(
context: context, context: context,
applicationName: 'Hacki', applicationName: 'Hacki',
applicationVersion: 'v0.2.23', applicationVersion: 'v0.2.24',
applicationIcon: ClipRRect( applicationIcon: ClipRRect(
borderRadius: const BorderRadius.all( borderRadius: const BorderRadius.all(
Radius.circular( Radius.circular(

View File

@ -7,6 +7,7 @@ import 'package:hacki/blocs/blocs.dart';
import 'package:hacki/cubits/cubits.dart'; import 'package:hacki/cubits/cubits.dart';
import 'package:hacki/extensions/extensions.dart'; import 'package:hacki/extensions/extensions.dart';
import 'package:hacki/models/models.dart'; import 'package:hacki/models/models.dart';
import 'package:hacki/services/services.dart';
import 'package:hacki/styles/styles.dart'; import 'package:hacki/styles/styles.dart';
import 'package:hacki/utils/utils.dart'; import 'package:hacki/utils/utils.dart';
@ -43,6 +44,7 @@ class CommentTile extends StatelessWidget {
lazy: false, lazy: false,
create: (_) => CollapseCubit( create: (_) => CollapseCubit(
commentId: comment.id, commentId: comment.id,
cacheService: context.read<CacheService>(),
)..init(), )..init(),
child: BlocBuilder<CollapseCubit, CollapseState>( child: BlocBuilder<CollapseCubit, CollapseState>(
builder: (BuildContext context, CollapseState state) { builder: (BuildContext context, CollapseState state) {

View File

@ -2,11 +2,11 @@ import 'package:hacki/models/models.dart' show Comment;
import 'package:rxdart/rxdart.dart'; import 'package:rxdart/rxdart.dart';
class CacheService { class CacheService {
static final Map<int, Comment> _comments = <int, Comment>{}; final Map<int, Comment> _comments = <int, Comment>{};
static final Map<int, String> _drafts = <int, String>{}; final Map<int, String> _drafts = <int, String>{};
static final Map<int, Set<int>> _kids = <int, Set<int>>{}; final Map<int, Set<int>> _kids = <int, Set<int>>{};
static final Set<int> _collapsed = <int>{}; final Set<int> _collapsed = <int>{};
static final Map<int, Set<int>> _hidden = <int, Set<int>>{}; final Map<int, Set<int>> _hidden = <int, Set<int>>{};
final PublishSubject<Map<int, Set<int>>> _hiddenCommentsSubject = final PublishSubject<Map<int, Set<int>>> _hiddenCommentsSubject =
PublishSubject<Map<int, Set<int>>>(); PublishSubject<Map<int, Set<int>>>();

View File

@ -1,6 +1,6 @@
name: hacki name: hacki
description: A Hacker News reader. description: A Hacker News reader.
version: 0.2.23+65 version: 0.2.24+66
publish_to: none publish_to: none
environment: environment: