mirror of
https://github.com/Livinglist/Hacki.git
synced 2025-08-14 10:12:32 +08:00
Compare commits
6 Commits
Author | SHA1 | Date | |
---|---|---|---|
f07254dbd4 | |||
1408b7343a | |||
bedc3b66ec | |||
3e3941380d | |||
bbed4e0e75 | |||
a4ae6a20e1 |
@ -1,7 +1,9 @@
|
|||||||
GEM
|
GEM
|
||||||
remote: https://rubygems.org/
|
remote: https://rubygems.org/
|
||||||
specs:
|
specs:
|
||||||
CFPropertyList (3.0.6)
|
CFPropertyList (3.0.7)
|
||||||
|
base64
|
||||||
|
nkf
|
||||||
rexml
|
rexml
|
||||||
activesupport (6.1.7)
|
activesupport (6.1.7)
|
||||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||||
@ -9,30 +11,31 @@ GEM
|
|||||||
minitest (>= 5.1)
|
minitest (>= 5.1)
|
||||||
tzinfo (~> 2.0)
|
tzinfo (~> 2.0)
|
||||||
zeitwerk (~> 2.3)
|
zeitwerk (~> 2.3)
|
||||||
addressable (2.8.6)
|
addressable (2.8.7)
|
||||||
public_suffix (>= 2.0.2, < 6.0)
|
public_suffix (>= 2.0.2, < 7.0)
|
||||||
algoliasearch (1.27.5)
|
algoliasearch (1.27.5)
|
||||||
httpclient (~> 2.8, >= 2.8.3)
|
httpclient (~> 2.8, >= 2.8.3)
|
||||||
json (>= 1.5.1)
|
json (>= 1.5.1)
|
||||||
artifactory (3.0.15)
|
artifactory (3.0.17)
|
||||||
atomos (0.1.3)
|
atomos (0.1.3)
|
||||||
aws-eventstream (1.3.0)
|
aws-eventstream (1.3.0)
|
||||||
aws-partitions (1.889.0)
|
aws-partitions (1.994.0)
|
||||||
aws-sdk-core (3.191.1)
|
aws-sdk-core (3.211.0)
|
||||||
aws-eventstream (~> 1, >= 1.3.0)
|
aws-eventstream (~> 1, >= 1.3.0)
|
||||||
aws-partitions (~> 1, >= 1.651.0)
|
aws-partitions (~> 1, >= 1.992.0)
|
||||||
aws-sigv4 (~> 1.8)
|
aws-sigv4 (~> 1.9)
|
||||||
jmespath (~> 1, >= 1.6.1)
|
jmespath (~> 1, >= 1.6.1)
|
||||||
aws-sdk-kms (1.77.0)
|
aws-sdk-kms (1.95.0)
|
||||||
aws-sdk-core (~> 3, >= 3.191.0)
|
aws-sdk-core (~> 3, >= 3.210.0)
|
||||||
aws-sigv4 (~> 1.1)
|
aws-sigv4 (~> 1.5)
|
||||||
aws-sdk-s3 (1.143.0)
|
aws-sdk-s3 (1.169.0)
|
||||||
aws-sdk-core (~> 3, >= 3.191.0)
|
aws-sdk-core (~> 3, >= 3.210.0)
|
||||||
aws-sdk-kms (~> 1)
|
aws-sdk-kms (~> 1)
|
||||||
aws-sigv4 (~> 1.8)
|
aws-sigv4 (~> 1.5)
|
||||||
aws-sigv4 (1.8.0)
|
aws-sigv4 (1.10.1)
|
||||||
aws-eventstream (~> 1, >= 1.0.2)
|
aws-eventstream (~> 1, >= 1.0.2)
|
||||||
babosa (1.0.4)
|
babosa (1.0.4)
|
||||||
|
base64 (0.2.0)
|
||||||
claide (1.1.0)
|
claide (1.1.0)
|
||||||
cocoapods (1.11.3)
|
cocoapods (1.11.3)
|
||||||
addressable (~> 2.8)
|
addressable (~> 2.8)
|
||||||
@ -87,7 +90,7 @@ GEM
|
|||||||
ethon (0.15.0)
|
ethon (0.15.0)
|
||||||
ffi (>= 1.15.0)
|
ffi (>= 1.15.0)
|
||||||
excon (0.109.0)
|
excon (0.109.0)
|
||||||
faraday (1.10.3)
|
faraday (1.10.4)
|
||||||
faraday-em_http (~> 1.0)
|
faraday-em_http (~> 1.0)
|
||||||
faraday-em_synchrony (~> 1.0)
|
faraday-em_synchrony (~> 1.0)
|
||||||
faraday-excon (~> 1.1)
|
faraday-excon (~> 1.1)
|
||||||
@ -108,22 +111,22 @@ GEM
|
|||||||
faraday-httpclient (1.0.1)
|
faraday-httpclient (1.0.1)
|
||||||
faraday-multipart (1.0.4)
|
faraday-multipart (1.0.4)
|
||||||
multipart-post (~> 2)
|
multipart-post (~> 2)
|
||||||
faraday-net_http (1.0.1)
|
faraday-net_http (1.0.2)
|
||||||
faraday-net_http_persistent (1.2.0)
|
faraday-net_http_persistent (1.2.0)
|
||||||
faraday-patron (1.0.0)
|
faraday-patron (1.0.0)
|
||||||
faraday-rack (1.0.0)
|
faraday-rack (1.0.0)
|
||||||
faraday-retry (1.0.3)
|
faraday-retry (1.0.3)
|
||||||
faraday_middleware (1.2.0)
|
faraday_middleware (1.2.1)
|
||||||
faraday (~> 1.0)
|
faraday (~> 1.0)
|
||||||
fastimage (2.3.0)
|
fastimage (2.3.1)
|
||||||
fastlane (2.219.0)
|
fastlane (2.225.0)
|
||||||
CFPropertyList (>= 2.3, < 4.0.0)
|
CFPropertyList (>= 2.3, < 4.0.0)
|
||||||
addressable (>= 2.8, < 3.0.0)
|
addressable (>= 2.8, < 3.0.0)
|
||||||
artifactory (~> 3.0)
|
artifactory (~> 3.0)
|
||||||
aws-sdk-s3 (~> 1.0)
|
aws-sdk-s3 (~> 1.0)
|
||||||
babosa (>= 1.0.3, < 2.0.0)
|
babosa (>= 1.0.3, < 2.0.0)
|
||||||
bundler (>= 1.12.0, < 3.0.0)
|
bundler (>= 1.12.0, < 3.0.0)
|
||||||
colored
|
colored (~> 1.2)
|
||||||
commander (~> 4.6)
|
commander (~> 4.6)
|
||||||
dotenv (>= 2.1.1, < 3.0.0)
|
dotenv (>= 2.1.1, < 3.0.0)
|
||||||
emoji_regex (>= 0.1, < 4.0)
|
emoji_regex (>= 0.1, < 4.0)
|
||||||
@ -132,6 +135,7 @@ GEM
|
|||||||
faraday-cookie_jar (~> 0.0.6)
|
faraday-cookie_jar (~> 0.0.6)
|
||||||
faraday_middleware (~> 1.0)
|
faraday_middleware (~> 1.0)
|
||||||
fastimage (>= 2.1.0, < 3.0.0)
|
fastimage (>= 2.1.0, < 3.0.0)
|
||||||
|
fastlane-sirp (>= 1.0.0)
|
||||||
gh_inspector (>= 1.1.2, < 2.0.0)
|
gh_inspector (>= 1.1.2, < 2.0.0)
|
||||||
google-apis-androidpublisher_v3 (~> 0.3)
|
google-apis-androidpublisher_v3 (~> 0.3)
|
||||||
google-apis-playcustomapp_v1 (~> 0.1)
|
google-apis-playcustomapp_v1 (~> 0.1)
|
||||||
@ -144,10 +148,10 @@ GEM
|
|||||||
mini_magick (>= 4.9.4, < 5.0.0)
|
mini_magick (>= 4.9.4, < 5.0.0)
|
||||||
multipart-post (>= 2.0.0, < 3.0.0)
|
multipart-post (>= 2.0.0, < 3.0.0)
|
||||||
naturally (~> 2.2)
|
naturally (~> 2.2)
|
||||||
optparse (>= 0.1.1)
|
optparse (>= 0.1.1, < 1.0.0)
|
||||||
plist (>= 3.1.0, < 4.0.0)
|
plist (>= 3.1.0, < 4.0.0)
|
||||||
rubyzip (>= 2.0.0, < 3.0.0)
|
rubyzip (>= 2.0.0, < 3.0.0)
|
||||||
security (= 0.1.3)
|
security (= 0.1.5)
|
||||||
simctl (~> 1.6.3)
|
simctl (~> 1.6.3)
|
||||||
terminal-notifier (>= 2.0.0, < 3.0.0)
|
terminal-notifier (>= 2.0.0, < 3.0.0)
|
||||||
terminal-table (~> 3)
|
terminal-table (~> 3)
|
||||||
@ -156,7 +160,9 @@ GEM
|
|||||||
word_wrap (~> 1.0.0)
|
word_wrap (~> 1.0.0)
|
||||||
xcodeproj (>= 1.13.0, < 2.0.0)
|
xcodeproj (>= 1.13.0, < 2.0.0)
|
||||||
xcpretty (~> 0.3.0)
|
xcpretty (~> 0.3.0)
|
||||||
xcpretty-travis-formatter (>= 0.0.3)
|
xcpretty-travis-formatter (>= 0.0.3, < 2.0.0)
|
||||||
|
fastlane-sirp (1.0.0)
|
||||||
|
sysrandom (~> 1.0)
|
||||||
ffi (1.15.5)
|
ffi (1.15.5)
|
||||||
fourflusher (2.3.1)
|
fourflusher (2.3.1)
|
||||||
fuzzy_match (2.0.4)
|
fuzzy_match (2.0.4)
|
||||||
@ -198,40 +204,42 @@ GEM
|
|||||||
os (>= 0.9, < 2.0)
|
os (>= 0.9, < 2.0)
|
||||||
signet (>= 0.16, < 2.a)
|
signet (>= 0.16, < 2.a)
|
||||||
highline (2.0.3)
|
highline (2.0.3)
|
||||||
http-cookie (1.0.5)
|
http-cookie (1.0.7)
|
||||||
domain_name (~> 0.5)
|
domain_name (~> 0.5)
|
||||||
httpclient (2.8.3)
|
httpclient (2.8.3)
|
||||||
i18n (1.12.0)
|
i18n (1.12.0)
|
||||||
concurrent-ruby (~> 1.0)
|
concurrent-ruby (~> 1.0)
|
||||||
jmespath (1.6.2)
|
jmespath (1.6.2)
|
||||||
json (2.7.1)
|
json (2.7.2)
|
||||||
jwt (2.7.1)
|
jwt (2.9.3)
|
||||||
mini_magick (4.12.0)
|
base64
|
||||||
|
mini_magick (4.13.2)
|
||||||
mini_mime (1.1.5)
|
mini_mime (1.1.5)
|
||||||
minitest (5.16.3)
|
minitest (5.16.3)
|
||||||
molinillo (0.8.0)
|
molinillo (0.8.0)
|
||||||
multi_json (1.15.0)
|
multi_json (1.15.0)
|
||||||
multipart-post (2.4.0)
|
multipart-post (2.4.1)
|
||||||
nanaimo (0.3.0)
|
nanaimo (0.3.0)
|
||||||
nap (1.1.0)
|
nap (1.1.0)
|
||||||
naturally (2.2.1)
|
naturally (2.2.1)
|
||||||
netrc (0.11.0)
|
netrc (0.11.0)
|
||||||
optparse (0.4.0)
|
nkf (0.2.0)
|
||||||
|
optparse (0.5.0)
|
||||||
os (1.1.4)
|
os (1.1.4)
|
||||||
plist (3.7.1)
|
plist (3.7.1)
|
||||||
public_suffix (4.0.7)
|
public_suffix (4.0.7)
|
||||||
rake (13.1.0)
|
rake (13.2.1)
|
||||||
representable (3.2.0)
|
representable (3.2.0)
|
||||||
declarative (< 0.1.0)
|
declarative (< 0.1.0)
|
||||||
trailblazer-option (>= 0.1.1, < 0.2.0)
|
trailblazer-option (>= 0.1.1, < 0.2.0)
|
||||||
uber (< 0.2.0)
|
uber (< 0.2.0)
|
||||||
retriable (3.1.2)
|
retriable (3.1.2)
|
||||||
rexml (3.2.6)
|
rexml (3.3.8)
|
||||||
rouge (2.0.7)
|
rouge (2.0.7)
|
||||||
ruby-macho (2.5.1)
|
ruby-macho (2.5.1)
|
||||||
ruby2_keywords (0.0.5)
|
ruby2_keywords (0.0.5)
|
||||||
rubyzip (2.3.2)
|
rubyzip (2.3.2)
|
||||||
security (0.1.3)
|
security (0.1.5)
|
||||||
signet (0.18.0)
|
signet (0.18.0)
|
||||||
addressable (~> 2.8)
|
addressable (~> 2.8)
|
||||||
faraday (>= 0.17.5, < 3.a)
|
faraday (>= 0.17.5, < 3.a)
|
||||||
@ -240,6 +248,7 @@ GEM
|
|||||||
simctl (1.6.10)
|
simctl (1.6.10)
|
||||||
CFPropertyList
|
CFPropertyList
|
||||||
naturally
|
naturally
|
||||||
|
sysrandom (1.0.5)
|
||||||
terminal-notifier (2.0.0)
|
terminal-notifier (2.0.0)
|
||||||
terminal-table (3.0.2)
|
terminal-table (3.0.2)
|
||||||
unicode-display_width (>= 1.1.1, < 3)
|
unicode-display_width (>= 1.1.1, < 3)
|
||||||
@ -253,18 +262,16 @@ GEM
|
|||||||
tzinfo (2.0.5)
|
tzinfo (2.0.5)
|
||||||
concurrent-ruby (~> 1.0)
|
concurrent-ruby (~> 1.0)
|
||||||
uber (0.1.0)
|
uber (0.1.0)
|
||||||
unf (0.1.4)
|
unf (0.2.0)
|
||||||
unf_ext
|
unicode-display_width (2.6.0)
|
||||||
unf_ext (0.0.9.1)
|
|
||||||
unicode-display_width (2.5.0)
|
|
||||||
word_wrap (1.0.0)
|
word_wrap (1.0.0)
|
||||||
xcodeproj (1.24.0)
|
xcodeproj (1.25.1)
|
||||||
CFPropertyList (>= 2.3.3, < 4.0)
|
CFPropertyList (>= 2.3.3, < 4.0)
|
||||||
atomos (~> 0.1.3)
|
atomos (~> 0.1.3)
|
||||||
claide (>= 1.0.2, < 2.0)
|
claide (>= 1.0.2, < 2.0)
|
||||||
colored2 (~> 3.1)
|
colored2 (~> 3.1)
|
||||||
nanaimo (~> 0.3.0)
|
nanaimo (~> 0.3.0)
|
||||||
rexml (~> 3.2.4)
|
rexml (>= 3.3.6, < 4.0)
|
||||||
xcpretty (0.3.0)
|
xcpretty (0.3.0)
|
||||||
rouge (~> 2.0.7)
|
rouge (~> 2.0.7)
|
||||||
xcpretty-travis-formatter (1.0.1)
|
xcpretty-travis-formatter (1.0.1)
|
||||||
|
@ -10,10 +10,10 @@ PODS:
|
|||||||
- flutter_inappwebview_ios (0.0.1):
|
- flutter_inappwebview_ios (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- flutter_inappwebview_ios/Core (= 0.0.1)
|
- flutter_inappwebview_ios/Core (= 0.0.1)
|
||||||
- OrderedSet (~> 5.0)
|
- OrderedSet (~> 6.0.3)
|
||||||
- flutter_inappwebview_ios/Core (0.0.1):
|
- flutter_inappwebview_ios/Core (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- OrderedSet (~> 5.0)
|
- OrderedSet (~> 6.0.3)
|
||||||
- flutter_local_notifications (0.0.1):
|
- flutter_local_notifications (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- flutter_native_splash (0.0.1):
|
- flutter_native_splash (0.0.1):
|
||||||
@ -25,7 +25,7 @@ PODS:
|
|||||||
- integration_test (0.0.1):
|
- integration_test (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- MTBBarcodeScanner (5.0.11)
|
- MTBBarcodeScanner (5.0.11)
|
||||||
- OrderedSet (5.0.0)
|
- OrderedSet (6.0.3)
|
||||||
- package_info_plus (0.4.5):
|
- package_info_plus (0.4.5):
|
||||||
- Flutter
|
- Flutter
|
||||||
- path_provider_foundation (0.0.1):
|
- path_provider_foundation (0.0.1):
|
||||||
@ -135,14 +135,14 @@ SPEC CHECKSUMS:
|
|||||||
device_info_plus: 97af1d7e84681a90d0693e63169a5d50e0839a0d
|
device_info_plus: 97af1d7e84681a90d0693e63169a5d50e0839a0d
|
||||||
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
|
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
|
||||||
flutter_email_sender: 10a22605f92809a11ef52b2f412db806c6082d40
|
flutter_email_sender: 10a22605f92809a11ef52b2f412db806c6082d40
|
||||||
flutter_inappwebview_ios: 97215cf7d4677db55df76782dbd2930c5e1c1ea0
|
flutter_inappwebview_ios: 6f63631e2c62a7c350263b13fa5427aedefe81d4
|
||||||
flutter_local_notifications: 4cde75091f6327eb8517fa068a0a5950212d2086
|
flutter_local_notifications: 4cde75091f6327eb8517fa068a0a5950212d2086
|
||||||
flutter_native_splash: edf599c81f74d093a4daf8e17bd7a018854bc778
|
flutter_native_splash: edf599c81f74d093a4daf8e17bd7a018854bc778
|
||||||
flutter_secure_storage: d33dac7ae2ea08509be337e775f6b59f1ff45f12
|
flutter_secure_storage: d33dac7ae2ea08509be337e775f6b59f1ff45f12
|
||||||
in_app_review: 318597b3a06c22bb46dc454d56828c85f444f99d
|
in_app_review: 318597b3a06c22bb46dc454d56828c85f444f99d
|
||||||
integration_test: 252f60fa39af5e17c3aa9899d35d908a0721b573
|
integration_test: 252f60fa39af5e17c3aa9899d35d908a0721b573
|
||||||
MTBBarcodeScanner: f453b33c4b7dfe545d8c6484ed744d55671788cb
|
MTBBarcodeScanner: f453b33c4b7dfe545d8c6484ed744d55671788cb
|
||||||
OrderedSet: aaeb196f7fef5a9edf55d89760da9176ad40b93c
|
OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94
|
||||||
package_info_plus: 58f0028419748fad15bf008b270aaa8e54380b1c
|
package_info_plus: 58f0028419748fad15bf008b270aaa8e54380b1c
|
||||||
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
|
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
|
||||||
qr_code_scanner: bb67d64904c3b9658ada8c402e8b4d406d5d796e
|
qr_code_scanner: bb67d64904c3b9658ada8c402e8b4d406d5d796e
|
||||||
@ -158,4 +158,4 @@ SPEC CHECKSUMS:
|
|||||||
|
|
||||||
PODFILE CHECKSUM: f03c7c11cf2b623592c89c68c628682778bb78b4
|
PODFILE CHECKSUM: f03c7c11cf2b623592c89c68c628682778bb78b4
|
||||||
|
|
||||||
COCOAPODS: 1.15.2
|
COCOAPODS: 1.16.2
|
||||||
|
@ -19,6 +19,7 @@ class FavCubit extends Cubit<FavState> with Loggable {
|
|||||||
PreferenceRepository? preferenceRepository,
|
PreferenceRepository? preferenceRepository,
|
||||||
HackerNewsRepository? hackerNewsRepository,
|
HackerNewsRepository? hackerNewsRepository,
|
||||||
HackerNewsWebRepository? hackerNewsWebRepository,
|
HackerNewsWebRepository? hackerNewsWebRepository,
|
||||||
|
SembastRepository? sembastRepository,
|
||||||
}) : _authBloc = authBloc,
|
}) : _authBloc = authBloc,
|
||||||
_authRepository = authRepository ?? locator.get<AuthRepository>(),
|
_authRepository = authRepository ?? locator.get<AuthRepository>(),
|
||||||
_preferenceRepository =
|
_preferenceRepository =
|
||||||
@ -27,6 +28,8 @@ class FavCubit extends Cubit<FavState> with Loggable {
|
|||||||
hackerNewsRepository ?? locator.get<HackerNewsRepository>(),
|
hackerNewsRepository ?? locator.get<HackerNewsRepository>(),
|
||||||
_hackerNewsWebRepository =
|
_hackerNewsWebRepository =
|
||||||
hackerNewsWebRepository ?? locator.get<HackerNewsWebRepository>(),
|
hackerNewsWebRepository ?? locator.get<HackerNewsWebRepository>(),
|
||||||
|
_sembastRepository =
|
||||||
|
sembastRepository ?? locator.get<SembastRepository>(),
|
||||||
super(FavState.init()) {
|
super(FavState.init()) {
|
||||||
init();
|
init();
|
||||||
}
|
}
|
||||||
@ -36,8 +39,9 @@ class FavCubit extends Cubit<FavState> with Loggable {
|
|||||||
final PreferenceRepository _preferenceRepository;
|
final PreferenceRepository _preferenceRepository;
|
||||||
final HackerNewsRepository _hackerNewsRepository;
|
final HackerNewsRepository _hackerNewsRepository;
|
||||||
final HackerNewsWebRepository _hackerNewsWebRepository;
|
final HackerNewsWebRepository _hackerNewsWebRepository;
|
||||||
|
final SembastRepository _sembastRepository;
|
||||||
late final StreamSubscription<String>? _usernameSubscription;
|
late final StreamSubscription<String>? _usernameSubscription;
|
||||||
static const int _pageSize = 20;
|
static const int _pageSize = 100;
|
||||||
|
|
||||||
Future<void> init() async {
|
Future<void> init() async {
|
||||||
_usernameSubscription = _authBloc.stream
|
_usernameSubscription = _authBloc.stream
|
||||||
@ -55,6 +59,8 @@ class FavCubit extends Cubit<FavState> with Loggable {
|
|||||||
_hackerNewsRepository
|
_hackerNewsRepository
|
||||||
.fetchItemsStream(
|
.fetchItemsStream(
|
||||||
ids: favIds.sublist(0, _pageSize.clamp(0, favIds.length)),
|
ids: favIds.sublist(0, _pageSize.clamp(0, favIds.length)),
|
||||||
|
getFromCache: (int id) =>
|
||||||
|
_sembastRepository.getCachedItem(id: id),
|
||||||
)
|
)
|
||||||
.listen(_onItemLoaded)
|
.listen(_onItemLoaded)
|
||||||
.onDone(() {
|
.onDone(() {
|
||||||
@ -97,7 +103,10 @@ class FavCubit extends Cubit<FavState> with Loggable {
|
|||||||
void removeFav(int id) {
|
void removeFav(int id) {
|
||||||
_preferenceRepository
|
_preferenceRepository
|
||||||
..removeFav(username: username, id: id)
|
..removeFav(username: username, id: id)
|
||||||
..removeFav(username: '', id: id);
|
..removeFav(
|
||||||
|
username: '',
|
||||||
|
id: id,
|
||||||
|
);
|
||||||
|
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
@ -200,6 +209,7 @@ class FavCubit extends Cubit<FavState> with Loggable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _onItemLoaded(Item item) {
|
void _onItemLoaded(Item item) {
|
||||||
|
_sembastRepository.cacheItem(item);
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
favItems: List<Item>.from(state.favItems)..add(item),
|
favItems: List<Item>.from(state.favItems)..add(item),
|
||||||
@ -207,6 +217,9 @@ class FavCubit extends Cubit<FavState> with Loggable {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void switchTab() =>
|
||||||
|
emit(state.copyWith(isDisplayingStories: !state.isDisplayingStories));
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> close() {
|
Future<void> close() {
|
||||||
_usernameSubscription?.cancel();
|
_usernameSubscription?.cancel();
|
||||||
|
@ -7,6 +7,7 @@ class FavState extends Equatable {
|
|||||||
required this.status,
|
required this.status,
|
||||||
required this.mergeStatus,
|
required this.mergeStatus,
|
||||||
required this.currentPage,
|
required this.currentPage,
|
||||||
|
required this.isDisplayingStories,
|
||||||
});
|
});
|
||||||
|
|
||||||
FavState.init()
|
FavState.init()
|
||||||
@ -14,13 +15,21 @@ class FavState extends Equatable {
|
|||||||
favItems = <Item>[],
|
favItems = <Item>[],
|
||||||
status = Status.idle,
|
status = Status.idle,
|
||||||
mergeStatus = Status.idle,
|
mergeStatus = Status.idle,
|
||||||
currentPage = 0;
|
currentPage = 0,
|
||||||
|
isDisplayingStories = true;
|
||||||
|
|
||||||
final List<int> favIds;
|
final List<int> favIds;
|
||||||
final List<Item> favItems;
|
final List<Item> favItems;
|
||||||
final Status status;
|
final Status status;
|
||||||
final Status mergeStatus;
|
final Status mergeStatus;
|
||||||
final int currentPage;
|
final int currentPage;
|
||||||
|
final bool isDisplayingStories;
|
||||||
|
|
||||||
|
List<Comment> get favComments =>
|
||||||
|
favItems.whereType<Comment>().toList(growable: false);
|
||||||
|
|
||||||
|
List<Story> get favStories =>
|
||||||
|
favItems.whereType<Story>().toList(growable: false);
|
||||||
|
|
||||||
FavState copyWith({
|
FavState copyWith({
|
||||||
List<int>? favIds,
|
List<int>? favIds,
|
||||||
@ -28,6 +37,7 @@ class FavState extends Equatable {
|
|||||||
Status? status,
|
Status? status,
|
||||||
Status? mergeStatus,
|
Status? mergeStatus,
|
||||||
int? currentPage,
|
int? currentPage,
|
||||||
|
bool? isDisplayingStories,
|
||||||
}) {
|
}) {
|
||||||
return FavState(
|
return FavState(
|
||||||
favIds: favIds ?? this.favIds,
|
favIds: favIds ?? this.favIds,
|
||||||
@ -35,6 +45,7 @@ class FavState extends Equatable {
|
|||||||
status: status ?? this.status,
|
status: status ?? this.status,
|
||||||
mergeStatus: mergeStatus ?? this.mergeStatus,
|
mergeStatus: mergeStatus ?? this.mergeStatus,
|
||||||
currentPage: currentPage ?? this.currentPage,
|
currentPage: currentPage ?? this.currentPage,
|
||||||
|
isDisplayingStories: isDisplayingStories ?? this.isDisplayingStories,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,5 +56,6 @@ class FavState extends Equatable {
|
|||||||
currentPage,
|
currentPage,
|
||||||
favIds,
|
favIds,
|
||||||
favItems,
|
favItems,
|
||||||
|
isDisplayingStories,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -41,6 +41,7 @@ class BuildableComment extends Comment with Buildable {
|
|||||||
BuildableComment copyWith({
|
BuildableComment copyWith({
|
||||||
int? level,
|
int? level,
|
||||||
bool? hidden,
|
bool? hidden,
|
||||||
|
int? kid,
|
||||||
}) {
|
}) {
|
||||||
return BuildableComment(
|
return BuildableComment(
|
||||||
id: id,
|
id: id,
|
||||||
@ -49,7 +50,7 @@ class BuildableComment extends Comment with Buildable {
|
|||||||
score: score,
|
score: score,
|
||||||
by: by,
|
by: by,
|
||||||
text: text,
|
text: text,
|
||||||
kids: kids,
|
kids: kid == null ? kids : <int>[...kids, kid],
|
||||||
dead: dead,
|
dead: dead,
|
||||||
deleted: deleted,
|
deleted: deleted,
|
||||||
hidden: hidden ?? this.hidden,
|
hidden: hidden ?? this.hidden,
|
||||||
|
@ -36,6 +36,7 @@ class Comment extends Item {
|
|||||||
Comment copyWith({
|
Comment copyWith({
|
||||||
int? level,
|
int? level,
|
||||||
bool? hidden,
|
bool? hidden,
|
||||||
|
int? kid,
|
||||||
}) {
|
}) {
|
||||||
return Comment(
|
return Comment(
|
||||||
id: id,
|
id: id,
|
||||||
@ -44,7 +45,7 @@ class Comment extends Item {
|
|||||||
score: score,
|
score: score,
|
||||||
by: by,
|
by: by,
|
||||||
text: text,
|
text: text,
|
||||||
kids: kids,
|
kids: kid == null ? kids : <int>[...kids, kid],
|
||||||
dead: dead,
|
dead: dead,
|
||||||
deleted: deleted,
|
deleted: deleted,
|
||||||
hidden: hidden ?? this.hidden,
|
hidden: hidden ?? this.hidden,
|
||||||
|
@ -302,24 +302,32 @@ class HackerNewsRepository with Loggable {
|
|||||||
|
|
||||||
/// Fetch a list of [Item] based on ids and return results
|
/// Fetch a list of [Item] based on ids and return results
|
||||||
/// using a stream.
|
/// using a stream.
|
||||||
Stream<Item> fetchItemsStream({required List<int> ids}) async* {
|
Stream<Item> fetchItemsStream({
|
||||||
|
required List<int> ids,
|
||||||
|
Future<Item?> Function(int)? getFromCache,
|
||||||
|
}) async* {
|
||||||
for (final int id in ids) {
|
for (final int id in ids) {
|
||||||
final Item? item =
|
final Item? cachedItem = await getFromCache?.call(id);
|
||||||
await _fetchItemJson(id).then((Map<String, dynamic>? json) async {
|
if (cachedItem != null) {
|
||||||
if (json == null) return null;
|
yield cachedItem;
|
||||||
|
} else {
|
||||||
|
final Item? item =
|
||||||
|
await _fetchItemJson(id).then((Map<String, dynamic>? json) async {
|
||||||
|
if (json == null) return null;
|
||||||
|
|
||||||
if (json.isStory) {
|
if (json.isStory) {
|
||||||
final Story story = Story.fromJson(json);
|
final Story story = Story.fromJson(json);
|
||||||
return story;
|
return story;
|
||||||
} else if (json.isComment) {
|
} else if (json.isComment) {
|
||||||
final Comment comment = Comment.fromJson(json);
|
final Comment comment = Comment.fromJson(json);
|
||||||
return comment;
|
return comment;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (item != null) {
|
||||||
|
yield item;
|
||||||
}
|
}
|
||||||
return null;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (item != null) {
|
|
||||||
yield item;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -175,7 +175,8 @@ class HackerNewsWebRepository with Loggable {
|
|||||||
subtextElement.querySelector(_ageSelector) ??
|
subtextElement.querySelector(_ageSelector) ??
|
||||||
subtextElement.querySelector('.age');
|
subtextElement.querySelector('.age');
|
||||||
|
|
||||||
final String? dateStr = postDateElement?.attributes['title'];
|
final String? dateStr =
|
||||||
|
postDateElement?.attributes['title']?.split(' ').firstOrNull;
|
||||||
final int? timestamp = dateStr == null
|
final int? timestamp = dateStr == null
|
||||||
? null
|
? null
|
||||||
: DateTime.parse(dateStr)
|
: DateTime.parse(dateStr)
|
||||||
@ -401,7 +402,8 @@ class HackerNewsWebRepository with Loggable {
|
|||||||
/// Get comment age.
|
/// Get comment age.
|
||||||
final Element? cmtAgeElement =
|
final Element? cmtAgeElement =
|
||||||
element.querySelector(_commentAgeSelector);
|
element.querySelector(_commentAgeSelector);
|
||||||
final String? ageString = cmtAgeElement?.attributes['title'];
|
final String? ageString =
|
||||||
|
cmtAgeElement?.attributes['title']?.split(' ').firstOrNull;
|
||||||
|
|
||||||
final int? timestamp = ageString == null
|
final int? timestamp = ageString == null
|
||||||
? null
|
? null
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
|
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/services/services.dart';
|
||||||
@ -10,6 +11,8 @@ import 'package:sembast/sembast.dart';
|
|||||||
import 'package:sembast/sembast_io.dart';
|
import 'package:sembast/sembast_io.dart';
|
||||||
|
|
||||||
/// [SembastRepository] is for storing stories and comments for faster loading.
|
/// [SembastRepository] is for storing stories and comments for faster loading.
|
||||||
|
/// This is currently used by [TimeMachineCubit], [NotificationCubit] and
|
||||||
|
/// [FavCubit].
|
||||||
///
|
///
|
||||||
/// Sembast [Database] is used as its database and is being stored in the
|
/// Sembast [Database] is used as its database and is being stored in the
|
||||||
/// documents directory assigned by host system which you can retrieve
|
/// documents directory assigned by host system which you can retrieve
|
||||||
@ -67,7 +70,7 @@ class SembastRepository with Loggable {
|
|||||||
return db;
|
return db;
|
||||||
}
|
}
|
||||||
|
|
||||||
//#region Cached comments for time machine feature.
|
//#region Cached comments for time machine feature and favorites screen.
|
||||||
Future<Map<String, Object?>> cacheComment(Comment comment) async {
|
Future<Map<String, Object?>> cacheComment(Comment comment) async {
|
||||||
final Database db = _database ?? await initializeDatabase();
|
final Database db = _database ?? await initializeDatabase();
|
||||||
final StoreRef<int, Map<String, Object?>> store =
|
final StoreRef<int, Map<String, Object?>> store =
|
||||||
@ -89,7 +92,34 @@ class SembastRepository with Loggable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<int> deleteAllCachedComments() async {
|
Future<Map<String, Object?>> cacheItem(Item item) async {
|
||||||
|
final Database db = _database ?? await initializeDatabase();
|
||||||
|
final StoreRef<int, Map<String, Object?>> store =
|
||||||
|
intMapStoreFactory.store(_cachedCommentsKey);
|
||||||
|
return store.record(item.id).put(db, item.toJson());
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Item?> getCachedItem({required int id}) async {
|
||||||
|
final Database db = _database ?? await initializeDatabase();
|
||||||
|
final StoreRef<int, Map<String, Object?>> store =
|
||||||
|
intMapStoreFactory.store(_cachedCommentsKey);
|
||||||
|
final RecordSnapshot<int, Map<String, Object?>>? snapshot =
|
||||||
|
await store.record(id).getSnapshot(db);
|
||||||
|
if (snapshot != null) {
|
||||||
|
final bool isStory = snapshot['type'] == 'story';
|
||||||
|
if (isStory) {
|
||||||
|
final Story story = Story.fromJson(snapshot.value);
|
||||||
|
return story;
|
||||||
|
} else {
|
||||||
|
final Comment comment = Comment.fromJson(snapshot.value);
|
||||||
|
return comment;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<int> deleteAllCachedItems() async {
|
||||||
final Database db = _database ?? await initializeDatabase();
|
final Database db = _database ?? await initializeDatabase();
|
||||||
final StoreRef<int, Map<String, Object?>> store =
|
final StoreRef<int, Map<String, Object?>> store =
|
||||||
intMapStoreFactory.store(_cachedCommentsKey);
|
intMapStoreFactory.store(_cachedCommentsKey);
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
import 'package:flutter/gestures.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:flutter_slidable/flutter_slidable.dart';
|
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:hacki/blocs/blocs.dart';
|
import 'package:hacki/blocs/blocs.dart';
|
||||||
import 'package:hacki/config/constants.dart';
|
import 'package:hacki/config/constants.dart';
|
||||||
@ -130,124 +128,12 @@ class _ProfileScreenState extends State<ProfileScreen>
|
|||||||
top: Dimens.pt50,
|
top: Dimens.pt50,
|
||||||
child: Visibility(
|
child: Visibility(
|
||||||
visible: pageType == PageType.fav,
|
visible: pageType == PageType.fav,
|
||||||
child: BlocConsumer<FavCubit, FavState>(
|
child: FavoritesScreen(
|
||||||
listener: (BuildContext context, FavState favState) {
|
refreshController: refreshControllerFav,
|
||||||
if (favState.status == Status.success) {
|
authState: authState,
|
||||||
refreshControllerFav
|
onItemTap: (Item item) => goToItemScreen(
|
||||||
..refreshCompleted()
|
args: ItemScreenArgs(item: item),
|
||||||
..loadComplete();
|
),
|
||||||
}
|
|
||||||
},
|
|
||||||
buildWhen: (FavState previous, FavState current) =>
|
|
||||||
previous.favItems.length != current.favItems.length,
|
|
||||||
builder: (BuildContext context, FavState favState) {
|
|
||||||
Widget? header() => authState.isLoggedIn
|
|
||||||
? BlocSelector<FavCubit, FavState, Status>(
|
|
||||||
selector: (FavState state) => state.mergeStatus,
|
|
||||||
builder: (
|
|
||||||
BuildContext context,
|
|
||||||
Status status,
|
|
||||||
) {
|
|
||||||
return TextButton(
|
|
||||||
onPressed: () =>
|
|
||||||
context.read<FavCubit>().merge(
|
|
||||||
onError: (AppException e) =>
|
|
||||||
showErrorSnackBar(e.message),
|
|
||||||
onSuccess: () => showSnackBar(
|
|
||||||
content: '''Sync completed.''',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: status == Status.inProgress
|
|
||||||
? const SizedBox(
|
|
||||||
height: Dimens.pt12,
|
|
||||||
width: Dimens.pt12,
|
|
||||||
child:
|
|
||||||
CustomCircularProgressIndicator(
|
|
||||||
strokeWidth: Dimens.pt2,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: const Text('Sync from Hacker News'),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
)
|
|
||||||
: null;
|
|
||||||
|
|
||||||
if (favState.favItems.isEmpty &&
|
|
||||||
favState.status != Status.inProgress) {
|
|
||||||
return Column(
|
|
||||||
children: <Widget>[
|
|
||||||
header() ?? const SizedBox.shrink(),
|
|
||||||
const CenteredMessageView(
|
|
||||||
content:
|
|
||||||
'Your favorite stories will show up here.'
|
|
||||||
'\nThey will be synced to your Hacker '
|
|
||||||
'News account if you are logged in.',
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return BlocBuilder<PreferenceCubit, PreferenceState>(
|
|
||||||
buildWhen: (
|
|
||||||
PreferenceState previous,
|
|
||||||
PreferenceState current,
|
|
||||||
) =>
|
|
||||||
previous.isComplexStoryTileEnabled !=
|
|
||||||
current.isComplexStoryTileEnabled ||
|
|
||||||
previous.isMetadataEnabled !=
|
|
||||||
current.isMetadataEnabled ||
|
|
||||||
previous.isUrlEnabled != current.isUrlEnabled,
|
|
||||||
builder: (
|
|
||||||
BuildContext context,
|
|
||||||
PreferenceState prefState,
|
|
||||||
) {
|
|
||||||
return ItemsListView<Item>(
|
|
||||||
showWebPreviewOnStoryTile:
|
|
||||||
prefState.isComplexStoryTileEnabled,
|
|
||||||
showMetadataOnStoryTile:
|
|
||||||
prefState.isMetadataEnabled,
|
|
||||||
showFavicon: prefState.isFaviconEnabled,
|
|
||||||
showUrl: prefState.isUrlEnabled,
|
|
||||||
useSimpleTileForStory: true,
|
|
||||||
refreshController: refreshControllerFav,
|
|
||||||
items: favState.favItems,
|
|
||||||
onRefresh: () {
|
|
||||||
HapticFeedbackUtil.light();
|
|
||||||
context.read<FavCubit>().refresh();
|
|
||||||
},
|
|
||||||
onLoadMore: () {
|
|
||||||
context.read<FavCubit>().loadMore();
|
|
||||||
},
|
|
||||||
onTap: (Item item) => goToItemScreen(
|
|
||||||
args: ItemScreenArgs(item: item),
|
|
||||||
),
|
|
||||||
header: header(),
|
|
||||||
itemBuilder: (Widget child, Item item) {
|
|
||||||
return Slidable(
|
|
||||||
dragStartBehavior: DragStartBehavior.start,
|
|
||||||
startActionPane: ActionPane(
|
|
||||||
motion: const BehindMotion(),
|
|
||||||
children: <Widget>[
|
|
||||||
SlidableAction(
|
|
||||||
onPressed: (_) {
|
|
||||||
HapticFeedbackUtil.light();
|
|
||||||
context
|
|
||||||
.read<FavCubit>()
|
|
||||||
.removeFav(item.id);
|
|
||||||
},
|
|
||||||
backgroundColor: Palette.red,
|
|
||||||
foregroundColor: Palette.white,
|
|
||||||
icon: Icons.close,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
child: child,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
187
lib/screens/profile/widgets/favorites_screen.dart
Normal file
187
lib/screens/profile/widgets/favorites_screen.dart
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
import 'package:flutter/gestures.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:flutter_slidable/flutter_slidable.dart';
|
||||||
|
import 'package:hacki/blocs/auth/auth_bloc.dart';
|
||||||
|
import 'package:hacki/cubits/cubits.dart';
|
||||||
|
import 'package:hacki/extensions/extensions.dart';
|
||||||
|
import 'package:hacki/models/models.dart';
|
||||||
|
import 'package:hacki/screens/profile/widgets/centered_message_view.dart';
|
||||||
|
import 'package:hacki/screens/widgets/widgets.dart';
|
||||||
|
import 'package:hacki/styles/styles.dart';
|
||||||
|
import 'package:hacki/utils/utils.dart';
|
||||||
|
import 'package:pull_to_refresh/pull_to_refresh.dart';
|
||||||
|
|
||||||
|
class FavoritesScreen extends StatelessWidget {
|
||||||
|
const FavoritesScreen({
|
||||||
|
required this.refreshController,
|
||||||
|
required this.authState,
|
||||||
|
required this.onItemTap,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
final RefreshController refreshController;
|
||||||
|
final AuthState authState;
|
||||||
|
final void Function(Item) onItemTap;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BlocConsumer<FavCubit, FavState>(
|
||||||
|
listener: (BuildContext context, FavState favState) {
|
||||||
|
if (favState.status == Status.success) {
|
||||||
|
refreshController
|
||||||
|
..refreshCompleted()
|
||||||
|
..loadComplete();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
buildWhen: (FavState previous, FavState current) =>
|
||||||
|
previous.favItems.length != current.favItems.length ||
|
||||||
|
previous.isDisplayingStories != current.isDisplayingStories,
|
||||||
|
builder: (BuildContext context, FavState favState) {
|
||||||
|
Widget? header() => Column(
|
||||||
|
children: <Widget>[
|
||||||
|
if (authState.isLoggedIn)
|
||||||
|
BlocSelector<FavCubit, FavState, Status>(
|
||||||
|
selector: (FavState state) => state.mergeStatus,
|
||||||
|
builder: (
|
||||||
|
BuildContext context,
|
||||||
|
Status status,
|
||||||
|
) {
|
||||||
|
return TextButton(
|
||||||
|
onPressed: () => context.read<FavCubit>().merge(
|
||||||
|
onError: (AppException e) =>
|
||||||
|
context.showErrorSnackBar(e.message),
|
||||||
|
onSuccess: () => context.showSnackBar(
|
||||||
|
content: '''Sync completed.''',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: status == Status.inProgress
|
||||||
|
? const SizedBox(
|
||||||
|
height: Dimens.pt12,
|
||||||
|
width: Dimens.pt12,
|
||||||
|
child: CustomCircularProgressIndicator(
|
||||||
|
strokeWidth: Dimens.pt2,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: const Text(
|
||||||
|
'Sync from Hacker News',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
children: <Widget>[
|
||||||
|
const SizedBox(
|
||||||
|
width: Dimens.pt12,
|
||||||
|
),
|
||||||
|
CustomChip(
|
||||||
|
selected: favState.isDisplayingStories,
|
||||||
|
label: 'Story',
|
||||||
|
onSelected: (_) => context.read<FavCubit>().switchTab(),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
width: Dimens.pt12,
|
||||||
|
),
|
||||||
|
CustomChip(
|
||||||
|
selected: !favState.isDisplayingStories,
|
||||||
|
label: 'Comment',
|
||||||
|
onSelected: (_) => context.read<FavCubit>().switchTab(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
if (favState.favItems.isEmpty && favState.status != Status.inProgress) {
|
||||||
|
return Column(
|
||||||
|
children: <Widget>[
|
||||||
|
header() ?? const SizedBox.shrink(),
|
||||||
|
const CenteredMessageView(
|
||||||
|
content: 'Your favorite stories will show up here.'
|
||||||
|
'\nThey will be synced to your Hacker '
|
||||||
|
'News account if you are logged in.',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
if (favState.isDisplayingStories && favState.favStories.isEmpty) {
|
||||||
|
return Column(
|
||||||
|
children: <Widget>[
|
||||||
|
header() ?? const SizedBox.shrink(),
|
||||||
|
const CenteredMessageView(
|
||||||
|
content: 'No favorite story.',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
} else if (!favState.isDisplayingStories &&
|
||||||
|
favState.favComments.isEmpty) {
|
||||||
|
return Column(
|
||||||
|
children: <Widget>[
|
||||||
|
header() ?? const SizedBox.shrink(),
|
||||||
|
const CenteredMessageView(
|
||||||
|
content: 'No favorite comment.',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return BlocBuilder<PreferenceCubit, PreferenceState>(
|
||||||
|
buildWhen: (
|
||||||
|
PreferenceState previous,
|
||||||
|
PreferenceState current,
|
||||||
|
) =>
|
||||||
|
previous.isComplexStoryTileEnabled !=
|
||||||
|
current.isComplexStoryTileEnabled ||
|
||||||
|
previous.isMetadataEnabled != current.isMetadataEnabled ||
|
||||||
|
previous.isUrlEnabled != current.isUrlEnabled,
|
||||||
|
builder: (
|
||||||
|
BuildContext context,
|
||||||
|
PreferenceState prefState,
|
||||||
|
) {
|
||||||
|
return ItemsListView<Item>(
|
||||||
|
showWebPreviewOnStoryTile: prefState.isComplexStoryTileEnabled,
|
||||||
|
showMetadataOnStoryTile: prefState.isMetadataEnabled,
|
||||||
|
showFavicon: prefState.isFaviconEnabled,
|
||||||
|
showUrl: prefState.isUrlEnabled,
|
||||||
|
useSimpleTileForStory: true,
|
||||||
|
refreshController: refreshController,
|
||||||
|
items: favState.isDisplayingStories
|
||||||
|
? favState.favStories
|
||||||
|
: favState.favComments,
|
||||||
|
onRefresh: () {
|
||||||
|
HapticFeedbackUtil.light();
|
||||||
|
context.read<FavCubit>().refresh();
|
||||||
|
},
|
||||||
|
onLoadMore: () {
|
||||||
|
context.read<FavCubit>().loadMore();
|
||||||
|
},
|
||||||
|
onTap: onItemTap,
|
||||||
|
header: header(),
|
||||||
|
itemBuilder: (Widget child, Item item) {
|
||||||
|
return Slidable(
|
||||||
|
dragStartBehavior: DragStartBehavior.start,
|
||||||
|
startActionPane: ActionPane(
|
||||||
|
motion: const BehindMotion(),
|
||||||
|
children: <Widget>[
|
||||||
|
SlidableAction(
|
||||||
|
onPressed: (_) {
|
||||||
|
HapticFeedbackUtil.light();
|
||||||
|
context.read<FavCubit>().removeFav(item.id);
|
||||||
|
},
|
||||||
|
backgroundColor: Palette.red,
|
||||||
|
foregroundColor: Palette.white,
|
||||||
|
icon: Icons.close,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -606,7 +606,7 @@ class _SettingsState extends State<Settings> with ItemActionMixin, Loggable {
|
|||||||
context.pop();
|
context.pop();
|
||||||
locator
|
locator
|
||||||
.get<SembastRepository>()
|
.get<SembastRepository>()
|
||||||
.deleteAllCachedComments()
|
.deleteAllCachedItems()
|
||||||
.whenComplete(
|
.whenComplete(
|
||||||
locator.get<OfflineRepository>().deleteAll,
|
locator.get<OfflineRepository>().deleteAll,
|
||||||
)
|
)
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
export 'centered_message_view.dart';
|
export 'centered_message_view.dart';
|
||||||
export 'enter_offline_mode_list_tile.dart';
|
export 'enter_offline_mode_list_tile.dart';
|
||||||
|
export 'favorites_screen.dart';
|
||||||
export 'inbox_view.dart';
|
export 'inbox_view.dart';
|
||||||
export 'offline_list_tile.dart';
|
export 'offline_list_tile.dart';
|
||||||
export 'settings.dart';
|
export 'settings.dart';
|
||||||
|
@ -3,7 +3,18 @@ import 'package:hacki/models/models.dart' show Comment;
|
|||||||
class CommentCache {
|
class CommentCache {
|
||||||
static final Map<int, Comment> _comments = <int, Comment>{};
|
static final Map<int, Comment> _comments = <int, Comment>{};
|
||||||
|
|
||||||
void cacheComment(Comment comment) => _comments[comment.id] = comment;
|
void cacheComment(Comment comment) {
|
||||||
|
_comments[comment.id] = comment;
|
||||||
|
|
||||||
|
/// Comments fetched from `HackerNewsWebRepository` doesn't have populated
|
||||||
|
/// `kids` field, this is why we need to update that of the parent
|
||||||
|
/// comment here.
|
||||||
|
final int parentId = comment.parent;
|
||||||
|
final Comment? parent = _comments[parentId];
|
||||||
|
if (parent == null || parent.kids.contains(comment.id)) return;
|
||||||
|
final Comment updatedParent = parent.copyWith(kid: comment.id);
|
||||||
|
_comments[parentId] = updatedParent;
|
||||||
|
}
|
||||||
|
|
||||||
Comment? getComment(int id) => _comments[id];
|
Comment? getComment(int id) => _comments[id];
|
||||||
|
|
||||||
|
44
pubspec.lock
44
pubspec.lock
@ -250,10 +250,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: dio
|
name: dio
|
||||||
sha256: "0dfb6b6a1979dac1c1245e17cef824d7b452ea29bd33d3467269f9bef3715fb0"
|
sha256: "5598aa796bbf4699afd5c67c0f5f6e2ed542afc956884b9cd58c306966efc260"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.6.0"
|
version: "5.7.0"
|
||||||
dio_smart_retry:
|
dio_smart_retry:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -381,18 +381,18 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: flutter_inappwebview
|
name: flutter_inappwebview
|
||||||
sha256: "3e9a443a18ecef966fb930c3a76ca5ab6a7aafc0c7b5e14a4a850cf107b09959"
|
sha256: "80092d13d3e29b6227e25b67973c67c7210bd5e35c4b747ca908e31eb71a46d5"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.0.0"
|
version: "6.1.5"
|
||||||
flutter_inappwebview_android:
|
flutter_inappwebview_android:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: flutter_inappwebview_android
|
name: flutter_inappwebview_android
|
||||||
sha256: d247f6ed417f1f8c364612fa05a2ecba7f775c8d0c044c1d3b9ee33a6515c421
|
sha256: "62557c15a5c2db5d195cb3892aab74fcaec266d7b86d59a6f0027abd672cddba"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.13"
|
version: "1.1.3"
|
||||||
flutter_inappwebview_internal_annotations:
|
flutter_inappwebview_internal_annotations:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -405,34 +405,42 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: flutter_inappwebview_ios
|
name: flutter_inappwebview_ios
|
||||||
sha256: f363577208b97b10b319cd0c428555cd8493e88b468019a8c5635a0e4312bd0f
|
sha256: "5818cf9b26cf0cbb0f62ff50772217d41ea8d3d9cc00279c45f8aabaa1b4025d"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.13"
|
version: "1.1.2"
|
||||||
flutter_inappwebview_macos:
|
flutter_inappwebview_macos:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: flutter_inappwebview_macos
|
name: flutter_inappwebview_macos
|
||||||
sha256: b55b9e506c549ce88e26580351d2c71d54f4825901666bd6cfa4be9415bb2636
|
sha256: c1fbb86af1a3738e3541364d7d1866315ffb0468a1a77e34198c9be571287da1
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.11"
|
version: "1.1.2"
|
||||||
flutter_inappwebview_platform_interface:
|
flutter_inappwebview_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: flutter_inappwebview_platform_interface
|
name: flutter_inappwebview_platform_interface
|
||||||
sha256: "545fd4c25a07d2775f7d5af05a979b2cac4fbf79393b0a7f5d33ba39ba4f6187"
|
sha256: cf5323e194096b6ede7a1ca808c3e0a078e4b33cc3f6338977d75b4024ba2500
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.10"
|
version: "1.3.0+1"
|
||||||
flutter_inappwebview_web:
|
flutter_inappwebview_web:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: flutter_inappwebview_web
|
name: flutter_inappwebview_web
|
||||||
sha256: d8c680abfb6fec71609a700199635d38a744df0febd5544c5a020bd73de8ee07
|
sha256: "55f89c83b0a0d3b7893306b3bb545ba4770a4df018204917148ebb42dc14a598"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.8"
|
version: "1.1.2"
|
||||||
|
flutter_inappwebview_windows:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_inappwebview_windows
|
||||||
|
sha256: "8b4d3a46078a2cdc636c4a3d10d10f2a16882f6be607962dbfff8874d1642055"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.6.0"
|
||||||
flutter_local_notifications:
|
flutter_local_notifications:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -1458,13 +1466,13 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.0"
|
version: "1.1.0"
|
||||||
web:
|
web:
|
||||||
dependency: transitive
|
dependency: "direct overridden"
|
||||||
description:
|
description:
|
||||||
name: web
|
name: web
|
||||||
sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27"
|
sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.5.1"
|
version: "1.1.0"
|
||||||
web_socket:
|
web_socket:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -1578,5 +1586,5 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.2"
|
version: "3.1.2"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.4.0 <4.0.0"
|
dart: ">=3.5.0 <4.0.0"
|
||||||
flutter: ">=3.24.3"
|
flutter: ">=3.24.3"
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
name: hacki
|
name: hacki
|
||||||
description: A Hacker News reader.
|
description: A Hacker News reader.
|
||||||
version: 2.9.3+151
|
version: 2.9.7+155
|
||||||
publish_to: none
|
publish_to: none
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
@ -17,7 +17,7 @@ dependencies:
|
|||||||
collection: ^1.17.1
|
collection: ^1.17.1
|
||||||
connectivity_plus: ^6.0.3
|
connectivity_plus: ^6.0.3
|
||||||
device_info_plus: ^10.1.0
|
device_info_plus: ^10.1.0
|
||||||
dio: ^5.4.3+1
|
dio: ^5.7.0
|
||||||
dio_smart_retry: ^6.0.0
|
dio_smart_retry: ^6.0.0
|
||||||
equatable: ^2.0.5
|
equatable: ^2.0.5
|
||||||
fast_gbk: ^1.0.0
|
fast_gbk: ^1.0.0
|
||||||
@ -32,7 +32,7 @@ dependencies:
|
|||||||
flutter_email_sender: ^6.0.3
|
flutter_email_sender: ^6.0.3
|
||||||
flutter_fadein: ^2.0.0
|
flutter_fadein: ^2.0.0
|
||||||
flutter_feather_icons: 2.0.0+1
|
flutter_feather_icons: 2.0.0+1
|
||||||
flutter_inappwebview: ^6.0.0
|
flutter_inappwebview: ^6.1.5
|
||||||
flutter_local_notifications: ^17.1.2
|
flutter_local_notifications: ^17.1.2
|
||||||
flutter_material_color_picker: ^1.2.0
|
flutter_material_color_picker: ^1.2.0
|
||||||
flutter_native_splash: ^2.4.1
|
flutter_native_splash: ^2.4.1
|
||||||
@ -83,6 +83,9 @@ dependencies:
|
|||||||
webview_flutter: ^4.8.0
|
webview_flutter: ^4.8.0
|
||||||
workmanager: ^0.5.1
|
workmanager: ^0.5.1
|
||||||
|
|
||||||
|
dependency_overrides:
|
||||||
|
web: ^1.0.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
bloc_test: ^9.1.0
|
bloc_test: ^9.1.0
|
||||||
flutter_driver:
|
flutter_driver:
|
||||||
|
Reference in New Issue
Block a user