mirror of
https://github.com/Livinglist/Hacki.git
synced 2025-08-14 10:32:46 +08:00
Compare commits
1 Commits
Author | SHA1 | Date | |
---|---|---|---|
cff73a010b |
2
fastlane/metadata/android/en-US/changelogs/67.txt
Normal file
2
fastlane/metadata/android/en-US/changelogs/67.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
- Offline mode now includes web pages.
|
||||||
|
- You can now sort comments in story screen.
|
@ -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 = 3;
|
CURRENT_PROJECT_VERSION = 2;
|
||||||
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.24;
|
MARKETING_VERSION = 0.2.25;
|
||||||
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 = 3;
|
CURRENT_PROJECT_VERSION = 2;
|
||||||
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.24;
|
MARKETING_VERSION = 0.2.25;
|
||||||
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 = 3;
|
CURRENT_PROJECT_VERSION = 2;
|
||||||
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.24;
|
MARKETING_VERSION = 0.2.25;
|
||||||
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 = "";
|
||||||
|
@ -17,11 +17,12 @@ part 'stories_state.dart';
|
|||||||
class StoriesBloc extends Bloc<StoriesEvent, StoriesState> {
|
class StoriesBloc extends Bloc<StoriesEvent, StoriesState> {
|
||||||
StoriesBloc({
|
StoriesBloc({
|
||||||
required PreferenceCubit preferenceCubit,
|
required PreferenceCubit preferenceCubit,
|
||||||
CacheRepository? cacheRepository,
|
OfflineRepository? offlineRepository,
|
||||||
StoriesRepository? storiesRepository,
|
StoriesRepository? storiesRepository,
|
||||||
PreferenceRepository? preferenceRepository,
|
PreferenceRepository? preferenceRepository,
|
||||||
}) : _preferenceCubit = preferenceCubit,
|
}) : _preferenceCubit = preferenceCubit,
|
||||||
_cacheRepository = cacheRepository ?? locator.get<CacheRepository>(),
|
_offlineRepository =
|
||||||
|
offlineRepository ?? locator.get<OfflineRepository>(),
|
||||||
_storiesRepository =
|
_storiesRepository =
|
||||||
storiesRepository ?? locator.get<StoriesRepository>(),
|
storiesRepository ?? locator.get<StoriesRepository>(),
|
||||||
_preferenceRepository =
|
_preferenceRepository =
|
||||||
@ -41,7 +42,7 @@ class StoriesBloc extends Bloc<StoriesEvent, StoriesState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final PreferenceCubit _preferenceCubit;
|
final PreferenceCubit _preferenceCubit;
|
||||||
final CacheRepository _cacheRepository;
|
final OfflineRepository _offlineRepository;
|
||||||
final StoriesRepository _storiesRepository;
|
final StoriesRepository _storiesRepository;
|
||||||
final PreferenceRepository _preferenceRepository;
|
final PreferenceRepository _preferenceRepository;
|
||||||
DeviceScreenType? deviceScreenType;
|
DeviceScreenType? deviceScreenType;
|
||||||
@ -73,7 +74,7 @@ class StoriesBloc extends Bloc<StoriesEvent, StoriesState> {
|
|||||||
add(StoriesPageSizeChanged(pageSize: pageSize));
|
add(StoriesPageSizeChanged(pageSize: pageSize));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
final bool hasCachedStories = await _cacheRepository.hasCachedStories;
|
final bool hasCachedStories = await _offlineRepository.hasCachedStories;
|
||||||
final bool isComplexTile = _preferenceCubit.state.showComplexStoryTile;
|
final bool isComplexTile = _preferenceCubit.state.showComplexStoryTile;
|
||||||
final int pageSize = _getPageSize(isComplexTile: isComplexTile);
|
final int pageSize = _getPageSize(isComplexTile: isComplexTile);
|
||||||
emit(
|
emit(
|
||||||
@ -92,13 +93,13 @@ class StoriesBloc extends Bloc<StoriesEvent, StoriesState> {
|
|||||||
required Emitter<StoriesState> emit,
|
required Emitter<StoriesState> emit,
|
||||||
}) async {
|
}) async {
|
||||||
if (state.offlineReading) {
|
if (state.offlineReading) {
|
||||||
final List<int> ids = await _cacheRepository.getCachedStoryIds(of: of);
|
final List<int> ids = await _offlineRepository.getCachedStoryIds(of: of);
|
||||||
emit(
|
emit(
|
||||||
state
|
state
|
||||||
.copyWithStoryIdsUpdated(of: of, to: ids)
|
.copyWithStoryIdsUpdated(of: of, to: ids)
|
||||||
.copyWithCurrentPageUpdated(of: of, to: 0),
|
.copyWithCurrentPageUpdated(of: of, to: 0),
|
||||||
);
|
);
|
||||||
_cacheRepository
|
_offlineRepository
|
||||||
.getCachedStoriesStream(
|
.getCachedStoriesStream(
|
||||||
ids: ids.sublist(0, min(ids.length, state.currentPageSize)),
|
ids: ids.sublist(0, min(ids.length, state.currentPageSize)),
|
||||||
)
|
)
|
||||||
@ -169,7 +170,7 @@ class StoriesBloc extends Bloc<StoriesEvent, StoriesState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (state.offlineReading) {
|
if (state.offlineReading) {
|
||||||
_cacheRepository
|
_offlineRepository
|
||||||
.getCachedStoriesStream(
|
.getCachedStoriesStream(
|
||||||
ids: state.storyIdsByType[event.type]!.sublist(
|
ids: state.storyIdsByType[event.type]!.sublist(
|
||||||
lower,
|
lower,
|
||||||
@ -243,9 +244,9 @@ class StoriesBloc extends Bloc<StoriesEvent, StoriesState> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
await _cacheRepository.deleteAllStoryIds();
|
await _offlineRepository.deleteAllStoryIds();
|
||||||
await _cacheRepository.deleteAllStories();
|
await _offlineRepository.deleteAllStories();
|
||||||
await _cacheRepository.deleteAllComments();
|
await _offlineRepository.deleteAllComments();
|
||||||
|
|
||||||
final Set<int> prioritizedIds = <int>{};
|
final Set<int> prioritizedIds = <int>{};
|
||||||
final List<StoryType> prioritizedTypes = <StoryType>[...types]
|
final List<StoryType> prioritizedTypes = <StoryType>[...types]
|
||||||
@ -253,7 +254,7 @@ class StoriesBloc extends Bloc<StoriesEvent, StoriesState> {
|
|||||||
|
|
||||||
for (final StoryType type in prioritizedTypes) {
|
for (final StoryType type in prioritizedTypes) {
|
||||||
final List<int> ids = await _storiesRepository.fetchStoryIds(of: type);
|
final List<int> ids = await _storiesRepository.fetchStoryIds(of: type);
|
||||||
await _cacheRepository.cacheStoryIds(of: type, ids: ids);
|
await _offlineRepository.cacheStoryIds(of: type, ids: ids);
|
||||||
prioritizedIds.addAll(ids);
|
prioritizedIds.addAll(ids);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -275,7 +276,7 @@ class StoriesBloc extends Bloc<StoriesEvent, StoriesState> {
|
|||||||
final List<int> ids = await _storiesRepository.fetchStoryIds(
|
final List<int> ids = await _storiesRepository.fetchStoryIds(
|
||||||
of: StoryType.latest,
|
of: StoryType.latest,
|
||||||
);
|
);
|
||||||
await _cacheRepository.cacheStoryIds(of: StoryType.latest, ids: ids);
|
await _offlineRepository.cacheStoryIds(of: StoryType.latest, ids: ids);
|
||||||
latestIds.addAll(ids);
|
latestIds.addAll(ids);
|
||||||
|
|
||||||
await fetchAndCacheStories(
|
await fetchAndCacheStories(
|
||||||
@ -314,11 +315,11 @@ class StoriesBloc extends Bloc<StoriesEvent, StoriesState> {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
await _cacheRepository.cacheStory(story: story);
|
await _offlineRepository.cacheStory(story: story);
|
||||||
|
|
||||||
if (story.url.isNotEmpty && includingWebPage) {
|
if (story.url.isNotEmpty && includingWebPage) {
|
||||||
locator.get<Logger>().i('downloading ${story.url}');
|
locator.get<Logger>().i('downloading ${story.url}');
|
||||||
await _cacheRepository.cacheUrl(url: story.url);
|
await _offlineRepository.cacheUrl(url: story.url);
|
||||||
}
|
}
|
||||||
|
|
||||||
_storiesRepository
|
_storiesRepository
|
||||||
@ -326,7 +327,7 @@ class StoriesBloc extends Bloc<StoriesEvent, StoriesState> {
|
|||||||
.whereType<Comment>()
|
.whereType<Comment>()
|
||||||
.listen(
|
.listen(
|
||||||
(Comment comment) => unawaited(
|
(Comment comment) => unawaited(
|
||||||
_cacheRepository.cacheComment(comment: comment),
|
_offlineRepository.cacheComment(comment: comment),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.onDone(() => add(StoryDownloaded(skipped: false)));
|
.onDone(() => add(StoryDownloaded(skipped: false)));
|
||||||
@ -378,10 +379,10 @@ class StoriesBloc extends Bloc<StoriesEvent, StoriesState> {
|
|||||||
StoriesExitOffline event,
|
StoriesExitOffline event,
|
||||||
Emitter<StoriesState> emit,
|
Emitter<StoriesState> emit,
|
||||||
) async {
|
) async {
|
||||||
await _cacheRepository.deleteAllStoryIds();
|
await _offlineRepository.deleteAllStoryIds();
|
||||||
await _cacheRepository.deleteAllStories();
|
await _offlineRepository.deleteAllStories();
|
||||||
await _cacheRepository.deleteAllComments();
|
await _offlineRepository.deleteAllComments();
|
||||||
await _cacheRepository.deleteAllWebPages();
|
await _offlineRepository.deleteAllWebPages();
|
||||||
emit(state.copyWith(offlineReading: false));
|
emit(state.copyWith(offlineReading: false));
|
||||||
add(StoriesInitialize());
|
add(StoriesInitialize());
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
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/config/custom_log_filter.dart';
|
||||||
import 'package:hacki/repositories/repositories.dart';
|
import 'package:hacki/repositories/repositories.dart';
|
||||||
@ -16,8 +17,12 @@ Future<void> setUpLocator() async {
|
|||||||
..registerSingleton<AuthRepository>(AuthRepository())
|
..registerSingleton<AuthRepository>(AuthRepository())
|
||||||
..registerSingleton<PostRepository>(PostRepository())
|
..registerSingleton<PostRepository>(PostRepository())
|
||||||
..registerSingleton<SembastRepository>(SembastRepository())
|
..registerSingleton<SembastRepository>(SembastRepository())
|
||||||
..registerSingleton<CacheRepository>(CacheRepository())
|
..registerSingleton<OfflineRepository>(OfflineRepository())
|
||||||
..registerSingleton<CacheService>(CacheService())
|
..registerSingleton<DraftCache>(DraftCache())
|
||||||
|
..registerSingleton<CommentCache>(CommentCache())
|
||||||
..registerSingleton<LocalNotification>(LocalNotification())
|
..registerSingleton<LocalNotification>(LocalNotification())
|
||||||
..registerSingleton<Logger>(Logger(filter: CustomLogFilter()));
|
..registerSingleton<Logger>(Logger(filter: CustomLogFilter()))
|
||||||
|
..registerSingleton<RouteObserver<ModalRoute<dynamic>>>(
|
||||||
|
RouteObserver<ModalRoute<dynamic>>(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
@ -10,31 +10,31 @@ part 'collapse_state.dart';
|
|||||||
class CollapseCubit extends Cubit<CollapseState> {
|
class CollapseCubit extends Cubit<CollapseState> {
|
||||||
CollapseCubit({
|
CollapseCubit({
|
||||||
required int commentId,
|
required int commentId,
|
||||||
CacheService? cacheService,
|
CollapseCache? collapseCache,
|
||||||
}) : _commentId = commentId,
|
}) : _commentId = commentId,
|
||||||
_cacheService = cacheService ?? locator.get<CacheService>(),
|
_collapseCache = collapseCache ?? locator.get<CollapseCache>(),
|
||||||
super(const CollapseState.init());
|
super(const CollapseState.init());
|
||||||
|
|
||||||
final int _commentId;
|
final int _commentId;
|
||||||
final CacheService _cacheService;
|
final CollapseCache _collapseCache;
|
||||||
late final StreamSubscription<Map<int, Set<int>>> _streamSubscription;
|
late final StreamSubscription<Map<int, Set<int>>> _streamSubscription;
|
||||||
|
|
||||||
void init() {
|
void init() {
|
||||||
_streamSubscription =
|
_streamSubscription =
|
||||||
_cacheService.hiddenComments.listen(hiddenCommentsStreamListener);
|
_collapseCache.hiddenComments.listen(hiddenCommentsStreamListener);
|
||||||
|
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
collapsedCount: _cacheService.totalHidden(_commentId),
|
collapsedCount: _collapseCache.totalHidden(_commentId),
|
||||||
collapsed: _cacheService.isCollapsed(_commentId),
|
collapsed: _collapseCache.isCollapsed(_commentId),
|
||||||
hidden: _cacheService.isHidden(_commentId),
|
hidden: _collapseCache.isHidden(_commentId),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void collapse() {
|
void collapse() {
|
||||||
if (state.collapsed) {
|
if (state.collapsed) {
|
||||||
_cacheService.uncollapse(_commentId);
|
_collapseCache.uncollapse(_commentId);
|
||||||
|
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
@ -43,7 +43,7 @@ class CollapseCubit extends Cubit<CollapseState> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
final int count = _cacheService.collapse(_commentId);
|
final int count = _collapseCache.collapse(_commentId);
|
||||||
|
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
|
@ -16,22 +16,26 @@ part 'comments_state.dart';
|
|||||||
|
|
||||||
class CommentsCubit extends Cubit<CommentsState> {
|
class CommentsCubit extends Cubit<CommentsState> {
|
||||||
CommentsCubit({
|
CommentsCubit({
|
||||||
CacheService? cacheService,
|
required CollapseCache collapseCache,
|
||||||
CacheRepository? cacheRepository,
|
CommentCache? commentCache,
|
||||||
|
OfflineRepository? offlineRepository,
|
||||||
StoriesRepository? storiesRepository,
|
StoriesRepository? storiesRepository,
|
||||||
SembastRepository? sembastRepository,
|
SembastRepository? sembastRepository,
|
||||||
required bool offlineReading,
|
required bool offlineReading,
|
||||||
required Item item,
|
required Item item,
|
||||||
}) : _cacheService = cacheService ?? locator.get<CacheService>(),
|
}) : _collapseCache = collapseCache,
|
||||||
_cacheRepository = cacheRepository ?? locator.get<CacheRepository>(),
|
_commentCache = commentCache ?? locator.get<CommentCache>(),
|
||||||
|
_offlineRepository =
|
||||||
|
offlineRepository ?? locator.get<OfflineRepository>(),
|
||||||
_storiesRepository =
|
_storiesRepository =
|
||||||
storiesRepository ?? locator.get<StoriesRepository>(),
|
storiesRepository ?? locator.get<StoriesRepository>(),
|
||||||
_sembastRepository =
|
_sembastRepository =
|
||||||
sembastRepository ?? locator.get<SembastRepository>(),
|
sembastRepository ?? locator.get<SembastRepository>(),
|
||||||
super(CommentsState.init(offlineReading: offlineReading, item: item));
|
super(CommentsState.init(offlineReading: offlineReading, item: item));
|
||||||
|
|
||||||
final CacheService _cacheService;
|
final CollapseCache _collapseCache;
|
||||||
final CacheRepository _cacheRepository;
|
final CommentCache _commentCache;
|
||||||
|
final OfflineRepository _offlineRepository;
|
||||||
final StoriesRepository _storiesRepository;
|
final StoriesRepository _storiesRepository;
|
||||||
final SembastRepository _sembastRepository;
|
final SembastRepository _sembastRepository;
|
||||||
StreamSubscription<Comment>? _streamSubscription;
|
StreamSubscription<Comment>? _streamSubscription;
|
||||||
@ -47,6 +51,7 @@ class CommentsCubit extends Cubit<CommentsState> {
|
|||||||
|
|
||||||
Future<void> init({
|
Future<void> init({
|
||||||
bool onlyShowTargetComment = false,
|
bool onlyShowTargetComment = false,
|
||||||
|
bool useCommentCache = false,
|
||||||
List<Comment>? targetParents,
|
List<Comment>? targetParents,
|
||||||
}) async {
|
}) async {
|
||||||
if (onlyShowTargetComment && (targetParents?.isNotEmpty ?? false)) {
|
if (onlyShowTargetComment && (targetParents?.isNotEmpty ?? false)) {
|
||||||
@ -80,13 +85,16 @@ class CommentsCubit extends Cubit<CommentsState> {
|
|||||||
emit(state.copyWith(item: updatedItem));
|
emit(state.copyWith(item: updatedItem));
|
||||||
|
|
||||||
if (state.offlineReading) {
|
if (state.offlineReading) {
|
||||||
_streamSubscription = _cacheRepository
|
_streamSubscription = _offlineRepository
|
||||||
.getCachedCommentsStream(ids: kids)
|
.getCachedCommentsStream(ids: kids)
|
||||||
.listen(_onCommentFetched)
|
.listen(_onCommentFetched)
|
||||||
..onDone(_onDone);
|
..onDone(_onDone);
|
||||||
} else {
|
} else {
|
||||||
_streamSubscription = _storiesRepository
|
_streamSubscription = _storiesRepository
|
||||||
.fetchCommentsStream(ids: kids)
|
.fetchCommentsStream(
|
||||||
|
ids: kids,
|
||||||
|
getFromCache: useCommentCache ? _commentCache.getComment : null,
|
||||||
|
)
|
||||||
.listen(_onCommentFetched)
|
.listen(_onCommentFetched)
|
||||||
..onDone(_onDone);
|
..onDone(_onDone);
|
||||||
}
|
}
|
||||||
@ -102,9 +110,7 @@ class CommentsCubit extends Cubit<CommentsState> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_cacheService
|
_collapseCache.resetCollapsedComments();
|
||||||
..resetComments()
|
|
||||||
..resetCollapsedComments();
|
|
||||||
|
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
@ -179,7 +185,7 @@ class CommentsCubit extends Cubit<CommentsState> {
|
|||||||
if (order == null) return;
|
if (order == null) return;
|
||||||
_streamSubscription?.cancel();
|
_streamSubscription?.cancel();
|
||||||
emit(state.copyWith(order: order, comments: <Comment>[]));
|
emit(state.copyWith(order: order, comments: <Comment>[]));
|
||||||
init();
|
init(useCommentCache: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<int> sortKids(List<int> kids) {
|
List<int> sortKids(List<int> kids) {
|
||||||
@ -205,9 +211,8 @@ class CommentsCubit extends Cubit<CommentsState> {
|
|||||||
|
|
||||||
void _onCommentFetched(Comment? comment) {
|
void _onCommentFetched(Comment? comment) {
|
||||||
if (comment != null) {
|
if (comment != null) {
|
||||||
_cacheService
|
_collapseCache.addKid(comment.id, to: comment.parent);
|
||||||
..addKid(comment.id, to: comment.parent)
|
_commentCache.cacheComment(comment);
|
||||||
..cacheComment(comment);
|
|
||||||
_sembastRepository.cacheComment(comment);
|
_sembastRepository.cacheComment(comment);
|
||||||
|
|
||||||
final List<LinkifyElement> elements = _linkify(
|
final List<LinkifyElement> elements = _linkify(
|
||||||
@ -227,7 +232,7 @@ class CommentsCubit extends Cubit<CommentsState> {
|
|||||||
if (updatedComments.length >= _pageSize + _pageSize * state.currentPage &&
|
if (updatedComments.length >= _pageSize + _pageSize * state.currentPage &&
|
||||||
updatedComments.length <=
|
updatedComments.length <=
|
||||||
_pageSize * 2 + _pageSize * state.currentPage) {
|
_pageSize * 2 + _pageSize * state.currentPage) {
|
||||||
final bool isHidden = _cacheService.isHidden(comment.id);
|
final bool isHidden = _collapseCache.isHidden(comment.id);
|
||||||
|
|
||||||
if (!isHidden) {
|
if (!isHidden) {
|
||||||
_streamSubscription?.pause();
|
_streamSubscription?.pause();
|
||||||
|
@ -8,19 +8,19 @@ import 'package:hacki/utils/debouncer.dart';
|
|||||||
part 'edit_state.dart';
|
part 'edit_state.dart';
|
||||||
|
|
||||||
class EditCubit extends Cubit<EditState> {
|
class EditCubit extends Cubit<EditState> {
|
||||||
EditCubit({CacheService? cacheService})
|
EditCubit({DraftCache? draftCache})
|
||||||
: _cacheService = cacheService ?? locator.get<CacheService>(),
|
: _draftCache = draftCache ?? locator.get<DraftCache>(),
|
||||||
_debouncer = Debouncer(delay: const Duration(seconds: 1)),
|
_debouncer = Debouncer(delay: const Duration(seconds: 1)),
|
||||||
super(const EditState.init());
|
super(const EditState.init());
|
||||||
|
|
||||||
final CacheService _cacheService;
|
final DraftCache _draftCache;
|
||||||
final Debouncer _debouncer;
|
final Debouncer _debouncer;
|
||||||
|
|
||||||
void onReplyTapped(Item item) {
|
void onReplyTapped(Item item) {
|
||||||
emit(
|
emit(
|
||||||
EditState(
|
EditState(
|
||||||
replyingTo: item,
|
replyingTo: item,
|
||||||
text: _cacheService.getDraft(replyingTo: item.id),
|
text: _draftCache.getDraft(replyingTo: item.id),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -44,7 +44,7 @@ class EditCubit extends Cubit<EditState> {
|
|||||||
|
|
||||||
void onReplySubmittedSuccessfully() {
|
void onReplySubmittedSuccessfully() {
|
||||||
if (state.replyingTo != null) {
|
if (state.replyingTo != null) {
|
||||||
_cacheService.removeDraft(replyingTo: state.replyingTo!.id);
|
_draftCache.removeDraft(replyingTo: state.replyingTo!.id);
|
||||||
}
|
}
|
||||||
emit(const EditState.init());
|
emit(const EditState.init());
|
||||||
}
|
}
|
||||||
@ -54,7 +54,7 @@ class EditCubit extends Cubit<EditState> {
|
|||||||
if (state.replyingTo != null) {
|
if (state.replyingTo != null) {
|
||||||
final int? id = state.replyingTo?.id;
|
final int? id = state.replyingTo?.id;
|
||||||
_debouncer.run(() {
|
_debouncer.run(() {
|
||||||
_cacheService.cacheDraft(
|
_draftCache.cacheDraft(
|
||||||
text: text,
|
text: text,
|
||||||
replyingTo: id!,
|
replyingTo: id!,
|
||||||
);
|
);
|
||||||
|
@ -3,18 +3,24 @@ import 'package:equatable/equatable.dart';
|
|||||||
import 'package:hacki/config/locator.dart';
|
import 'package:hacki/config/locator.dart';
|
||||||
import 'package:hacki/screens/screens.dart';
|
import 'package:hacki/screens/screens.dart';
|
||||||
import 'package:hacki/services/services.dart';
|
import 'package:hacki/services/services.dart';
|
||||||
|
import 'package:logger/logger.dart';
|
||||||
|
|
||||||
part 'split_view_state.dart';
|
part 'split_view_state.dart';
|
||||||
|
|
||||||
class SplitViewCubit extends Cubit<SplitViewState> {
|
class SplitViewCubit extends Cubit<SplitViewState> {
|
||||||
SplitViewCubit({CacheService? cacheService})
|
SplitViewCubit({
|
||||||
: _cacheService = cacheService ?? locator.get<CacheService>(),
|
CommentCache? commentCache,
|
||||||
|
Logger? logger,
|
||||||
|
}) : _commentCache = commentCache ?? locator.get<CommentCache>(),
|
||||||
|
_logger = logger ?? locator.get<Logger>(),
|
||||||
super(const SplitViewState.init());
|
super(const SplitViewState.init());
|
||||||
|
|
||||||
final CacheService _cacheService;
|
final Logger _logger;
|
||||||
|
final CommentCache _commentCache;
|
||||||
|
|
||||||
void updateItemScreenArgs(ItemScreenArgs args) {
|
void updateItemScreenArgs(ItemScreenArgs args) {
|
||||||
_cacheService.resetCollapsedComments();
|
_logger.i('resetting comments in CommentCache');
|
||||||
|
_commentCache.resetComments();
|
||||||
emit(state.copyWith(itemScreenArgs: args));
|
emit(state.copyWith(itemScreenArgs: args));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,34 +3,34 @@ import 'package:equatable/equatable.dart';
|
|||||||
import 'package:hacki/config/locator.dart';
|
import 'package:hacki/config/locator.dart';
|
||||||
import 'package:hacki/models/models.dart' show Comment;
|
import 'package:hacki/models/models.dart' show Comment;
|
||||||
import 'package:hacki/repositories/repositories.dart';
|
import 'package:hacki/repositories/repositories.dart';
|
||||||
import 'package:hacki/services/cache_service.dart';
|
import 'package:hacki/services/services.dart';
|
||||||
|
|
||||||
part 'time_machine_state.dart';
|
part 'time_machine_state.dart';
|
||||||
|
|
||||||
class TimeMachineCubit extends Cubit<TimeMachineState> {
|
class TimeMachineCubit extends Cubit<TimeMachineState> {
|
||||||
TimeMachineCubit({
|
TimeMachineCubit({
|
||||||
SembastRepository? sembastRepository,
|
SembastRepository? sembastRepository,
|
||||||
CacheService? cacheService,
|
CommentCache? commentCache,
|
||||||
}) : _sembastRepository =
|
}) : _sembastRepository =
|
||||||
sembastRepository ?? locator.get<SembastRepository>(),
|
sembastRepository ?? locator.get<SembastRepository>(),
|
||||||
_cacheService = cacheService ?? locator.get<CacheService>(),
|
_commentCache = commentCache ?? locator.get<CommentCache>(),
|
||||||
super(TimeMachineState.init());
|
super(TimeMachineState.init());
|
||||||
|
|
||||||
final SembastRepository _sembastRepository;
|
final SembastRepository _sembastRepository;
|
||||||
final CacheService _cacheService;
|
final CommentCache _commentCache;
|
||||||
|
|
||||||
Future<void> activateTimeMachine(Comment comment) async {
|
Future<void> activateTimeMachine(Comment comment) async {
|
||||||
emit(state.copyWith(parents: <Comment>[]));
|
emit(state.copyWith(parents: <Comment>[]));
|
||||||
|
|
||||||
final List<Comment> parents = <Comment>[];
|
final List<Comment> parents = <Comment>[];
|
||||||
Comment? parent = _cacheService.getComment(comment.parent);
|
Comment? parent = _commentCache.getComment(comment.parent);
|
||||||
parent ??= await _sembastRepository.getCachedComment(id: comment.parent);
|
parent ??= await _sembastRepository.getCachedComment(id: comment.parent);
|
||||||
|
|
||||||
while (parent != null) {
|
while (parent != null) {
|
||||||
parents.insert(0, parent);
|
parents.insert(0, parent);
|
||||||
|
|
||||||
final int parentId = parent.parent;
|
final int parentId = parent.parent;
|
||||||
parent = _cacheService.getComment(parentId);
|
parent = _commentCache.getComment(parentId);
|
||||||
parent ??= await _sembastRepository.getCachedComment(id: parentId);
|
parent ??= await _sembastRepository.getCachedComment(id: parentId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
12
lib/extensions/context_extension.dart
Normal file
12
lib/extensions/context_extension.dart
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
|
extension TryReadContext on BuildContext {
|
||||||
|
T? tryRead<T>() {
|
||||||
|
try {
|
||||||
|
return read<T>();
|
||||||
|
} catch (_) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,3 +1,4 @@
|
|||||||
|
export 'context_extension.dart';
|
||||||
export 'date_time_extension.dart';
|
export 'date_time_extension.dart';
|
||||||
export 'int_extension.dart';
|
export 'int_extension.dart';
|
||||||
export 'list_extension.dart';
|
export 'list_extension.dart';
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
extension WidgetModifier on Widget {
|
extension WidgetModifier on Widget {
|
||||||
Widget padding([EdgeInsetsGeometry value = const EdgeInsets.all(12)]) {
|
Widget padded([EdgeInsetsGeometry value = const EdgeInsets.all(12)]) {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: value,
|
padding: value,
|
||||||
child: this,
|
child: this,
|
||||||
|
@ -221,6 +221,9 @@ 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,
|
||||||
),
|
),
|
||||||
|
@ -5,10 +5,10 @@ import 'package:hive/hive.dart';
|
|||||||
import 'package:http/http.dart';
|
import 'package:http/http.dart';
|
||||||
import 'package:logger/logger.dart';
|
import 'package:logger/logger.dart';
|
||||||
|
|
||||||
/// [CacheRepository] is for storing stories and comments for offline reading.
|
/// [OfflineRepository] 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.
|
||||||
class CacheRepository {
|
class OfflineRepository {
|
||||||
CacheRepository({
|
OfflineRepository({
|
||||||
Future<Box<List<int>>>? storyIdBox,
|
Future<Box<List<int>>>? storyIdBox,
|
||||||
Future<Box<Map<dynamic, dynamic>>>? storyBox,
|
Future<Box<Map<dynamic, dynamic>>>? storyBox,
|
||||||
Future<LazyBox<String>>? webPageBox,
|
Future<LazyBox<String>>? webPageBox,
|
||||||
|
@ -43,7 +43,8 @@ class PreferenceRepository {
|
|||||||
|
|
||||||
static const bool _notificationModeDefaultValue = true;
|
static const bool _notificationModeDefaultValue = true;
|
||||||
static const bool _displayModeDefaultValue = true;
|
static const bool _displayModeDefaultValue = true;
|
||||||
static const bool _navigationModeDefaultValue = true;
|
static const bool _navigationModeDefaultValueIOS = true;
|
||||||
|
static const bool _navigationModeDefaultValueAndroid = false;
|
||||||
static const bool _eyeCandyModeDefaultValue = false;
|
static const bool _eyeCandyModeDefaultValue = false;
|
||||||
static const bool _trueDarkModeDefaultValue = false;
|
static const bool _trueDarkModeDefaultValue = false;
|
||||||
static const bool _readerModeDefaultValue = true;
|
static const bool _readerModeDefaultValue = true;
|
||||||
@ -84,7 +85,10 @@ class PreferenceRepository {
|
|||||||
|
|
||||||
Future<bool> get shouldShowWebFirst async => _prefs.then(
|
Future<bool> get shouldShowWebFirst async => _prefs.then(
|
||||||
(SharedPreferences prefs) =>
|
(SharedPreferences prefs) =>
|
||||||
prefs.getBool(_navigationModeKey) ?? _navigationModeDefaultValue,
|
prefs.getBool(_navigationModeKey) ??
|
||||||
|
(Platform.isAndroid
|
||||||
|
? _navigationModeDefaultValueAndroid
|
||||||
|
: _navigationModeDefaultValueIOS),
|
||||||
);
|
);
|
||||||
|
|
||||||
Future<bool> get shouldShowEyeCandy async => _prefs.then(
|
Future<bool> get shouldShowEyeCandy async => _prefs.then(
|
||||||
@ -188,8 +192,10 @@ class PreferenceRepository {
|
|||||||
|
|
||||||
Future<void> toggleNavigationMode() async {
|
Future<void> toggleNavigationMode() async {
|
||||||
final SharedPreferences prefs = await _prefs;
|
final SharedPreferences prefs = await _prefs;
|
||||||
final bool currentMode =
|
final bool currentMode = prefs.getBool(_navigationModeKey) ??
|
||||||
prefs.getBool(_navigationModeKey) ?? _navigationModeDefaultValue;
|
(Platform.isAndroid
|
||||||
|
? _navigationModeDefaultValueAndroid
|
||||||
|
: _navigationModeDefaultValueIOS);
|
||||||
await prefs.setBool(_navigationModeKey, !currentMode);
|
await prefs.setBool(_navigationModeKey, !currentMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,9 +51,12 @@ class StoriesRepository {
|
|||||||
Stream<Comment> fetchCommentsStream({
|
Stream<Comment> fetchCommentsStream({
|
||||||
required List<int> ids,
|
required List<int> ids,
|
||||||
int level = 0,
|
int level = 0,
|
||||||
|
Comment? Function(int)? getFromCache,
|
||||||
}) async* {
|
}) async* {
|
||||||
for (final int id in ids) {
|
for (final int id in ids) {
|
||||||
final Comment? comment = await _firebaseClient
|
Comment? comment = getFromCache?.call(id)?.copyWith(level: level);
|
||||||
|
|
||||||
|
comment ??= await _firebaseClient
|
||||||
.get('${_baseUrl}item/$id.json')
|
.get('${_baseUrl}item/$id.json')
|
||||||
.then((dynamic json) => _parseJson(json as Map<String, dynamic>?))
|
.then((dynamic json) => _parseJson(json as Map<String, dynamic>?))
|
||||||
.then((Map<String, dynamic>? json) async {
|
.then((Map<String, dynamic>? json) async {
|
||||||
@ -69,6 +72,7 @@ class StoriesRepository {
|
|||||||
yield* fetchCommentsStream(
|
yield* fetchCommentsStream(
|
||||||
ids: comment.kids,
|
ids: comment.kids,
|
||||||
level: level + 1,
|
level: level + 1,
|
||||||
|
getFromCache: getFromCache,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,7 @@ import 'package:hacki/screens/widgets/widgets.dart';
|
|||||||
import 'package:hacki/services/services.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';
|
||||||
|
import 'package:logger/logger.dart';
|
||||||
import 'package:receive_sharing_intent/receive_sharing_intent.dart';
|
import 'package:receive_sharing_intent/receive_sharing_intent.dart';
|
||||||
import 'package:responsive_builder/responsive_builder.dart';
|
import 'package:responsive_builder/responsive_builder.dart';
|
||||||
|
|
||||||
@ -46,8 +47,7 @@ class HomeScreen extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _HomeScreenState extends State<HomeScreen>
|
class _HomeScreenState extends State<HomeScreen>
|
||||||
with SingleTickerProviderStateMixin {
|
with SingleTickerProviderStateMixin, RouteAware {
|
||||||
final CacheService cacheService = locator.get<CacheService>();
|
|
||||||
final Throttle featureDiscoveryDismissThrottle = Throttle(
|
final Throttle featureDiscoveryDismissThrottle = Throttle(
|
||||||
delay: _throttleDelay,
|
delay: _throttleDelay,
|
||||||
);
|
);
|
||||||
@ -61,6 +61,19 @@ 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) {
|
||||||
|
locator.get<Logger>().i('resetting comments in CommentCache');
|
||||||
|
Future<void>.delayed(
|
||||||
|
const Duration(milliseconds: 500),
|
||||||
|
locator.get<CommentCache>().resetComments,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
@ -88,14 +101,24 @@ class _HomeScreenState extends State<HomeScreen>
|
|||||||
siriSuggestionSubject.stream.listen(onSiriSuggestionTapped);
|
siriSuggestionSubject.stream.listen(onSiriSuggestionTapped);
|
||||||
}
|
}
|
||||||
|
|
||||||
SchedulerBinding.instance.addPostFrameCallback((_) {
|
SchedulerBinding.instance
|
||||||
FeatureDiscovery.discoverFeatures(
|
..addPostFrameCallback((_) {
|
||||||
context,
|
FeatureDiscovery.discoverFeatures(
|
||||||
const <String>{
|
context,
|
||||||
Constants.featureLogIn,
|
const <String>{
|
||||||
},
|
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)
|
||||||
..addListener(() {
|
..addListener(() {
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
// ignore_for_file: comment_references
|
||||||
|
|
||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:feature_discovery/feature_discovery.dart';
|
import 'package:feature_discovery/feature_discovery.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@ -38,6 +40,7 @@ class ItemScreenArgs extends Equatable {
|
|||||||
const ItemScreenArgs({
|
const ItemScreenArgs({
|
||||||
required this.item,
|
required this.item,
|
||||||
this.onlyShowTargetComment = false,
|
this.onlyShowTargetComment = false,
|
||||||
|
this.useCommentCache = false,
|
||||||
this.targetComments,
|
this.targetComments,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -45,11 +48,17 @@ class ItemScreenArgs extends Equatable {
|
|||||||
final bool onlyShowTargetComment;
|
final bool onlyShowTargetComment;
|
||||||
final List<Comment>? targetComments;
|
final List<Comment>? targetComments;
|
||||||
|
|
||||||
|
/// when a user is trying to view a sub-thread from a main thread, we don't
|
||||||
|
/// need to fetch comments from [StoryRepository] since we have some, if not
|
||||||
|
/// all, comments cached in [CommentCache].
|
||||||
|
final bool useCommentCache;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => <Object?>[
|
List<Object?> get props => <Object?>[
|
||||||
item,
|
item,
|
||||||
onlyShowTargetComment,
|
onlyShowTargetComment,
|
||||||
targetComments,
|
targetComments,
|
||||||
|
useCommentCache,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,8 +75,8 @@ 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) => RepositoryProvider<CacheService>(
|
builder: (BuildContext context) => RepositoryProvider<CollapseCache>(
|
||||||
create: (BuildContext context) => CacheService(),
|
create: (BuildContext context) => CollapseCache(),
|
||||||
lazy: false,
|
lazy: false,
|
||||||
child: MultiBlocProvider(
|
child: MultiBlocProvider(
|
||||||
providers: <BlocProvider<dynamic>>[
|
providers: <BlocProvider<dynamic>>[
|
||||||
@ -76,10 +85,11 @@ class ItemScreen extends StatefulWidget {
|
|||||||
offlineReading:
|
offlineReading:
|
||||||
context.read<StoriesBloc>().state.offlineReading,
|
context.read<StoriesBloc>().state.offlineReading,
|
||||||
item: args.item,
|
item: args.item,
|
||||||
cacheService: context.read<CacheService>(),
|
collapseCache: context.read<CollapseCache>(),
|
||||||
)..init(
|
)..init(
|
||||||
onlyShowTargetComment: args.onlyShowTargetComment,
|
onlyShowTargetComment: args.onlyShowTargetComment,
|
||||||
targetParents: args.targetComments,
|
targetParents: args.targetComments,
|
||||||
|
useCommentCache: args.useCommentCache,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
BlocProvider<EditCubit>(
|
BlocProvider<EditCubit>(
|
||||||
@ -106,8 +116,9 @@ class ItemScreen extends StatefulWidget {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: RepositoryProvider<CacheService>(
|
child: RepositoryProvider<CollapseCache>(
|
||||||
create: (BuildContext context) => CacheService(),
|
create: (BuildContext context) => CollapseCache(),
|
||||||
|
lazy: false,
|
||||||
child: MultiBlocProvider(
|
child: MultiBlocProvider(
|
||||||
key: ValueKey<ItemScreenArgs>(args),
|
key: ValueKey<ItemScreenArgs>(args),
|
||||||
providers: <BlocProvider<dynamic>>[
|
providers: <BlocProvider<dynamic>>[
|
||||||
@ -116,7 +127,7 @@ class ItemScreen extends StatefulWidget {
|
|||||||
offlineReading:
|
offlineReading:
|
||||||
context.read<StoriesBloc>().state.offlineReading,
|
context.read<StoriesBloc>().state.offlineReading,
|
||||||
item: args.item,
|
item: args.item,
|
||||||
cacheService: context.read<CacheService>(),
|
collapseCache: context.read<CollapseCache>(),
|
||||||
)..init(
|
)..init(
|
||||||
onlyShowTargetComment: args.onlyShowTargetComment,
|
onlyShowTargetComment: args.onlyShowTargetComment,
|
||||||
targetParents: args.targetComments,
|
targetParents: args.targetComments,
|
||||||
@ -191,7 +202,6 @@ class _ItemScreenState extends State<ItemScreen> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
locator.get<CacheService>().resetComments();
|
|
||||||
refreshController.dispose();
|
refreshController.dispose();
|
||||||
commentEditingController.dispose();
|
commentEditingController.dispose();
|
||||||
scrollController.dispose();
|
scrollController.dispose();
|
||||||
@ -753,7 +763,10 @@ class _ItemScreenState extends State<ItemScreen> {
|
|||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
goToItemScreen(
|
goToItemScreen(
|
||||||
args: ItemScreenArgs(item: comment),
|
args: ItemScreenArgs(
|
||||||
|
item: comment,
|
||||||
|
useCommentCache: true,
|
||||||
|
),
|
||||||
forceNewScreen: true,
|
forceNewScreen: true,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -410,7 +410,7 @@ class _ProfileScreenState extends State<ProfileScreen>
|
|||||||
showAboutDialog(
|
showAboutDialog(
|
||||||
context: context,
|
context: context,
|
||||||
applicationName: 'Hacki',
|
applicationName: 'Hacki',
|
||||||
applicationVersion: 'v0.2.24',
|
applicationVersion: 'v0.2.25',
|
||||||
applicationIcon: ClipRRect(
|
applicationIcon: ClipRRect(
|
||||||
borderRadius: const BorderRadius.all(
|
borderRadius: const BorderRadius.all(
|
||||||
Radius.circular(
|
Radius.circular(
|
||||||
@ -680,7 +680,7 @@ class _ProfileScreenState extends State<ProfileScreen>
|
|||||||
.get<SembastRepository>()
|
.get<SembastRepository>()
|
||||||
.deleteAllCachedComments()
|
.deleteAllCachedComments()
|
||||||
.whenComplete(
|
.whenComplete(
|
||||||
locator.get<CacheRepository>().deleteAll,
|
locator.get<OfflineRepository>().deleteAll,
|
||||||
)
|
)
|
||||||
.whenComplete(
|
.whenComplete(
|
||||||
locator.get<PreferenceRepository>().clearAllReadStories,
|
locator.get<PreferenceRepository>().clearAllReadStories,
|
||||||
|
@ -34,7 +34,7 @@ class _WebViewScreenState extends State<WebViewScreen> {
|
|||||||
),
|
),
|
||||||
body: WebView(
|
body: WebView(
|
||||||
onWebViewCreated: (WebViewController controller) async {
|
onWebViewCreated: (WebViewController controller) async {
|
||||||
final String? html = await locator.get<CacheRepository>().getHtml(
|
final String? html = await locator.get<OfflineRepository>().getHtml(
|
||||||
url: widget.url,
|
url: widget.url,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -44,7 +44,7 @@ class CommentTile extends StatelessWidget {
|
|||||||
lazy: false,
|
lazy: false,
|
||||||
create: (_) => CollapseCubit(
|
create: (_) => CollapseCubit(
|
||||||
commentId: comment.id,
|
commentId: comment.id,
|
||||||
cacheService: context.read<CacheService>(),
|
collapseCache: context.tryRead<CollapseCache>() ?? CollapseCache(),
|
||||||
)..init(),
|
)..init(),
|
||||||
child: BlocBuilder<CollapseCubit, CollapseState>(
|
child: BlocBuilder<CollapseCubit, CollapseState>(
|
||||||
builder: (BuildContext context, CollapseState state) {
|
builder: (BuildContext context, CollapseState state) {
|
||||||
|
@ -140,7 +140,7 @@ class WebAnalyzer {
|
|||||||
|
|
||||||
while (comment == null && index < story.kids.length) {
|
while (comment == null && index < story.kids.length) {
|
||||||
comment = await locator
|
comment = await locator
|
||||||
.get<CacheRepository>()
|
.get<OfflineRepository>()
|
||||||
.getCachedComment(id: story.kids.elementAt(index));
|
.getCachedComment(id: story.kids.elementAt(index));
|
||||||
index++;
|
index++;
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,8 @@ class StoriesListView extends StatefulWidget {
|
|||||||
State<StoriesListView> createState() => _StoriesListViewState();
|
State<StoriesListView> createState() => _StoriesListViewState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _StoriesListViewState extends State<StoriesListView> {
|
class _StoriesListViewState extends State<StoriesListView>
|
||||||
|
with AutomaticKeepAliveClientMixin {
|
||||||
final RefreshController refreshController = RefreshController();
|
final RefreshController refreshController = RefreshController();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -34,6 +35,7 @@ class _StoriesListViewState extends State<StoriesListView> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
super.build(context);
|
||||||
final StoryType storyType = widget.storyType;
|
final StoryType storyType = widget.storyType;
|
||||||
final Widget header = widget.header;
|
final Widget header = widget.header;
|
||||||
final ValueChanged<Story> onStoryTapped = widget.onStoryTapped;
|
final ValueChanged<Story> onStoryTapped = widget.onStoryTapped;
|
||||||
@ -91,4 +93,7 @@ class _StoriesListViewState extends State<StoriesListView> {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get wantKeepAlive => true;
|
||||||
}
|
}
|
||||||
|
3
lib/services/caches/caches.dart
Normal file
3
lib/services/caches/caches.dart
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export 'collapse_cache.dart';
|
||||||
|
export 'comment_cache.dart';
|
||||||
|
export 'draft_cache.dart';
|
@ -1,9 +1,6 @@
|
|||||||
import 'package:hacki/models/models.dart' show Comment;
|
|
||||||
import 'package:rxdart/rxdart.dart';
|
import 'package:rxdart/rxdart.dart';
|
||||||
|
|
||||||
class CacheService {
|
class CollapseCache {
|
||||||
final Map<int, Comment> _comments = <int, Comment>{};
|
|
||||||
final Map<int, String> _drafts = <int, String>{};
|
|
||||||
final Map<int, Set<int>> _kids = <int, Set<int>>{};
|
final Map<int, Set<int>> _kids = <int, Set<int>>{};
|
||||||
final Set<int> _collapsed = <int>{};
|
final Set<int> _collapsed = <int>{};
|
||||||
final Map<int, Set<int>> _hidden = <int, Set<int>>{};
|
final Map<int, Set<int>> _hidden = <int, Set<int>>{};
|
||||||
@ -76,19 +73,4 @@ class CacheService {
|
|||||||
bool isCollapsed(int commentId) => _collapsed.contains(commentId);
|
bool isCollapsed(int commentId) => _collapsed.contains(commentId);
|
||||||
|
|
||||||
int totalHidden(int commentId) => _hidden[commentId]?.length ?? 0;
|
int totalHidden(int commentId) => _hidden[commentId]?.length ?? 0;
|
||||||
|
|
||||||
void cacheComment(Comment comment) => _comments[comment.id] = comment;
|
|
||||||
|
|
||||||
Comment? getComment(int id) => _comments[id];
|
|
||||||
|
|
||||||
void resetComments() {
|
|
||||||
_comments.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
void removeDraft({required int replyingTo}) => _drafts.remove(replyingTo);
|
|
||||||
|
|
||||||
void cacheDraft({required String text, required int replyingTo}) =>
|
|
||||||
_drafts[replyingTo] = text;
|
|
||||||
|
|
||||||
String? getDraft({required int replyingTo}) => _drafts[replyingTo];
|
|
||||||
}
|
}
|
13
lib/services/caches/comment_cache.dart
Normal file
13
lib/services/caches/comment_cache.dart
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import 'package:hacki/models/models.dart' show Comment;
|
||||||
|
|
||||||
|
class CommentCache {
|
||||||
|
static final Map<int, Comment> _comments = <int, Comment>{};
|
||||||
|
|
||||||
|
void cacheComment(Comment comment) => _comments[comment.id] = comment;
|
||||||
|
|
||||||
|
Comment? getComment(int id) => _comments[id];
|
||||||
|
|
||||||
|
void resetComments() {
|
||||||
|
_comments.clear();
|
||||||
|
}
|
||||||
|
}
|
10
lib/services/caches/draft_cache.dart
Normal file
10
lib/services/caches/draft_cache.dart
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
class DraftCache {
|
||||||
|
static final Map<int, String> _drafts = <int, String>{};
|
||||||
|
|
||||||
|
void removeDraft({required int replyingTo}) => _drafts.remove(replyingTo);
|
||||||
|
|
||||||
|
void cacheDraft({required String text, required int replyingTo}) =>
|
||||||
|
_drafts[replyingTo] = text;
|
||||||
|
|
||||||
|
String? getDraft({required int replyingTo}) => _drafts[replyingTo];
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
export 'cache_service.dart';
|
export 'caches/caches.dart';
|
||||||
export 'custom_bloc_observer.dart';
|
export 'custom_bloc_observer.dart';
|
||||||
export 'fetcher.dart';
|
export 'fetcher.dart';
|
||||||
export 'firebase_client.dart';
|
export 'firebase_client.dart';
|
||||||
|
@ -19,7 +19,7 @@ abstract class LinkUtil {
|
|||||||
}) {
|
}) {
|
||||||
if (offlineReading) {
|
if (offlineReading) {
|
||||||
locator
|
locator
|
||||||
.get<CacheRepository>()
|
.get<OfflineRepository>()
|
||||||
.hasCachedWebPage(url: link)
|
.hasCachedWebPage(url: link)
|
||||||
.then((bool cached) {
|
.then((bool cached) {
|
||||||
if (cached) {
|
if (cached) {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
name: hacki
|
name: hacki
|
||||||
description: A Hacker News reader.
|
description: A Hacker News reader.
|
||||||
version: 0.2.24+66
|
version: 0.2.25+67
|
||||||
publish_to: none
|
publish_to: none
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
|
Reference in New Issue
Block a user