mirror of
https://github.com/Livinglist/Hacki.git
synced 2025-08-06 18:24:42 +08:00
Compare commits
31 Commits
Author | SHA1 | Date | |
---|---|---|---|
90ec117b4b | |||
9580e9b3e5 | |||
0afaa5a0aa | |||
fbce1dff73 | |||
b0d6561486 | |||
11639118c5 | |||
e54c893e6c | |||
8c57e5e323 | |||
b4ec7ec44e | |||
8b65256294 | |||
58f7bf14d7 | |||
d9aad3d34e | |||
ed48d95375 | |||
1eaded5694 | |||
70bb78afcb | |||
df2d2478d5 | |||
d5ae60327d | |||
615a092c1e | |||
5a7699d866 | |||
56a9bab3f2 | |||
e9bbf46b4f | |||
10f503a6c0 | |||
582f3156b2 | |||
90fb45146f | |||
c19c54e762 | |||
70e5a84b63 | |||
3a51fa83f2 | |||
cb90751330 | |||
835ed7e841 | |||
125ccd2dd1 | |||
5b991c4287 |
7
.github/workflows/publish_ios.yml
vendored
7
.github/workflows/publish_ios.yml
vendored
@ -10,7 +10,7 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build_and_publish:
|
build_and_publish:
|
||||||
runs-on: macos-latest
|
runs-on: macos-13
|
||||||
timeout-minutes: 30
|
timeout-minutes: 30
|
||||||
|
|
||||||
env:
|
env:
|
||||||
@ -19,6 +19,11 @@ jobs:
|
|||||||
BUNDLE_GEMFILE: ${{ github.workspace }}/ios/Gemfile
|
BUNDLE_GEMFILE: ${{ github.workspace }}/ios/Gemfile
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
- name: Set XCode version
|
||||||
|
uses: maxim-lobanov/setup-xcode@v1
|
||||||
|
with:
|
||||||
|
xcode-version: '15.0'
|
||||||
|
|
||||||
- name: Check out from git
|
- name: Check out from git
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
|
@ -33,7 +33,7 @@ if (keystorePropertiesFile.exists()) {
|
|||||||
|
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 33
|
compileSdkVersion 34
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility JavaVersion.VERSION_1_8
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
@ -51,7 +51,7 @@ android {
|
|||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "com.jiaqifeng.hacki"
|
applicationId "com.jiaqifeng.hacki"
|
||||||
minSdkVersion 25
|
minSdkVersion 25
|
||||||
targetSdkVersion 33
|
targetSdkVersion 34
|
||||||
versionCode flutterVersionCode.toInteger()
|
versionCode flutterVersionCode.toInteger()
|
||||||
versionName flutterVersionName
|
versionName flutterVersionName
|
||||||
}
|
}
|
||||||
|
3
fastlane/metadata/android/en-US/changelogs/135.txt
Normal file
3
fastlane/metadata/android/en-US/changelogs/135.txt
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
- Return of true dark mode.
|
||||||
|
- Better comment fetching strategy.
|
||||||
|
- Minor UI fixes.
|
@ -21,6 +21,6 @@
|
|||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>1.0</string>
|
<string>1.0</string>
|
||||||
<key>MinimumOSVersion</key>
|
<key>MinimumOSVersion</key>
|
||||||
<string>11.0</string>
|
<string>12.0</string>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
111
ios/Gemfile.lock
111
ios/Gemfile.lock
@ -1,7 +1,7 @@
|
|||||||
GEM
|
GEM
|
||||||
remote: https://rubygems.org/
|
remote: https://rubygems.org/
|
||||||
specs:
|
specs:
|
||||||
CFPropertyList (3.0.5)
|
CFPropertyList (3.0.6)
|
||||||
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,28 +9,28 @@ GEM
|
|||||||
minitest (>= 5.1)
|
minitest (>= 5.1)
|
||||||
tzinfo (~> 2.0)
|
tzinfo (~> 2.0)
|
||||||
zeitwerk (~> 2.3)
|
zeitwerk (~> 2.3)
|
||||||
addressable (2.8.1)
|
addressable (2.8.6)
|
||||||
public_suffix (>= 2.0.2, < 6.0)
|
public_suffix (>= 2.0.2, < 6.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.15)
|
||||||
atomos (0.1.3)
|
atomos (0.1.3)
|
||||||
aws-eventstream (1.2.0)
|
aws-eventstream (1.3.0)
|
||||||
aws-partitions (1.680.0)
|
aws-partitions (1.889.0)
|
||||||
aws-sdk-core (3.168.4)
|
aws-sdk-core (3.191.1)
|
||||||
aws-eventstream (~> 1, >= 1.0.2)
|
aws-eventstream (~> 1, >= 1.3.0)
|
||||||
aws-partitions (~> 1, >= 1.651.0)
|
aws-partitions (~> 1, >= 1.651.0)
|
||||||
aws-sigv4 (~> 1.5)
|
aws-sigv4 (~> 1.8)
|
||||||
jmespath (~> 1, >= 1.6.1)
|
jmespath (~> 1, >= 1.6.1)
|
||||||
aws-sdk-kms (1.61.0)
|
aws-sdk-kms (1.77.0)
|
||||||
aws-sdk-core (~> 3, >= 3.165.0)
|
aws-sdk-core (~> 3, >= 3.191.0)
|
||||||
aws-sigv4 (~> 1.1)
|
aws-sigv4 (~> 1.1)
|
||||||
aws-sdk-s3 (1.117.2)
|
aws-sdk-s3 (1.143.0)
|
||||||
aws-sdk-core (~> 3, >= 3.165.0)
|
aws-sdk-core (~> 3, >= 3.191.0)
|
||||||
aws-sdk-kms (~> 1)
|
aws-sdk-kms (~> 1)
|
||||||
aws-sigv4 (~> 1.4)
|
aws-sigv4 (~> 1.8)
|
||||||
aws-sigv4 (1.5.2)
|
aws-sigv4 (1.8.0)
|
||||||
aws-eventstream (~> 1, >= 1.0.2)
|
aws-eventstream (~> 1, >= 1.0.2)
|
||||||
babosa (1.0.4)
|
babosa (1.0.4)
|
||||||
claide (1.1.0)
|
claide (1.1.0)
|
||||||
@ -77,7 +77,7 @@ GEM
|
|||||||
highline (~> 2.0.0)
|
highline (~> 2.0.0)
|
||||||
concurrent-ruby (1.1.10)
|
concurrent-ruby (1.1.10)
|
||||||
declarative (0.0.20)
|
declarative (0.0.20)
|
||||||
digest-crc (0.6.4)
|
digest-crc (0.6.5)
|
||||||
rake (>= 12.0.0, < 14.0.0)
|
rake (>= 12.0.0, < 14.0.0)
|
||||||
domain_name (0.5.20190701)
|
domain_name (0.5.20190701)
|
||||||
unf (>= 0.0.5, < 1.0.0)
|
unf (>= 0.0.5, < 1.0.0)
|
||||||
@ -86,8 +86,8 @@ GEM
|
|||||||
escape (0.0.4)
|
escape (0.0.4)
|
||||||
ethon (0.15.0)
|
ethon (0.15.0)
|
||||||
ffi (>= 1.15.0)
|
ffi (>= 1.15.0)
|
||||||
excon (0.95.0)
|
excon (0.109.0)
|
||||||
faraday (1.10.2)
|
faraday (1.10.3)
|
||||||
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)
|
||||||
@ -115,8 +115,8 @@ GEM
|
|||||||
faraday-retry (1.0.3)
|
faraday-retry (1.0.3)
|
||||||
faraday_middleware (1.2.0)
|
faraday_middleware (1.2.0)
|
||||||
faraday (~> 1.0)
|
faraday (~> 1.0)
|
||||||
fastimage (2.2.6)
|
fastimage (2.3.0)
|
||||||
fastlane (2.211.0)
|
fastlane (2.219.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)
|
||||||
@ -135,20 +135,22 @@ GEM
|
|||||||
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)
|
||||||
|
google-cloud-env (>= 1.6.0, < 2.0.0)
|
||||||
google-cloud-storage (~> 1.31)
|
google-cloud-storage (~> 1.31)
|
||||||
highline (~> 2.0)
|
highline (~> 2.0)
|
||||||
|
http-cookie (~> 1.0.5)
|
||||||
json (< 3.0.0)
|
json (< 3.0.0)
|
||||||
jwt (>= 2.1.0, < 3)
|
jwt (>= 2.1.0, < 3)
|
||||||
mini_magick (>= 4.9.4, < 5.0.0)
|
mini_magick (>= 4.9.4, < 5.0.0)
|
||||||
multipart-post (~> 2.0.0)
|
multipart-post (>= 2.0.0, < 3.0.0)
|
||||||
naturally (~> 2.2)
|
naturally (~> 2.2)
|
||||||
optparse (~> 0.1.1)
|
optparse (>= 0.1.1)
|
||||||
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.3)
|
||||||
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 (>= 1.4.5, < 2.0.0)
|
terminal-table (~> 3)
|
||||||
tty-screen (>= 0.6.3, < 1.0.0)
|
tty-screen (>= 0.6.3, < 1.0.0)
|
||||||
tty-spinner (>= 0.8.0, < 1.0.0)
|
tty-spinner (>= 0.8.0, < 1.0.0)
|
||||||
word_wrap (~> 1.0.0)
|
word_wrap (~> 1.0.0)
|
||||||
@ -159,9 +161,9 @@ GEM
|
|||||||
fourflusher (2.3.1)
|
fourflusher (2.3.1)
|
||||||
fuzzy_match (2.0.4)
|
fuzzy_match (2.0.4)
|
||||||
gh_inspector (1.1.3)
|
gh_inspector (1.1.3)
|
||||||
google-apis-androidpublisher_v3 (0.32.0)
|
google-apis-androidpublisher_v3 (0.54.0)
|
||||||
google-apis-core (>= 0.9.1, < 2.a)
|
google-apis-core (>= 0.11.0, < 2.a)
|
||||||
google-apis-core (0.9.2)
|
google-apis-core (0.11.3)
|
||||||
addressable (~> 2.5, >= 2.5.1)
|
addressable (~> 2.5, >= 2.5.1)
|
||||||
googleauth (>= 0.16.2, < 2.a)
|
googleauth (>= 0.16.2, < 2.a)
|
||||||
httpclient (>= 2.8.1, < 3.a)
|
httpclient (>= 2.8.1, < 3.a)
|
||||||
@ -169,31 +171,29 @@ GEM
|
|||||||
representable (~> 3.0)
|
representable (~> 3.0)
|
||||||
retriable (>= 2.0, < 4.a)
|
retriable (>= 2.0, < 4.a)
|
||||||
rexml
|
rexml
|
||||||
webrick
|
google-apis-iamcredentials_v1 (0.17.0)
|
||||||
google-apis-iamcredentials_v1 (0.16.0)
|
google-apis-core (>= 0.11.0, < 2.a)
|
||||||
google-apis-core (>= 0.9.1, < 2.a)
|
google-apis-playcustomapp_v1 (0.13.0)
|
||||||
google-apis-playcustomapp_v1 (0.12.0)
|
google-apis-core (>= 0.11.0, < 2.a)
|
||||||
google-apis-core (>= 0.9.1, < 2.a)
|
google-apis-storage_v1 (0.29.0)
|
||||||
google-apis-storage_v1 (0.19.0)
|
google-apis-core (>= 0.11.0, < 2.a)
|
||||||
google-apis-core (>= 0.9.0, < 2.a)
|
google-cloud-core (1.6.1)
|
||||||
google-cloud-core (1.6.0)
|
google-cloud-env (>= 1.0, < 3.a)
|
||||||
google-cloud-env (~> 1.0)
|
|
||||||
google-cloud-errors (~> 1.0)
|
google-cloud-errors (~> 1.0)
|
||||||
google-cloud-env (1.6.0)
|
google-cloud-env (1.6.0)
|
||||||
faraday (>= 0.17.3, < 3.0)
|
faraday (>= 0.17.3, < 3.0)
|
||||||
google-cloud-errors (1.3.0)
|
google-cloud-errors (1.3.1)
|
||||||
google-cloud-storage (1.44.0)
|
google-cloud-storage (1.45.0)
|
||||||
addressable (~> 2.8)
|
addressable (~> 2.8)
|
||||||
digest-crc (~> 0.4)
|
digest-crc (~> 0.4)
|
||||||
google-apis-iamcredentials_v1 (~> 0.1)
|
google-apis-iamcredentials_v1 (~> 0.1)
|
||||||
google-apis-storage_v1 (~> 0.19.0)
|
google-apis-storage_v1 (~> 0.29.0)
|
||||||
google-cloud-core (~> 1.6)
|
google-cloud-core (~> 1.6)
|
||||||
googleauth (>= 0.16.2, < 2.a)
|
googleauth (>= 0.16.2, < 2.a)
|
||||||
mini_mime (~> 1.0)
|
mini_mime (~> 1.0)
|
||||||
googleauth (1.3.0)
|
googleauth (1.8.1)
|
||||||
faraday (>= 0.17.3, < 3.a)
|
faraday (>= 0.17.3, < 3.a)
|
||||||
jwt (>= 1.4, < 3.0)
|
jwt (>= 1.4, < 3.0)
|
||||||
memoist (~> 0.16)
|
|
||||||
multi_json (~> 1.11)
|
multi_json (~> 1.11)
|
||||||
os (>= 0.9, < 2.0)
|
os (>= 0.9, < 2.0)
|
||||||
signet (>= 0.16, < 2.a)
|
signet (>= 0.16, < 2.a)
|
||||||
@ -204,49 +204,48 @@ GEM
|
|||||||
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.6.3)
|
json (2.7.1)
|
||||||
jwt (2.5.0)
|
jwt (2.7.1)
|
||||||
memoist (0.16.2)
|
|
||||||
mini_magick (4.12.0)
|
mini_magick (4.12.0)
|
||||||
mini_mime (1.1.2)
|
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.0.0)
|
multipart-post (2.4.0)
|
||||||
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.1.1)
|
optparse (0.4.0)
|
||||||
os (1.1.4)
|
os (1.1.4)
|
||||||
plist (3.6.0)
|
plist (3.7.1)
|
||||||
public_suffix (4.0.7)
|
public_suffix (4.0.7)
|
||||||
rake (13.0.6)
|
rake (13.1.0)
|
||||||
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.5)
|
rexml (3.2.6)
|
||||||
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.3)
|
||||||
signet (0.17.0)
|
signet (0.18.0)
|
||||||
addressable (~> 2.8)
|
addressable (~> 2.8)
|
||||||
faraday (>= 0.17.5, < 3.a)
|
faraday (>= 0.17.5, < 3.a)
|
||||||
jwt (>= 1.5, < 3.0)
|
jwt (>= 1.5, < 3.0)
|
||||||
multi_json (~> 1.10)
|
multi_json (~> 1.10)
|
||||||
simctl (1.6.8)
|
simctl (1.6.10)
|
||||||
CFPropertyList
|
CFPropertyList
|
||||||
naturally
|
naturally
|
||||||
terminal-notifier (2.0.0)
|
terminal-notifier (2.0.0)
|
||||||
terminal-table (1.8.0)
|
terminal-table (3.0.2)
|
||||||
unicode-display_width (~> 1.1, >= 1.1.1)
|
unicode-display_width (>= 1.1.1, < 3)
|
||||||
trailblazer-option (0.1.2)
|
trailblazer-option (0.1.2)
|
||||||
tty-cursor (0.7.1)
|
tty-cursor (0.7.1)
|
||||||
tty-screen (0.8.1)
|
tty-screen (0.8.2)
|
||||||
tty-spinner (0.9.3)
|
tty-spinner (0.9.3)
|
||||||
tty-cursor (~> 0.7)
|
tty-cursor (~> 0.7)
|
||||||
typhoeus (1.4.0)
|
typhoeus (1.4.0)
|
||||||
@ -256,11 +255,10 @@ GEM
|
|||||||
uber (0.1.0)
|
uber (0.1.0)
|
||||||
unf (0.1.4)
|
unf (0.1.4)
|
||||||
unf_ext
|
unf_ext
|
||||||
unf_ext (0.0.8.2)
|
unf_ext (0.0.9.1)
|
||||||
unicode-display_width (1.8.0)
|
unicode-display_width (2.5.0)
|
||||||
webrick (1.7.0)
|
|
||||||
word_wrap (1.0.0)
|
word_wrap (1.0.0)
|
||||||
xcodeproj (1.22.0)
|
xcodeproj (1.24.0)
|
||||||
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)
|
||||||
@ -275,6 +273,7 @@ GEM
|
|||||||
|
|
||||||
PLATFORMS
|
PLATFORMS
|
||||||
universal-darwin-21
|
universal-darwin-21
|
||||||
|
universal-darwin-23
|
||||||
x86_64-darwin-19
|
x86_64-darwin-19
|
||||||
|
|
||||||
DEPENDENCIES
|
DEPENDENCIES
|
||||||
|
@ -34,5 +34,8 @@ end
|
|||||||
post_install do |installer|
|
post_install do |installer|
|
||||||
installer.pods_project.targets.each do |target|
|
installer.pods_project.targets.each do |target|
|
||||||
flutter_additional_ios_build_settings(target)
|
flutter_additional_ios_build_settings(target)
|
||||||
|
target.build_configurations.each do |config|
|
||||||
|
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = 15.0
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -7,11 +7,11 @@ PODS:
|
|||||||
- Flutter (1.0.0)
|
- Flutter (1.0.0)
|
||||||
- flutter_email_sender (0.0.1):
|
- flutter_email_sender (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- flutter_inappwebview (0.0.1):
|
- flutter_inappwebview_ios (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- flutter_inappwebview/Core (= 0.0.1)
|
- flutter_inappwebview_ios/Core (= 0.0.1)
|
||||||
- OrderedSet (~> 5.0)
|
- OrderedSet (~> 5.0)
|
||||||
- flutter_inappwebview/Core (0.0.1):
|
- flutter_inappwebview_ios/Core (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- OrderedSet (~> 5.0)
|
- OrderedSet (~> 5.0)
|
||||||
- flutter_local_notifications (0.0.1):
|
- flutter_local_notifications (0.0.1):
|
||||||
@ -20,9 +20,6 @@ PODS:
|
|||||||
- Flutter
|
- Flutter
|
||||||
- flutter_siri_suggestions (0.0.1):
|
- flutter_siri_suggestions (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- FMDB (2.7.5):
|
|
||||||
- FMDB/standard (= 2.7.5)
|
|
||||||
- FMDB/standard (2.7.5)
|
|
||||||
- in_app_review (0.2.0):
|
- in_app_review (0.2.0):
|
||||||
- Flutter
|
- Flutter
|
||||||
- integration_test (0.0.1):
|
- integration_test (0.0.1):
|
||||||
@ -38,7 +35,7 @@ PODS:
|
|||||||
- Flutter
|
- Flutter
|
||||||
- MTBBarcodeScanner
|
- MTBBarcodeScanner
|
||||||
- ReachabilitySwift (5.0.0)
|
- ReachabilitySwift (5.0.0)
|
||||||
- receive_sharing_intent (0.0.1):
|
- receive_sharing_intent (1.5.3):
|
||||||
- Flutter
|
- Flutter
|
||||||
- share_plus (0.0.1):
|
- share_plus (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
@ -47,7 +44,7 @@ PODS:
|
|||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- sqflite (0.0.3):
|
- sqflite (0.0.3):
|
||||||
- Flutter
|
- Flutter
|
||||||
- FMDB (>= 2.7.5)
|
- FlutterMacOS
|
||||||
- synced_shared_preferences (0.0.1):
|
- synced_shared_preferences (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- url_launcher_ios (0.0.1):
|
- url_launcher_ios (0.0.1):
|
||||||
@ -64,7 +61,7 @@ DEPENDENCIES:
|
|||||||
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
|
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
|
||||||
- Flutter (from `Flutter`)
|
- Flutter (from `Flutter`)
|
||||||
- flutter_email_sender (from `.symlinks/plugins/flutter_email_sender/ios`)
|
- flutter_email_sender (from `.symlinks/plugins/flutter_email_sender/ios`)
|
||||||
- flutter_inappwebview (from `.symlinks/plugins/flutter_inappwebview/ios`)
|
- flutter_inappwebview_ios (from `.symlinks/plugins/flutter_inappwebview_ios/ios`)
|
||||||
- flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`)
|
- flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`)
|
||||||
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
|
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
|
||||||
- flutter_siri_suggestions (from `.symlinks/plugins/flutter_siri_suggestions/ios`)
|
- flutter_siri_suggestions (from `.symlinks/plugins/flutter_siri_suggestions/ios`)
|
||||||
@ -76,7 +73,7 @@ DEPENDENCIES:
|
|||||||
- receive_sharing_intent (from `.symlinks/plugins/receive_sharing_intent/ios`)
|
- receive_sharing_intent (from `.symlinks/plugins/receive_sharing_intent/ios`)
|
||||||
- share_plus (from `.symlinks/plugins/share_plus/ios`)
|
- share_plus (from `.symlinks/plugins/share_plus/ios`)
|
||||||
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
||||||
- sqflite (from `.symlinks/plugins/sqflite/ios`)
|
- sqflite (from `.symlinks/plugins/sqflite/darwin`)
|
||||||
- synced_shared_preferences (from `.symlinks/plugins/synced_shared_preferences/ios`)
|
- synced_shared_preferences (from `.symlinks/plugins/synced_shared_preferences/ios`)
|
||||||
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
||||||
- wakelock (from `.symlinks/plugins/wakelock/ios`)
|
- wakelock (from `.symlinks/plugins/wakelock/ios`)
|
||||||
@ -85,7 +82,6 @@ DEPENDENCIES:
|
|||||||
|
|
||||||
SPEC REPOS:
|
SPEC REPOS:
|
||||||
trunk:
|
trunk:
|
||||||
- FMDB
|
|
||||||
- MTBBarcodeScanner
|
- MTBBarcodeScanner
|
||||||
- OrderedSet
|
- OrderedSet
|
||||||
- ReachabilitySwift
|
- ReachabilitySwift
|
||||||
@ -99,8 +95,8 @@ EXTERNAL SOURCES:
|
|||||||
:path: Flutter
|
:path: Flutter
|
||||||
flutter_email_sender:
|
flutter_email_sender:
|
||||||
:path: ".symlinks/plugins/flutter_email_sender/ios"
|
:path: ".symlinks/plugins/flutter_email_sender/ios"
|
||||||
flutter_inappwebview:
|
flutter_inappwebview_ios:
|
||||||
:path: ".symlinks/plugins/flutter_inappwebview/ios"
|
:path: ".symlinks/plugins/flutter_inappwebview_ios/ios"
|
||||||
flutter_local_notifications:
|
flutter_local_notifications:
|
||||||
:path: ".symlinks/plugins/flutter_local_notifications/ios"
|
:path: ".symlinks/plugins/flutter_local_notifications/ios"
|
||||||
flutter_secure_storage:
|
flutter_secure_storage:
|
||||||
@ -124,7 +120,7 @@ EXTERNAL SOURCES:
|
|||||||
shared_preferences_foundation:
|
shared_preferences_foundation:
|
||||||
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
|
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
|
||||||
sqflite:
|
sqflite:
|
||||||
:path: ".symlinks/plugins/sqflite/ios"
|
:path: ".symlinks/plugins/sqflite/darwin"
|
||||||
synced_shared_preferences:
|
synced_shared_preferences:
|
||||||
:path: ".symlinks/plugins/synced_shared_preferences/ios"
|
:path: ".symlinks/plugins/synced_shared_preferences/ios"
|
||||||
url_launcher_ios:
|
url_launcher_ios:
|
||||||
@ -139,31 +135,30 @@ EXTERNAL SOURCES:
|
|||||||
SPEC CHECKSUMS:
|
SPEC CHECKSUMS:
|
||||||
connectivity_plus: bf0076dd84a130856aa636df1c71ccaff908fa1d
|
connectivity_plus: bf0076dd84a130856aa636df1c71ccaff908fa1d
|
||||||
device_info_plus: c6fb39579d0f423935b0c9ce7ee2f44b71b9fce6
|
device_info_plus: c6fb39579d0f423935b0c9ce7ee2f44b71b9fce6
|
||||||
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
|
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
|
||||||
flutter_email_sender: 02d7443217d8c41483223627972bfdc09f74276b
|
flutter_email_sender: 02d7443217d8c41483223627972bfdc09f74276b
|
||||||
flutter_inappwebview: 3d32228f1304635e7c028b0d4252937730bbc6cf
|
flutter_inappwebview_ios: 97215cf7d4677db55df76782dbd2930c5e1c1ea0
|
||||||
flutter_local_notifications: 0c0b1ae97e741e1521e4c1629a459d04b9aec743
|
flutter_local_notifications: 4cde75091f6327eb8517fa068a0a5950212d2086
|
||||||
flutter_secure_storage: 23fc622d89d073675f2eaa109381aefbcf5a49be
|
flutter_secure_storage: 23fc622d89d073675f2eaa109381aefbcf5a49be
|
||||||
flutter_siri_suggestions: 226fb7ef33d25d3fe0d4aa2a8bcf4b72730c466f
|
flutter_siri_suggestions: 226fb7ef33d25d3fe0d4aa2a8bcf4b72730c466f
|
||||||
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
|
|
||||||
in_app_review: 318597b3a06c22bb46dc454d56828c85f444f99d
|
in_app_review: 318597b3a06c22bb46dc454d56828c85f444f99d
|
||||||
integration_test: 13825b8a9334a850581300559b8839134b124670
|
integration_test: 13825b8a9334a850581300559b8839134b124670
|
||||||
MTBBarcodeScanner: f453b33c4b7dfe545d8c6484ed744d55671788cb
|
MTBBarcodeScanner: f453b33c4b7dfe545d8c6484ed744d55671788cb
|
||||||
OrderedSet: aaeb196f7fef5a9edf55d89760da9176ad40b93c
|
OrderedSet: aaeb196f7fef5a9edf55d89760da9176ad40b93c
|
||||||
package_info_plus: fd030dabf36271f146f1f3beacd48f564b0f17f7
|
package_info_plus: 115f4ad11e0698c8c1c5d8a689390df880f47e85
|
||||||
path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943
|
path_provider_foundation: 3784922295ac71e43754bd15e0653ccfd36a147c
|
||||||
qr_code_scanner: bb67d64904c3b9658ada8c402e8b4d406d5d796e
|
qr_code_scanner: bb67d64904c3b9658ada8c402e8b4d406d5d796e
|
||||||
ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
|
ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
|
||||||
receive_sharing_intent: c0d87310754e74c0f9542947e7cbdf3a0335a3b1
|
receive_sharing_intent: 753f808c6be5550247f6a20f2a14972466a5f33c
|
||||||
share_plus: c3fef564749587fc939ef86ffb283ceac0baf9f5
|
share_plus: c3fef564749587fc939ef86ffb283ceac0baf9f5
|
||||||
shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126
|
shared_preferences_foundation: b4c3b4cddf1c21f02770737f147a3f5da9d39695
|
||||||
sqflite: 31f7eba61e3074736dff8807a9b41581e4f7f15a
|
sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec
|
||||||
synced_shared_preferences: f722742b06d65c7315b8e9f56b794c9fbd5597f7
|
synced_shared_preferences: f722742b06d65c7315b8e9f56b794c9fbd5597f7
|
||||||
url_launcher_ios: 68d46cc9766d0c41dbdc884310529557e3cd7a86
|
url_launcher_ios: bbd758c6e7f9fd7b5b1d4cde34d2b95fcce5e812
|
||||||
wakelock: d0fc7c864128eac40eba1617cb5264d9c940b46f
|
wakelock: d0fc7c864128eac40eba1617cb5264d9c940b46f
|
||||||
webview_flutter_wkwebview: 2e2d318f21a5e036e2c3f26171342e95908bd60a
|
webview_flutter_wkwebview: be0f0d33777f1bfd0c9fdcb594786704dbf65f36
|
||||||
workmanager: 0afdcf5628bbde6924c21af7836fed07b42e30e6
|
workmanager: 0afdcf5628bbde6924c21af7836fed07b42e30e6
|
||||||
|
|
||||||
PODFILE CHECKSUM: d28e9a1c7bee335d05ddd795703aad5bf05bb937
|
PODFILE CHECKSUM: 0957b955069bb512c22bae4cadad9f4c34161dbe
|
||||||
|
|
||||||
COCOAPODS: 1.13.0
|
COCOAPODS: 1.13.0
|
||||||
|
@ -230,13 +230,13 @@
|
|||||||
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
|
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
|
||||||
buildPhases = (
|
buildPhases = (
|
||||||
E2E6E097A94005D9196D0A71 /* [CP] Check Pods Manifest.lock */,
|
E2E6E097A94005D9196D0A71 /* [CP] Check Pods Manifest.lock */,
|
||||||
|
9705A1C41CF9048500538489 /* Embed Frameworks */,
|
||||||
|
E51D52B8283B464E00FC8DD8 /* Embed App Extensions */,
|
||||||
9740EEB61CF901F6004384FC /* Run Script */,
|
9740EEB61CF901F6004384FC /* Run Script */,
|
||||||
97C146EA1CF9000F007C117D /* Sources */,
|
97C146EA1CF9000F007C117D /* Sources */,
|
||||||
97C146EB1CF9000F007C117D /* Frameworks */,
|
97C146EB1CF9000F007C117D /* Frameworks */,
|
||||||
97C146EC1CF9000F007C117D /* Resources */,
|
97C146EC1CF9000F007C117D /* Resources */,
|
||||||
9705A1C41CF9048500538489 /* Embed Frameworks */,
|
|
||||||
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
|
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
|
||||||
E51D52B8283B464E00FC8DD8 /* Embed App Extensions */,
|
|
||||||
F1959755D5521D58CA193498 /* [CP] Embed Pods Frameworks */,
|
F1959755D5521D58CA193498 /* [CP] Embed Pods Frameworks */,
|
||||||
);
|
);
|
||||||
buildRules = (
|
buildRules = (
|
||||||
@ -291,7 +291,7 @@
|
|||||||
isa = PBXProject;
|
isa = PBXProject;
|
||||||
attributes = {
|
attributes = {
|
||||||
LastSwiftUpdateCheck = 1330;
|
LastSwiftUpdateCheck = 1330;
|
||||||
LastUpgradeCheck = 1430;
|
LastUpgradeCheck = 1510;
|
||||||
ORGANIZATIONNAME = "";
|
ORGANIZATIONNAME = "";
|
||||||
TargetAttributes = {
|
TargetAttributes = {
|
||||||
97C146ED1CF9000F007C117D = {
|
97C146ED1CF9000F007C117D = {
|
||||||
@ -548,7 +548,7 @@
|
|||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 15;
|
||||||
MTL_ENABLE_DEBUG_INFO = NO;
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
SUPPORTED_PLATFORMS = iphoneos;
|
SUPPORTED_PLATFORMS = iphoneos;
|
||||||
@ -636,7 +636,7 @@
|
|||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 15;
|
||||||
MTL_ENABLE_DEBUG_INFO = YES;
|
MTL_ENABLE_DEBUG_INFO = YES;
|
||||||
ONLY_ACTIVE_ARCH = YES;
|
ONLY_ACTIVE_ARCH = YES;
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
@ -685,7 +685,7 @@
|
|||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 15;
|
||||||
MTL_ENABLE_DEBUG_INFO = NO;
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
SUPPORTED_PLATFORMS = iphoneos;
|
SUPPORTED_PLATFORMS = iphoneos;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Scheme
|
<Scheme
|
||||||
LastUpgradeVersion = "1430"
|
LastUpgradeVersion = "1510"
|
||||||
version = "1.3">
|
version = "1.3">
|
||||||
<BuildAction
|
<BuildAction
|
||||||
parallelizeBuildables = "YES"
|
parallelizeBuildables = "YES"
|
||||||
|
@ -114,6 +114,6 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
|||||||
|
|
||||||
await _authRepository.logout();
|
await _authRepository.logout();
|
||||||
await _preferenceRepository.updateUnreadCommentsIds(<int>[]);
|
await _preferenceRepository.updateUnreadCommentsIds(<int>[]);
|
||||||
await _sembastRepository.deleteAll();
|
await _sembastRepository.deleteCachedComments();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,7 +35,7 @@ class StoriesBloc extends Bloc<StoriesEvent, StoriesState> {
|
|||||||
super(const StoriesState.init()) {
|
super(const StoriesState.init()) {
|
||||||
on<LoadStories>(
|
on<LoadStories>(
|
||||||
onLoadStories,
|
onLoadStories,
|
||||||
transformer: sequential(),
|
transformer: concurrent(),
|
||||||
);
|
);
|
||||||
on<StoriesInitialize>(onInitialize);
|
on<StoriesInitialize>(onInitialize);
|
||||||
on<StoriesRefresh>(onRefresh);
|
on<StoriesRefresh>(onRefresh);
|
||||||
|
@ -83,4 +83,7 @@ abstract class AppDurations {
|
|||||||
static const Duration oneSecond = Duration(seconds: 1);
|
static const Duration oneSecond = Duration(seconds: 1);
|
||||||
static const Duration twoSeconds = Duration(seconds: 2);
|
static const Duration twoSeconds = Duration(seconds: 2);
|
||||||
static const Duration tenSeconds = Duration(seconds: 10);
|
static const Duration tenSeconds = Duration(seconds: 10);
|
||||||
|
static const Duration sec30 = Duration(seconds: 30);
|
||||||
|
static const Duration oneMinute = Duration(minutes: 1);
|
||||||
|
static const Duration twoMinutes = Duration(minutes: 2);
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ import 'dart:math';
|
|||||||
|
|
||||||
import 'package:bloc/bloc.dart';
|
import 'package:bloc/bloc.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
|
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:hacki/config/constants.dart';
|
import 'package:hacki/config/constants.dart';
|
||||||
@ -25,6 +26,7 @@ part 'comments_state.dart';
|
|||||||
class CommentsCubit extends Cubit<CommentsState> {
|
class CommentsCubit extends Cubit<CommentsState> {
|
||||||
CommentsCubit({
|
CommentsCubit({
|
||||||
required FilterCubit filterCubit,
|
required FilterCubit filterCubit,
|
||||||
|
required PreferenceCubit preferenceCubit,
|
||||||
required CollapseCache collapseCache,
|
required CollapseCache collapseCache,
|
||||||
required bool isOfflineReading,
|
required bool isOfflineReading,
|
||||||
required Item item,
|
required Item item,
|
||||||
@ -34,8 +36,10 @@ class CommentsCubit extends Cubit<CommentsState> {
|
|||||||
OfflineRepository? offlineRepository,
|
OfflineRepository? offlineRepository,
|
||||||
SembastRepository? sembastRepository,
|
SembastRepository? sembastRepository,
|
||||||
HackerNewsRepository? hackerNewsRepository,
|
HackerNewsRepository? hackerNewsRepository,
|
||||||
|
HackerNewsWebRepository? hackerNewsWebRepository,
|
||||||
Logger? logger,
|
Logger? logger,
|
||||||
}) : _filterCubit = filterCubit,
|
}) : _filterCubit = filterCubit,
|
||||||
|
_preferenceCubit = preferenceCubit,
|
||||||
_collapseCache = collapseCache,
|
_collapseCache = collapseCache,
|
||||||
_commentCache = commentCache ?? locator.get<CommentCache>(),
|
_commentCache = commentCache ?? locator.get<CommentCache>(),
|
||||||
_offlineRepository =
|
_offlineRepository =
|
||||||
@ -44,6 +48,8 @@ class CommentsCubit extends Cubit<CommentsState> {
|
|||||||
sembastRepository ?? locator.get<SembastRepository>(),
|
sembastRepository ?? locator.get<SembastRepository>(),
|
||||||
_hackerNewsRepository =
|
_hackerNewsRepository =
|
||||||
hackerNewsRepository ?? locator.get<HackerNewsRepository>(),
|
hackerNewsRepository ?? locator.get<HackerNewsRepository>(),
|
||||||
|
_hackerNewsWebRepository =
|
||||||
|
hackerNewsWebRepository ?? locator.get<HackerNewsWebRepository>(),
|
||||||
_logger = logger ?? locator.get<Logger>(),
|
_logger = logger ?? locator.get<Logger>(),
|
||||||
super(
|
super(
|
||||||
CommentsState.init(
|
CommentsState.init(
|
||||||
@ -55,11 +61,13 @@ class CommentsCubit extends Cubit<CommentsState> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
final FilterCubit _filterCubit;
|
final FilterCubit _filterCubit;
|
||||||
|
final PreferenceCubit _preferenceCubit;
|
||||||
final CollapseCache _collapseCache;
|
final CollapseCache _collapseCache;
|
||||||
final CommentCache _commentCache;
|
final CommentCache _commentCache;
|
||||||
final OfflineRepository _offlineRepository;
|
final OfflineRepository _offlineRepository;
|
||||||
final SembastRepository _sembastRepository;
|
final SembastRepository _sembastRepository;
|
||||||
final HackerNewsRepository _hackerNewsRepository;
|
final HackerNewsRepository _hackerNewsRepository;
|
||||||
|
final HackerNewsWebRepository _hackerNewsWebRepository;
|
||||||
final Logger _logger;
|
final Logger _logger;
|
||||||
|
|
||||||
final ItemScrollController itemScrollController = ItemScrollController();
|
final ItemScrollController itemScrollController = ItemScrollController();
|
||||||
@ -75,6 +83,30 @@ class CommentsCubit extends Cubit<CommentsState> {
|
|||||||
final Map<int, StreamSubscription<Comment>> _streamSubscriptions =
|
final Map<int, StreamSubscription<Comment>> _streamSubscriptions =
|
||||||
<int, StreamSubscription<Comment>>{};
|
<int, StreamSubscription<Comment>>{};
|
||||||
|
|
||||||
|
static const int _webFetchingCmtCountLowerLimit = 50;
|
||||||
|
|
||||||
|
Future<bool> get _shouldFetchFromWeb async {
|
||||||
|
final bool isOnWifi = await _isOnWifi;
|
||||||
|
if (isOnWifi) {
|
||||||
|
return switch (state.item) {
|
||||||
|
Story(descendants: final int descendants)
|
||||||
|
when descendants > _webFetchingCmtCountLowerLimit =>
|
||||||
|
true,
|
||||||
|
Comment(kids: final List<int> kids)
|
||||||
|
when kids.length > _webFetchingCmtCountLowerLimit =>
|
||||||
|
true,
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<bool> get _isOnWifi async {
|
||||||
|
final ConnectivityResult status = await Connectivity().checkConnectivity();
|
||||||
|
return status == ConnectivityResult.wifi;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void emit(CommentsState state) {
|
void emit(CommentsState state) {
|
||||||
if (!isClosed) {
|
if (!isClosed) {
|
||||||
@ -86,6 +118,8 @@ class CommentsCubit extends Cubit<CommentsState> {
|
|||||||
bool onlyShowTargetComment = false,
|
bool onlyShowTargetComment = false,
|
||||||
bool useCommentCache = false,
|
bool useCommentCache = false,
|
||||||
List<Comment>? targetAncestors,
|
List<Comment>? targetAncestors,
|
||||||
|
AppExceptionHandler? onError,
|
||||||
|
bool fetchFromWeb = true,
|
||||||
}) async {
|
}) async {
|
||||||
if (onlyShowTargetComment && (targetAncestors?.isNotEmpty ?? false)) {
|
if (onlyShowTargetComment && (targetAncestors?.isNotEmpty ?? false)) {
|
||||||
emit(
|
emit(
|
||||||
@ -143,11 +177,49 @@ class CommentsCubit extends Cubit<CommentsState> {
|
|||||||
getFromCache: useCommentCache ? _commentCache.getComment : null,
|
getFromCache: useCommentCache ? _commentCache.getComment : null,
|
||||||
);
|
);
|
||||||
case FetchMode.eager:
|
case FetchMode.eager:
|
||||||
commentStream =
|
switch (state.order) {
|
||||||
_hackerNewsRepository.fetchAllCommentsRecursivelyStream(
|
case CommentsOrder.natural:
|
||||||
ids: kids,
|
final bool shouldFetchFromWeb = await _shouldFetchFromWeb;
|
||||||
getFromCache: useCommentCache ? _commentCache.getComment : null,
|
if (fetchFromWeb && shouldFetchFromWeb) {
|
||||||
);
|
_logger.d('fetching from web.');
|
||||||
|
commentStream = _hackerNewsWebRepository
|
||||||
|
.fetchCommentsStream(state.item)
|
||||||
|
.handleError((dynamic e) {
|
||||||
|
_streamSubscription?.cancel();
|
||||||
|
|
||||||
|
_logger.e(e);
|
||||||
|
|
||||||
|
switch (e.runtimeType) {
|
||||||
|
case RateLimitedException:
|
||||||
|
case RateLimitedWithFallbackException:
|
||||||
|
case PossibleParsingException:
|
||||||
|
if (_preferenceCubit.state.devModeEnabled) {
|
||||||
|
onError?.call(e as AppException);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If fetching from web failed, fetch using API instead.
|
||||||
|
refresh(onError: onError, fetchFromWeb: false);
|
||||||
|
default:
|
||||||
|
onError?.call(GenericException());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
_logger.d('fetching from API.');
|
||||||
|
commentStream =
|
||||||
|
_hackerNewsRepository.fetchAllCommentsRecursivelyStream(
|
||||||
|
ids: kids,
|
||||||
|
getFromCache:
|
||||||
|
useCommentCache ? _commentCache.getComment : null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
case CommentsOrder.oldestFirst:
|
||||||
|
case CommentsOrder.newestFirst:
|
||||||
|
commentStream =
|
||||||
|
_hackerNewsRepository.fetchAllCommentsRecursivelyStream(
|
||||||
|
ids: kids,
|
||||||
|
getFromCache: useCommentCache ? _commentCache.getComment : null,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -158,7 +230,10 @@ class CommentsCubit extends Cubit<CommentsState> {
|
|||||||
..onDone(_onDone);
|
..onDone(_onDone);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> refresh() async {
|
Future<void> refresh({
|
||||||
|
required AppExceptionHandler? onError,
|
||||||
|
bool fetchFromWeb = true,
|
||||||
|
}) async {
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
status: CommentsStatus.inProgress,
|
status: CommentsStatus.inProgress,
|
||||||
@ -195,14 +270,47 @@ class CommentsCubit extends Cubit<CommentsState> {
|
|||||||
final List<int> kids = _sortKids(updatedItem.kids);
|
final List<int> kids = _sortKids(updatedItem.kids);
|
||||||
|
|
||||||
late final Stream<Comment> commentStream;
|
late final Stream<Comment> commentStream;
|
||||||
if (state.fetchMode == FetchMode.lazy) {
|
|
||||||
commentStream = _hackerNewsRepository.fetchCommentsStream(
|
switch (state.fetchMode) {
|
||||||
ids: kids,
|
case FetchMode.lazy:
|
||||||
);
|
commentStream = _hackerNewsRepository.fetchCommentsStream(ids: kids);
|
||||||
} else {
|
case FetchMode.eager:
|
||||||
commentStream = _hackerNewsRepository.fetchAllCommentsRecursivelyStream(
|
switch (state.order) {
|
||||||
ids: kids,
|
case CommentsOrder.natural:
|
||||||
);
|
final bool shouldFetchFromWeb = await _shouldFetchFromWeb;
|
||||||
|
if (fetchFromWeb && shouldFetchFromWeb) {
|
||||||
|
_logger.d('fetching from web.');
|
||||||
|
commentStream = _hackerNewsWebRepository
|
||||||
|
.fetchCommentsStream(state.item)
|
||||||
|
.handleError((dynamic e) {
|
||||||
|
_logger.e(e);
|
||||||
|
|
||||||
|
switch (e.runtimeType) {
|
||||||
|
case RateLimitedException:
|
||||||
|
case RateLimitedWithFallbackException:
|
||||||
|
case PossibleParsingException:
|
||||||
|
if (_preferenceCubit.state.devModeEnabled) {
|
||||||
|
onError?.call(e as AppException);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If fetching from web failed, fetch using API instead.
|
||||||
|
refresh(onError: onError, fetchFromWeb: false);
|
||||||
|
default:
|
||||||
|
onError?.call(GenericException());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
_logger.d('fetching from API.');
|
||||||
|
commentStream = _hackerNewsRepository
|
||||||
|
.fetchAllCommentsRecursivelyStream(ids: kids);
|
||||||
|
}
|
||||||
|
case CommentsOrder.oldestFirst:
|
||||||
|
case CommentsOrder.newestFirst:
|
||||||
|
commentStream =
|
||||||
|
_hackerNewsRepository.fetchAllCommentsRecursivelyStream(
|
||||||
|
ids: kids,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_streamSubscription = commentStream
|
_streamSubscription = commentStream
|
||||||
|
@ -2,11 +2,13 @@ import 'dart:async';
|
|||||||
import 'dart:collection';
|
import 'dart:collection';
|
||||||
|
|
||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:hacki/blocs/blocs.dart';
|
import 'package:hacki/blocs/blocs.dart';
|
||||||
import 'package:hacki/config/locator.dart';
|
import 'package:hacki/config/locator.dart';
|
||||||
import 'package:hacki/models/models.dart';
|
import 'package:hacki/models/models.dart';
|
||||||
import 'package:hacki/repositories/repositories.dart';
|
import 'package:hacki/repositories/repositories.dart';
|
||||||
|
import 'package:logger/logger.dart';
|
||||||
|
|
||||||
part 'fav_state.dart';
|
part 'fav_state.dart';
|
||||||
|
|
||||||
@ -17,6 +19,7 @@ class FavCubit extends Cubit<FavState> {
|
|||||||
PreferenceRepository? preferenceRepository,
|
PreferenceRepository? preferenceRepository,
|
||||||
HackerNewsRepository? hackerNewsRepository,
|
HackerNewsRepository? hackerNewsRepository,
|
||||||
HackerNewsWebRepository? hackerNewsWebRepository,
|
HackerNewsWebRepository? hackerNewsWebRepository,
|
||||||
|
Logger? logger,
|
||||||
}) : _authBloc = authBloc,
|
}) : _authBloc = authBloc,
|
||||||
_authRepository = authRepository ?? locator.get<AuthRepository>(),
|
_authRepository = authRepository ?? locator.get<AuthRepository>(),
|
||||||
_preferenceRepository =
|
_preferenceRepository =
|
||||||
@ -25,6 +28,7 @@ class FavCubit extends Cubit<FavState> {
|
|||||||
hackerNewsRepository ?? locator.get<HackerNewsRepository>(),
|
hackerNewsRepository ?? locator.get<HackerNewsRepository>(),
|
||||||
_hackerNewsWebRepository =
|
_hackerNewsWebRepository =
|
||||||
hackerNewsWebRepository ?? locator.get<HackerNewsWebRepository>(),
|
hackerNewsWebRepository ?? locator.get<HackerNewsWebRepository>(),
|
||||||
|
_logger = logger ?? locator.get<Logger>(),
|
||||||
super(FavState.init()) {
|
super(FavState.init()) {
|
||||||
init();
|
init();
|
||||||
}
|
}
|
||||||
@ -34,6 +38,7 @@ class FavCubit extends Cubit<FavState> {
|
|||||||
final PreferenceRepository _preferenceRepository;
|
final PreferenceRepository _preferenceRepository;
|
||||||
final HackerNewsRepository _hackerNewsRepository;
|
final HackerNewsRepository _hackerNewsRepository;
|
||||||
final HackerNewsWebRepository _hackerNewsWebRepository;
|
final HackerNewsWebRepository _hackerNewsWebRepository;
|
||||||
|
final Logger _logger;
|
||||||
late final StreamSubscription<String>? _usernameSubscription;
|
late final StreamSubscription<String>? _usernameSubscription;
|
||||||
static const int _pageSize = 20;
|
static const int _pageSize = 20;
|
||||||
|
|
||||||
@ -93,7 +98,9 @@ class FavCubit extends Cubit<FavState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void removeFav(int id) {
|
void removeFav(int id) {
|
||||||
_preferenceRepository.removeFav(username: username, id: id);
|
_preferenceRepository
|
||||||
|
..removeFav(username: username, id: id)
|
||||||
|
..removeFav(username: '', id: id);
|
||||||
|
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
@ -167,20 +174,31 @@ class FavCubit extends Cubit<FavState> {
|
|||||||
emit(FavState.init());
|
emit(FavState.init());
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> merge() async {
|
Future<void> merge({
|
||||||
|
required AppExceptionHandler onError,
|
||||||
|
required VoidCallback onSuccess,
|
||||||
|
}) async {
|
||||||
if (_authBloc.state.isLoggedIn) {
|
if (_authBloc.state.isLoggedIn) {
|
||||||
emit(state.copyWith(mergeStatus: Status.inProgress));
|
emit(state.copyWith(mergeStatus: Status.inProgress));
|
||||||
final Iterable<int> ids = await _hackerNewsWebRepository.fetchFavorites(
|
try {
|
||||||
of: _authBloc.state.username,
|
final Iterable<int> ids = await _hackerNewsWebRepository.fetchFavorites(
|
||||||
);
|
of: _authBloc.state.username,
|
||||||
final List<int> combinedIds = <int>[...ids, ...state.favIds];
|
);
|
||||||
final LinkedHashSet<int> mergedIds = LinkedHashSet<int>.from(combinedIds);
|
_logger.d('fetched ${ids.length} favorite items from HN.');
|
||||||
await _preferenceRepository.overwriteFav(
|
final List<int> combinedIds = <int>[...ids, ...state.favIds];
|
||||||
username: username,
|
final LinkedHashSet<int> mergedIds =
|
||||||
ids: mergedIds,
|
LinkedHashSet<int>.from(combinedIds);
|
||||||
);
|
await _preferenceRepository.overwriteFav(
|
||||||
emit(state.copyWith(mergeStatus: Status.success));
|
username: username,
|
||||||
refresh();
|
ids: mergedIds,
|
||||||
|
);
|
||||||
|
emit(state.copyWith(mergeStatus: Status.success));
|
||||||
|
onSuccess();
|
||||||
|
refresh();
|
||||||
|
} on RateLimitedException catch (e) {
|
||||||
|
onError(e);
|
||||||
|
emit(state.copyWith(mergeStatus: Status.failure));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ import 'package:hacki/config/locator.dart';
|
|||||||
import 'package:hacki/cubits/cubits.dart';
|
import 'package:hacki/cubits/cubits.dart';
|
||||||
import 'package:hacki/models/models.dart';
|
import 'package:hacki/models/models.dart';
|
||||||
import 'package:hacki/repositories/repositories.dart';
|
import 'package:hacki/repositories/repositories.dart';
|
||||||
|
import 'package:logger/logger.dart';
|
||||||
|
|
||||||
part 'notification_state.dart';
|
part 'notification_state.dart';
|
||||||
|
|
||||||
@ -19,6 +20,7 @@ class NotificationCubit extends Cubit<NotificationState> {
|
|||||||
HackerNewsRepository? hackerNewsRepository,
|
HackerNewsRepository? hackerNewsRepository,
|
||||||
PreferenceRepository? preferenceRepository,
|
PreferenceRepository? preferenceRepository,
|
||||||
SembastRepository? sembastRepository,
|
SembastRepository? sembastRepository,
|
||||||
|
Logger? logger,
|
||||||
}) : _authBloc = authBloc,
|
}) : _authBloc = authBloc,
|
||||||
_preferenceCubit = preferenceCubit,
|
_preferenceCubit = preferenceCubit,
|
||||||
_hackerNewsRepository =
|
_hackerNewsRepository =
|
||||||
@ -27,6 +29,7 @@ class NotificationCubit extends Cubit<NotificationState> {
|
|||||||
preferenceRepository ?? locator.get<PreferenceRepository>(),
|
preferenceRepository ?? locator.get<PreferenceRepository>(),
|
||||||
_sembastRepository =
|
_sembastRepository =
|
||||||
sembastRepository ?? locator.get<SembastRepository>(),
|
sembastRepository ?? locator.get<SembastRepository>(),
|
||||||
|
_logger = logger ?? locator.get<Logger>(),
|
||||||
super(NotificationState.init()) {
|
super(NotificationState.init()) {
|
||||||
_authBloc.stream
|
_authBloc.stream
|
||||||
.map((AuthState event) => event.username)
|
.map((AuthState event) => event.username)
|
||||||
@ -58,6 +61,7 @@ class NotificationCubit extends Cubit<NotificationState> {
|
|||||||
final HackerNewsRepository _hackerNewsRepository;
|
final HackerNewsRepository _hackerNewsRepository;
|
||||||
final PreferenceRepository _preferenceRepository;
|
final PreferenceRepository _preferenceRepository;
|
||||||
final SembastRepository _sembastRepository;
|
final SembastRepository _sembastRepository;
|
||||||
|
final Logger _logger;
|
||||||
Timer? _timer;
|
Timer? _timer;
|
||||||
|
|
||||||
static const Duration _refreshInterval = Duration(minutes: 5);
|
static const Duration _refreshInterval = Duration(minutes: 5);
|
||||||
@ -74,6 +78,7 @@ class NotificationCubit extends Cubit<NotificationState> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
await _preferenceRepository.unreadCommentsIds.then((List<int> unreadIds) {
|
await _preferenceRepository.unreadCommentsIds.then((List<int> unreadIds) {
|
||||||
|
_logger.i('NotificationCubit: ${unreadIds.length} unread items.');
|
||||||
emit(state.copyWith(unreadCommentsIds: unreadIds));
|
emit(state.copyWith(unreadCommentsIds: unreadIds));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -72,8 +72,12 @@ class PreferenceState extends Equatable {
|
|||||||
|
|
||||||
bool get manualPaginationEnabled => _isOn<ManualPaginationPreference>();
|
bool get manualPaginationEnabled => _isOn<ManualPaginationPreference>();
|
||||||
|
|
||||||
|
bool get trueDarkModeEnabled => _isOn<TrueDarkModePreference>();
|
||||||
|
|
||||||
bool get hapticFeedbackEnabled => _isOn<HapticFeedbackPreference>();
|
bool get hapticFeedbackEnabled => _isOn<HapticFeedbackPreference>();
|
||||||
|
|
||||||
|
bool get devModeEnabled => _isOn<DevMode>();
|
||||||
|
|
||||||
double get textScaleFactor =>
|
double get textScaleFactor =>
|
||||||
preferences.singleWhereType<TextScaleFactorPreference>().val;
|
preferences.singleWhereType<TextScaleFactorPreference>().val;
|
||||||
|
|
||||||
@ -115,6 +119,9 @@ class PreferenceState extends Equatable {
|
|||||||
Font get font =>
|
Font get font =>
|
||||||
Font.values.elementAt(preferences.singleWhereType<FontPreference>().val);
|
Font.values.elementAt(preferences.singleWhereType<FontPreference>().val);
|
||||||
|
|
||||||
|
DisplayDateFormat get displayDateFormat => DisplayDateFormat.values
|
||||||
|
.elementAt(preferences.singleWhereType<DateFormatPreference>().val);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => <Object?>[
|
List<Object?> get props => <Object?>[
|
||||||
...preferences.map<dynamic>((Preference<dynamic> e) => e.val),
|
...preferences.map<dynamic>((Preference<dynamic> e) => e.val),
|
||||||
|
@ -38,9 +38,19 @@ extension ContextExtension on BuildContext {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void showErrorSnackBar() => showSnackBar(
|
void showErrorSnackBar([String? message]) {
|
||||||
content: Constants.errorMessage,
|
ScaffoldMessenger.of(this).showSnackBar(
|
||||||
);
|
SnackBar(
|
||||||
|
backgroundColor: Theme.of(this).colorScheme.errorContainer,
|
||||||
|
content: Text(
|
||||||
|
message ?? Constants.errorMessage,
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(this).colorScheme.onErrorContainer,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Rect? get rect {
|
Rect? get rect {
|
||||||
final RenderBox? box = findRenderObject() as RenderBox?;
|
final RenderBox? box = findRenderObject() as RenderBox?;
|
||||||
|
@ -27,7 +27,8 @@ mixin ItemActionMixin<T extends StatefulWidget> on State<T> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void showErrorSnackBar() => context.showErrorSnackBar();
|
void showErrorSnackBar([String? message]) =>
|
||||||
|
context.showErrorSnackBar(message);
|
||||||
|
|
||||||
Future<void>? goToItemScreen({
|
Future<void>? goToItemScreen({
|
||||||
required ItemScreenArgs args,
|
required ItemScreenArgs args,
|
||||||
|
@ -23,6 +23,7 @@ import 'package:hacki/utils/haptic_feedback_util.dart';
|
|||||||
import 'package:hacki/utils/theme_util.dart';
|
import 'package:hacki/utils/theme_util.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:hydrated_bloc/hydrated_bloc.dart';
|
import 'package:hydrated_bloc/hydrated_bloc.dart';
|
||||||
|
import 'package:intl/date_symbol_data_local.dart';
|
||||||
import 'package:logger/logger.dart';
|
import 'package:logger/logger.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:rxdart/rxdart.dart' show BehaviorSubject;
|
import 'package:rxdart/rxdart.dart' show BehaviorSubject;
|
||||||
@ -45,6 +46,8 @@ void notificationReceiver(NotificationResponse details) =>
|
|||||||
Future<void> main({bool testing = false}) async {
|
Future<void> main({bool testing = false}) async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
|
await initializeDateFormatting(Platform.localeName);
|
||||||
|
|
||||||
isTesting = testing;
|
isTesting = testing;
|
||||||
|
|
||||||
final Directory tempDir = await getTemporaryDirectory();
|
final Directory tempDir = await getTemporaryDirectory();
|
||||||
@ -239,11 +242,12 @@ class HackiApp extends StatelessWidget {
|
|||||||
buildWhen: (PreferenceState previous, PreferenceState current) =>
|
buildWhen: (PreferenceState previous, PreferenceState current) =>
|
||||||
previous.appColor != current.appColor ||
|
previous.appColor != current.appColor ||
|
||||||
previous.font != current.font ||
|
previous.font != current.font ||
|
||||||
previous.textScaleFactor != current.textScaleFactor,
|
previous.textScaleFactor != current.textScaleFactor ||
|
||||||
|
previous.trueDarkModeEnabled != current.trueDarkModeEnabled,
|
||||||
builder: (BuildContext context, PreferenceState state) {
|
builder: (BuildContext context, PreferenceState state) {
|
||||||
return AdaptiveTheme(
|
return AdaptiveTheme(
|
||||||
key: ValueKey<String>(
|
key: ValueKey<String>(
|
||||||
'''${state.appColor}${state.font}''',
|
'''${state.appColor}${state.font}${state.trueDarkModeEnabled}''',
|
||||||
),
|
),
|
||||||
light: ThemeData(
|
light: ThemeData(
|
||||||
primaryColor: state.appColor,
|
primaryColor: state.appColor,
|
||||||
@ -286,6 +290,9 @@ class HackiApp extends StatelessWidget {
|
|||||||
brightness:
|
brightness:
|
||||||
isDarkModeEnabled ? Brightness.dark : Brightness.light,
|
isDarkModeEnabled ? Brightness.dark : Brightness.light,
|
||||||
seedColor: state.appColor,
|
seedColor: state.appColor,
|
||||||
|
background: isDarkModeEnabled && state.trueDarkModeEnabled
|
||||||
|
? Palette.black
|
||||||
|
: null,
|
||||||
);
|
);
|
||||||
return FeatureDiscovery(
|
return FeatureDiscovery(
|
||||||
child: MediaQuery(
|
child: MediaQuery(
|
||||||
@ -297,16 +304,10 @@ class HackiApp extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: MaterialApp.router(
|
child: MaterialApp.router(
|
||||||
key: Key(state.appColor.hashCode.toString()),
|
|
||||||
title: 'Hacki',
|
title: 'Hacki',
|
||||||
debugShowCheckedModeBanner: false,
|
debugShowCheckedModeBanner: false,
|
||||||
theme: ThemeData(
|
theme: ThemeData(
|
||||||
colorScheme: ColorScheme.fromSeed(
|
colorScheme: colorScheme,
|
||||||
brightness: isDarkModeEnabled
|
|
||||||
? Brightness.dark
|
|
||||||
: Brightness.light,
|
|
||||||
seedColor: state.appColor,
|
|
||||||
),
|
|
||||||
fontFamily: state.font.name,
|
fontFamily: state.font.name,
|
||||||
dividerTheme: DividerThemeData(
|
dividerTheme: DividerThemeData(
|
||||||
color: Palette.grey.withOpacity(0.2),
|
color: Palette.grey.withOpacity(0.2),
|
||||||
|
32
lib/models/app_exception.dart
Normal file
32
lib/models/app_exception.dart
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
typedef AppExceptionHandler = void Function(AppException);
|
||||||
|
|
||||||
|
class AppException implements Exception {
|
||||||
|
AppException({
|
||||||
|
required this.message,
|
||||||
|
this.stackTrace,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String? message;
|
||||||
|
final StackTrace? stackTrace;
|
||||||
|
}
|
||||||
|
|
||||||
|
class RateLimitedException extends AppException {
|
||||||
|
RateLimitedException() : super(message: 'Rate limited...');
|
||||||
|
}
|
||||||
|
|
||||||
|
class RateLimitedWithFallbackException extends AppException {
|
||||||
|
RateLimitedWithFallbackException()
|
||||||
|
: super(message: 'Rate limited, fetching from API instead...');
|
||||||
|
}
|
||||||
|
|
||||||
|
class PossibleParsingException extends AppException {
|
||||||
|
PossibleParsingException({
|
||||||
|
required this.itemId,
|
||||||
|
}) : super(message: 'Possible parsing failure...');
|
||||||
|
|
||||||
|
final int itemId;
|
||||||
|
}
|
||||||
|
|
||||||
|
class GenericException extends AppException {
|
||||||
|
GenericException() : super(message: 'Something went wrong...');
|
||||||
|
}
|
19
lib/models/dio/cached_response.dart
Normal file
19
lib/models/dio/cached_response.dart
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import 'package:dio/dio.dart';
|
||||||
|
|
||||||
|
class CachedResponse<T> extends Response<T> {
|
||||||
|
CachedResponse({
|
||||||
|
required super.requestOptions,
|
||||||
|
super.data,
|
||||||
|
super.statusCode,
|
||||||
|
}) : setDateTime = DateTime.now();
|
||||||
|
|
||||||
|
factory CachedResponse.fromResponse(Response<T> response) {
|
||||||
|
return CachedResponse<T>(
|
||||||
|
requestOptions: response.requestOptions,
|
||||||
|
data: response.data,
|
||||||
|
statusCode: response.statusCode,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final DateTime setDateTime;
|
||||||
|
}
|
54
lib/models/display_date_format.dart
Normal file
54
lib/models/display_date_format.dart
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:hacki/extensions/date_time_extension.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
|
enum DisplayDateFormat {
|
||||||
|
timeAgo,
|
||||||
|
yMd,
|
||||||
|
yMEd,
|
||||||
|
yMMMd,
|
||||||
|
yMMMEd;
|
||||||
|
|
||||||
|
String get description {
|
||||||
|
final DateTime exampleDate =
|
||||||
|
DateTime.now().subtract(const Duration(days: 5));
|
||||||
|
return switch (this) {
|
||||||
|
timeAgo => exampleDate.toTimeAgoString(),
|
||||||
|
yMd || yMEd || yMMMd || yMMMEd => () {
|
||||||
|
final String defaultLocale = Platform.localeName;
|
||||||
|
final DateFormat formatter = DateFormat(name, defaultLocale).add_Hm();
|
||||||
|
return formatter.format(exampleDate);
|
||||||
|
}(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
String convertToString(int timestamp) {
|
||||||
|
if (_cache.containsKey(timestamp)) {
|
||||||
|
return _cache[timestamp] ?? 'This is wrong';
|
||||||
|
}
|
||||||
|
|
||||||
|
int updatedTimeStamp = timestamp;
|
||||||
|
if (updatedTimeStamp < 9999999999) {
|
||||||
|
updatedTimeStamp = updatedTimeStamp * 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
final DateTime date = DateTime.fromMillisecondsSinceEpoch(updatedTimeStamp);
|
||||||
|
|
||||||
|
if (this == timeAgo) {
|
||||||
|
final String dateString = date.toTimeAgoString();
|
||||||
|
_cache[timestamp] = dateString;
|
||||||
|
return dateString;
|
||||||
|
} else {
|
||||||
|
final String defaultLocale = Platform.localeName;
|
||||||
|
final DateFormat formatter = DateFormat(name, defaultLocale).add_Hm();
|
||||||
|
final String dateString = formatter.format(date);
|
||||||
|
_cache[timestamp] = dateString;
|
||||||
|
return dateString;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void clearCache() => _cache.clear();
|
||||||
|
|
||||||
|
static Map<int, String> _cache = <int, String>{};
|
||||||
|
}
|
@ -90,8 +90,13 @@ class Item extends Equatable {
|
|||||||
final List<int> kids;
|
final List<int> kids;
|
||||||
final List<int> parts;
|
final List<int> parts;
|
||||||
|
|
||||||
String get timeAgo =>
|
String get timeAgo {
|
||||||
DateTime.fromMillisecondsSinceEpoch(time * 1000).toTimeAgoString();
|
int time = this.time;
|
||||||
|
if (time < 9999999999) {
|
||||||
|
time = time * 1000;
|
||||||
|
}
|
||||||
|
return DateTime.fromMillisecondsSinceEpoch(time).toTimeAgoString();
|
||||||
|
}
|
||||||
|
|
||||||
bool get isPoll => type == 'poll';
|
bool get isPoll => type == 'poll';
|
||||||
|
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
|
export 'app_exception.dart';
|
||||||
export 'comments_order.dart';
|
export 'comments_order.dart';
|
||||||
export 'discoverable_feature.dart';
|
export 'discoverable_feature.dart';
|
||||||
|
export 'display_date_format.dart';
|
||||||
export 'export_destination.dart';
|
export 'export_destination.dart';
|
||||||
export 'fetch_mode.dart';
|
export 'fetch_mode.dart';
|
||||||
export 'font.dart';
|
export 'font.dart';
|
||||||
|
@ -7,7 +7,7 @@ import 'package:hacki/models/displayable.dart';
|
|||||||
import 'package:hacki/models/models.dart';
|
import 'package:hacki/models/models.dart';
|
||||||
import 'package:hacki/styles/palette.dart';
|
import 'package:hacki/styles/palette.dart';
|
||||||
|
|
||||||
abstract class Preference<T> extends Equatable with SettingsDisplayable {
|
abstract final class Preference<T> extends Equatable with SettingsDisplayable {
|
||||||
const Preference({required this.val});
|
const Preference({required this.val});
|
||||||
|
|
||||||
final T val;
|
final T val;
|
||||||
@ -19,7 +19,7 @@ abstract class Preference<T> extends Equatable with SettingsDisplayable {
|
|||||||
static final List<Preference<dynamic>> allPreferences =
|
static final List<Preference<dynamic>> allPreferences =
|
||||||
UnmodifiableListView<Preference<dynamic>>(
|
UnmodifiableListView<Preference<dynamic>>(
|
||||||
<Preference<dynamic>>[
|
<Preference<dynamic>>[
|
||||||
// Order of these preferences does not matter.
|
// Order of these preferences does not matter.
|
||||||
FetchModePreference(),
|
FetchModePreference(),
|
||||||
CommentsOrderPreference(),
|
CommentsOrderPreference(),
|
||||||
FontPreference(),
|
FontPreference(),
|
||||||
@ -27,15 +27,16 @@ abstract class Preference<T> extends Equatable with SettingsDisplayable {
|
|||||||
TabOrderPreference(),
|
TabOrderPreference(),
|
||||||
StoryMarkingModePreference(),
|
StoryMarkingModePreference(),
|
||||||
AppColorPreference(),
|
AppColorPreference(),
|
||||||
|
DateFormatPreference(),
|
||||||
const TextScaleFactorPreference(),
|
const TextScaleFactorPreference(),
|
||||||
// Order of items below matters and
|
// Order of items below matters and
|
||||||
// reflects the order on settings screen.
|
// reflects the order on settings screen.
|
||||||
const DisplayModePreference(),
|
const DisplayModePreference(),
|
||||||
const MetadataModePreference(),
|
const MetadataModePreference(),
|
||||||
const StoryUrlModePreference(),
|
const StoryUrlModePreference(),
|
||||||
// Divider.
|
// Divider.
|
||||||
const MarkReadStoriesModePreference(),
|
const MarkReadStoriesModePreference(),
|
||||||
// Divider.
|
// Divider.
|
||||||
const NotificationModePreference(),
|
const NotificationModePreference(),
|
||||||
const AutoScrollModePreference(),
|
const AutoScrollModePreference(),
|
||||||
const CollapseModePreference(),
|
const CollapseModePreference(),
|
||||||
@ -45,6 +46,8 @@ abstract class Preference<T> extends Equatable with SettingsDisplayable {
|
|||||||
const SwipeGesturePreference(),
|
const SwipeGesturePreference(),
|
||||||
const HapticFeedbackPreference(),
|
const HapticFeedbackPreference(),
|
||||||
const EyeCandyModePreference(),
|
const EyeCandyModePreference(),
|
||||||
|
const TrueDarkModePreference(),
|
||||||
|
const DevMode(),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -52,46 +55,47 @@ abstract class Preference<T> extends Equatable with SettingsDisplayable {
|
|||||||
List<Object?> get props => <Object?>[key];
|
List<Object?> get props => <Object?>[key];
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class BooleanPreference extends Preference<bool> {
|
abstract final class BooleanPreference extends Preference<bool> {
|
||||||
const BooleanPreference({required super.val});
|
const BooleanPreference({required super.val});
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class IntPreference extends Preference<int> {
|
abstract final class IntPreference extends Preference<int> {
|
||||||
const IntPreference({required super.val});
|
const IntPreference({required super.val});
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class DoublePreference extends Preference<double> {
|
abstract final class DoublePreference extends Preference<double> {
|
||||||
const DoublePreference({required super.val});
|
const DoublePreference({required super.val});
|
||||||
}
|
}
|
||||||
|
|
||||||
const bool _notificationModeDefaultValue = true;
|
final class DevMode extends BooleanPreference {
|
||||||
const bool _swipeGestureModeDefaultValue = false;
|
const DevMode({bool? val}) : super(val: val ?? _devModeDefaultValue);
|
||||||
const bool _displayModeDefaultValue = true;
|
|
||||||
const bool _eyeCandyModeDefaultValue = false;
|
|
||||||
const bool _hapticFeedbackModeDefaultValue = true;
|
|
||||||
const bool _readerModeDefaultValue = true;
|
|
||||||
const bool _markReadStoriesModeDefaultValue = true;
|
|
||||||
const bool _metadataModeDefaultValue = true;
|
|
||||||
const bool _storyUrlModeDefaultValue = true;
|
|
||||||
const bool _collapseModeDefaultValue = true;
|
|
||||||
const bool _autoScrollModeDefaultValue = false;
|
|
||||||
const bool _customTabModeDefaultValue = false;
|
|
||||||
const bool _paginationModeDefaultValue = false;
|
|
||||||
const double _textScaleFactorDefaultValue = 1;
|
|
||||||
final int _fetchModeDefaultValue = FetchMode.eager.index;
|
|
||||||
final int _commentsOrderDefaultValue = CommentsOrder.natural.index;
|
|
||||||
final int _fontSizeDefaultValue = FontSize.regular.index;
|
|
||||||
final int _appColorDefaultValue = materialColors.indexOf(Palette.deepOrange);
|
|
||||||
final int _fontDefaultValue = Font.robotoSlab.index;
|
|
||||||
final int _tabOrderDefaultValue =
|
|
||||||
StoryType.convertToSettingsValue(StoryType.values);
|
|
||||||
final int _markStoriesAsReadWhenPreferenceDefaultValue =
|
|
||||||
StoryMarkingMode.tap.index;
|
|
||||||
|
|
||||||
class SwipeGesturePreference extends BooleanPreference {
|
static const bool _devModeDefaultValue = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
DevMode copyWith({required bool? val}) {
|
||||||
|
return DevMode(val: val);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get key => 'devMode';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get title => 'Dev Mode';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get subtitle => '';
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get isDisplayable => false;
|
||||||
|
}
|
||||||
|
|
||||||
|
final class SwipeGesturePreference extends BooleanPreference {
|
||||||
const SwipeGesturePreference({bool? val})
|
const SwipeGesturePreference({bool? val})
|
||||||
: super(val: val ?? _swipeGestureModeDefaultValue);
|
: super(val: val ?? _swipeGestureModeDefaultValue);
|
||||||
|
|
||||||
|
static const bool _swipeGestureModeDefaultValue = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
SwipeGesturePreference copyWith({required bool? val}) {
|
SwipeGesturePreference copyWith({required bool? val}) {
|
||||||
return SwipeGesturePreference(val: val);
|
return SwipeGesturePreference(val: val);
|
||||||
@ -108,10 +112,12 @@ class SwipeGesturePreference extends BooleanPreference {
|
|||||||
'''enable swipe gesture for switching between tabs. If enabled, long press on Story tile to trigger the action menu.''';
|
'''enable swipe gesture for switching between tabs. If enabled, long press on Story tile to trigger the action menu.''';
|
||||||
}
|
}
|
||||||
|
|
||||||
class NotificationModePreference extends BooleanPreference {
|
final class NotificationModePreference extends BooleanPreference {
|
||||||
const NotificationModePreference({bool? val})
|
const NotificationModePreference({bool? val})
|
||||||
: super(val: val ?? _notificationModeDefaultValue);
|
: super(val: val ?? _notificationModeDefaultValue);
|
||||||
|
|
||||||
|
static const bool _notificationModeDefaultValue = true;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
NotificationModePreference copyWith({required bool? val}) {
|
NotificationModePreference copyWith({required bool? val}) {
|
||||||
return NotificationModePreference(val: val);
|
return NotificationModePreference(val: val);
|
||||||
@ -128,10 +134,12 @@ class NotificationModePreference extends BooleanPreference {
|
|||||||
'''Hacki scans for new replies to your 15 most recent comments or stories every 5 minutes while the app is running in the foreground.''';
|
'''Hacki scans for new replies to your 15 most recent comments or stories every 5 minutes while the app is running in the foreground.''';
|
||||||
}
|
}
|
||||||
|
|
||||||
class CollapseModePreference extends BooleanPreference {
|
final class CollapseModePreference extends BooleanPreference {
|
||||||
const CollapseModePreference({bool? val})
|
const CollapseModePreference({bool? val})
|
||||||
: super(val: val ?? _collapseModeDefaultValue);
|
: super(val: val ?? _collapseModeDefaultValue);
|
||||||
|
|
||||||
|
static const bool _collapseModeDefaultValue = true;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
CollapseModePreference copyWith({required bool? val}) {
|
CollapseModePreference copyWith({required bool? val}) {
|
||||||
return CollapseModePreference(val: val);
|
return CollapseModePreference(val: val);
|
||||||
@ -148,10 +156,12 @@ class CollapseModePreference extends BooleanPreference {
|
|||||||
'''if disabled, tap on the top of comment tile to collapse.''';
|
'''if disabled, tap on the top of comment tile to collapse.''';
|
||||||
}
|
}
|
||||||
|
|
||||||
class AutoScrollModePreference extends BooleanPreference {
|
final class AutoScrollModePreference extends BooleanPreference {
|
||||||
const AutoScrollModePreference({bool? val})
|
const AutoScrollModePreference({bool? val})
|
||||||
: super(val: val ?? _autoScrollModeDefaultValue);
|
: super(val: val ?? _autoScrollModeDefaultValue);
|
||||||
|
|
||||||
|
static const bool _autoScrollModeDefaultValue = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
AutoScrollModePreference copyWith({required bool? val}) {
|
AutoScrollModePreference copyWith({required bool? val}) {
|
||||||
return AutoScrollModePreference(val: val);
|
return AutoScrollModePreference(val: val);
|
||||||
@ -161,7 +171,7 @@ class AutoScrollModePreference extends BooleanPreference {
|
|||||||
String get key => 'autoScrollMode';
|
String get key => 'autoScrollMode';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get title => 'Auto-scroll on collapsing';
|
String get title => 'Auto-scroll on Collapsing';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get subtitle =>
|
String get subtitle =>
|
||||||
@ -170,10 +180,12 @@ class AutoScrollModePreference extends BooleanPreference {
|
|||||||
|
|
||||||
/// The value deciding whether or not the story
|
/// The value deciding whether or not the story
|
||||||
/// tile should display link preview. Defaults to true.
|
/// tile should display link preview. Defaults to true.
|
||||||
class DisplayModePreference extends BooleanPreference {
|
final class DisplayModePreference extends BooleanPreference {
|
||||||
const DisplayModePreference({bool? val})
|
const DisplayModePreference({bool? val})
|
||||||
: super(val: val ?? _displayModeDefaultValue);
|
: super(val: val ?? _displayModeDefaultValue);
|
||||||
|
|
||||||
|
static const bool _displayModeDefaultValue = true;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
DisplayModePreference copyWith({required bool? val}) {
|
DisplayModePreference copyWith({required bool? val}) {
|
||||||
return DisplayModePreference(val: val);
|
return DisplayModePreference(val: val);
|
||||||
@ -189,10 +201,12 @@ class DisplayModePreference extends BooleanPreference {
|
|||||||
String get subtitle => 'show web preview in story tile.';
|
String get subtitle => 'show web preview in story tile.';
|
||||||
}
|
}
|
||||||
|
|
||||||
class MetadataModePreference extends BooleanPreference {
|
final class MetadataModePreference extends BooleanPreference {
|
||||||
const MetadataModePreference({bool? val})
|
const MetadataModePreference({bool? val})
|
||||||
: super(val: val ?? _metadataModeDefaultValue);
|
: super(val: val ?? _metadataModeDefaultValue);
|
||||||
|
|
||||||
|
static const bool _metadataModeDefaultValue = true;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
MetadataModePreference copyWith({required bool? val}) {
|
MetadataModePreference copyWith({required bool? val}) {
|
||||||
return MetadataModePreference(val: val);
|
return MetadataModePreference(val: val);
|
||||||
@ -209,10 +223,12 @@ class MetadataModePreference extends BooleanPreference {
|
|||||||
'''show number of comments and post date in story tile.''';
|
'''show number of comments and post date in story tile.''';
|
||||||
}
|
}
|
||||||
|
|
||||||
class StoryUrlModePreference extends BooleanPreference {
|
final class StoryUrlModePreference extends BooleanPreference {
|
||||||
const StoryUrlModePreference({bool? val})
|
const StoryUrlModePreference({bool? val})
|
||||||
: super(val: val ?? _storyUrlModeDefaultValue);
|
: super(val: val ?? _storyUrlModeDefaultValue);
|
||||||
|
|
||||||
|
static const bool _storyUrlModeDefaultValue = true;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
StoryUrlModePreference copyWith({required bool? val}) {
|
StoryUrlModePreference copyWith({required bool? val}) {
|
||||||
return StoryUrlModePreference(val: val);
|
return StoryUrlModePreference(val: val);
|
||||||
@ -228,10 +244,12 @@ class StoryUrlModePreference extends BooleanPreference {
|
|||||||
String get subtitle => '''show url in story tile.''';
|
String get subtitle => '''show url in story tile.''';
|
||||||
}
|
}
|
||||||
|
|
||||||
class ReaderModePreference extends BooleanPreference {
|
final class ReaderModePreference extends BooleanPreference {
|
||||||
const ReaderModePreference({bool? val})
|
const ReaderModePreference({bool? val})
|
||||||
: super(val: val ?? _readerModeDefaultValue);
|
: super(val: val ?? _readerModeDefaultValue);
|
||||||
|
|
||||||
|
static const bool _readerModeDefaultValue = true;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
ReaderModePreference copyWith({required bool? val}) {
|
ReaderModePreference copyWith({required bool? val}) {
|
||||||
return ReaderModePreference(val: val);
|
return ReaderModePreference(val: val);
|
||||||
@ -251,10 +269,12 @@ class ReaderModePreference extends BooleanPreference {
|
|||||||
bool get isDisplayable => Platform.isIOS;
|
bool get isDisplayable => Platform.isIOS;
|
||||||
}
|
}
|
||||||
|
|
||||||
class MarkReadStoriesModePreference extends BooleanPreference {
|
final class MarkReadStoriesModePreference extends BooleanPreference {
|
||||||
const MarkReadStoriesModePreference({bool? val})
|
const MarkReadStoriesModePreference({bool? val})
|
||||||
: super(val: val ?? _markReadStoriesModeDefaultValue);
|
: super(val: val ?? _markReadStoriesModeDefaultValue);
|
||||||
|
|
||||||
|
static const bool _markReadStoriesModeDefaultValue = true;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
MarkReadStoriesModePreference copyWith({required bool? val}) {
|
MarkReadStoriesModePreference copyWith({required bool? val}) {
|
||||||
return MarkReadStoriesModePreference(val: val);
|
return MarkReadStoriesModePreference(val: val);
|
||||||
@ -270,10 +290,12 @@ class MarkReadStoriesModePreference extends BooleanPreference {
|
|||||||
String get subtitle => 'grey out stories you have read.';
|
String get subtitle => 'grey out stories you have read.';
|
||||||
}
|
}
|
||||||
|
|
||||||
class EyeCandyModePreference extends BooleanPreference {
|
final class EyeCandyModePreference extends BooleanPreference {
|
||||||
const EyeCandyModePreference({bool? val})
|
const EyeCandyModePreference({bool? val})
|
||||||
: super(val: val ?? _eyeCandyModeDefaultValue);
|
: super(val: val ?? _eyeCandyModeDefaultValue);
|
||||||
|
|
||||||
|
static const bool _eyeCandyModeDefaultValue = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
EyeCandyModePreference copyWith({required bool? val}) {
|
EyeCandyModePreference copyWith({required bool? val}) {
|
||||||
return EyeCandyModePreference(val: val);
|
return EyeCandyModePreference(val: val);
|
||||||
@ -289,10 +311,12 @@ class EyeCandyModePreference extends BooleanPreference {
|
|||||||
String get subtitle => 'some sort of magic.';
|
String get subtitle => 'some sort of magic.';
|
||||||
}
|
}
|
||||||
|
|
||||||
class ManualPaginationPreference extends BooleanPreference {
|
final class ManualPaginationPreference extends BooleanPreference {
|
||||||
const ManualPaginationPreference({bool? val})
|
const ManualPaginationPreference({bool? val})
|
||||||
: super(val: val ?? _paginationModeDefaultValue);
|
: super(val: val ?? _paginationModeDefaultValue);
|
||||||
|
|
||||||
|
static const bool _paginationModeDefaultValue = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
ManualPaginationPreference copyWith({required bool? val}) {
|
ManualPaginationPreference copyWith({required bool? val}) {
|
||||||
return ManualPaginationPreference(val: val);
|
return ManualPaginationPreference(val: val);
|
||||||
@ -312,10 +336,12 @@ class ManualPaginationPreference extends BooleanPreference {
|
|||||||
/// If false, default browser will be used.
|
/// If false, default browser will be used.
|
||||||
///
|
///
|
||||||
/// https://developer.chrome.com/docs/android/custom-tabs/
|
/// https://developer.chrome.com/docs/android/custom-tabs/
|
||||||
class CustomTabPreference extends BooleanPreference {
|
final class CustomTabPreference extends BooleanPreference {
|
||||||
const CustomTabPreference({bool? val})
|
const CustomTabPreference({bool? val})
|
||||||
: super(val: val ?? _customTabModeDefaultValue);
|
: super(val: val ?? _customTabModeDefaultValue);
|
||||||
|
|
||||||
|
static const bool _customTabModeDefaultValue = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
CustomTabPreference copyWith({required bool? val}) {
|
CustomTabPreference copyWith({required bool? val}) {
|
||||||
return CustomTabPreference(val: val);
|
return CustomTabPreference(val: val);
|
||||||
@ -335,10 +361,33 @@ class CustomTabPreference extends BooleanPreference {
|
|||||||
bool get isDisplayable => Platform.isAndroid;
|
bool get isDisplayable => Platform.isAndroid;
|
||||||
}
|
}
|
||||||
|
|
||||||
class HapticFeedbackPreference extends BooleanPreference {
|
final class TrueDarkModePreference extends BooleanPreference {
|
||||||
|
const TrueDarkModePreference({bool? val})
|
||||||
|
: super(val: val ?? _trueDarkModeDefaultValue);
|
||||||
|
|
||||||
|
static const bool _trueDarkModeDefaultValue = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
TrueDarkModePreference copyWith({required bool? val}) {
|
||||||
|
return TrueDarkModePreference(val: val);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get key => 'trueDarkMode';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get title => 'True Dark Mode';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get subtitle => 'real dark.';
|
||||||
|
}
|
||||||
|
|
||||||
|
final class HapticFeedbackPreference extends BooleanPreference {
|
||||||
const HapticFeedbackPreference({bool? val})
|
const HapticFeedbackPreference({bool? val})
|
||||||
: super(val: val ?? _hapticFeedbackModeDefaultValue);
|
: super(val: val ?? _hapticFeedbackModeDefaultValue);
|
||||||
|
|
||||||
|
static const bool _hapticFeedbackModeDefaultValue = true;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
HapticFeedbackPreference copyWith({required bool? val}) {
|
HapticFeedbackPreference copyWith({required bool? val}) {
|
||||||
return HapticFeedbackPreference(val: val);
|
return HapticFeedbackPreference(val: val);
|
||||||
@ -352,14 +401,13 @@ class HapticFeedbackPreference extends BooleanPreference {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String get subtitle => '';
|
String get subtitle => '';
|
||||||
|
|
||||||
@override
|
|
||||||
bool get isDisplayable => Platform.isIOS;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class FetchModePreference extends IntPreference {
|
final class FetchModePreference extends IntPreference {
|
||||||
FetchModePreference({int? val}) : super(val: val ?? _fetchModeDefaultValue);
|
FetchModePreference({int? val}) : super(val: val ?? _fetchModeDefaultValue);
|
||||||
|
|
||||||
|
static final int _fetchModeDefaultValue = FetchMode.eager.index;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
FetchModePreference copyWith({required int? val}) {
|
FetchModePreference copyWith({required int? val}) {
|
||||||
return FetchModePreference(val: val);
|
return FetchModePreference(val: val);
|
||||||
@ -372,10 +420,12 @@ class FetchModePreference extends IntPreference {
|
|||||||
String get title => 'Default fetch mode';
|
String get title => 'Default fetch mode';
|
||||||
}
|
}
|
||||||
|
|
||||||
class CommentsOrderPreference extends IntPreference {
|
final class CommentsOrderPreference extends IntPreference {
|
||||||
CommentsOrderPreference({int? val})
|
CommentsOrderPreference({int? val})
|
||||||
: super(val: val ?? _commentsOrderDefaultValue);
|
: super(val: val ?? _commentsOrderDefaultValue);
|
||||||
|
|
||||||
|
static final int _commentsOrderDefaultValue = CommentsOrder.natural.index;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
CommentsOrderPreference copyWith({required int? val}) {
|
CommentsOrderPreference copyWith({required int? val}) {
|
||||||
return CommentsOrderPreference(val: val);
|
return CommentsOrderPreference(val: val);
|
||||||
@ -388,9 +438,11 @@ class CommentsOrderPreference extends IntPreference {
|
|||||||
String get title => 'Default comments order';
|
String get title => 'Default comments order';
|
||||||
}
|
}
|
||||||
|
|
||||||
class FontPreference extends IntPreference {
|
final class FontPreference extends IntPreference {
|
||||||
FontPreference({int? val}) : super(val: val ?? _fontDefaultValue);
|
FontPreference({int? val}) : super(val: val ?? _fontDefaultValue);
|
||||||
|
|
||||||
|
static final int _fontDefaultValue = Font.robotoSlab.index;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
FontPreference copyWith({required int? val}) {
|
FontPreference copyWith({required int? val}) {
|
||||||
return FontPreference(val: val);
|
return FontPreference(val: val);
|
||||||
@ -403,9 +455,11 @@ class FontPreference extends IntPreference {
|
|||||||
String get title => 'Default font';
|
String get title => 'Default font';
|
||||||
}
|
}
|
||||||
|
|
||||||
class FontSizePreference extends IntPreference {
|
final class FontSizePreference extends IntPreference {
|
||||||
FontSizePreference({int? val}) : super(val: val ?? _fontSizeDefaultValue);
|
FontSizePreference({int? val}) : super(val: val ?? _fontSizeDefaultValue);
|
||||||
|
|
||||||
|
static final int _fontSizeDefaultValue = FontSize.regular.index;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
FontSizePreference copyWith({required int? val}) {
|
FontSizePreference copyWith({required int? val}) {
|
||||||
return FontSizePreference(val: val);
|
return FontSizePreference(val: val);
|
||||||
@ -418,9 +472,12 @@ class FontSizePreference extends IntPreference {
|
|||||||
String get title => 'Default font size';
|
String get title => 'Default font size';
|
||||||
}
|
}
|
||||||
|
|
||||||
class TabOrderPreference extends IntPreference {
|
final class TabOrderPreference extends IntPreference {
|
||||||
TabOrderPreference({int? val}) : super(val: val ?? _tabOrderDefaultValue);
|
TabOrderPreference({int? val}) : super(val: val ?? _tabOrderDefaultValue);
|
||||||
|
|
||||||
|
static final int _tabOrderDefaultValue =
|
||||||
|
StoryType.convertToSettingsValue(StoryType.values);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
TabOrderPreference copyWith({required int? val}) {
|
TabOrderPreference copyWith({required int? val}) {
|
||||||
return TabOrderPreference(val: val);
|
return TabOrderPreference(val: val);
|
||||||
@ -433,10 +490,13 @@ class TabOrderPreference extends IntPreference {
|
|||||||
String get title => 'Tab order';
|
String get title => 'Tab order';
|
||||||
}
|
}
|
||||||
|
|
||||||
class StoryMarkingModePreference extends IntPreference {
|
final class StoryMarkingModePreference extends IntPreference {
|
||||||
StoryMarkingModePreference({int? val})
|
StoryMarkingModePreference({int? val})
|
||||||
: super(val: val ?? _markStoriesAsReadWhenPreferenceDefaultValue);
|
: super(val: val ?? _markStoriesAsReadWhenPreferenceDefaultValue);
|
||||||
|
|
||||||
|
static final int _markStoriesAsReadWhenPreferenceDefaultValue =
|
||||||
|
StoryMarkingMode.tap.index;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
StoryMarkingModePreference copyWith({required int? val}) {
|
StoryMarkingModePreference copyWith({required int? val}) {
|
||||||
return StoryMarkingModePreference(val: val);
|
return StoryMarkingModePreference(val: val);
|
||||||
@ -449,9 +509,12 @@ class StoryMarkingModePreference extends IntPreference {
|
|||||||
String get title => 'Mark as Read on';
|
String get title => 'Mark as Read on';
|
||||||
}
|
}
|
||||||
|
|
||||||
class AppColorPreference extends IntPreference {
|
final class AppColorPreference extends IntPreference {
|
||||||
AppColorPreference({int? val}) : super(val: val ?? _appColorDefaultValue);
|
AppColorPreference({int? val}) : super(val: val ?? _appColorDefaultValue);
|
||||||
|
|
||||||
|
static final int _appColorDefaultValue =
|
||||||
|
materialColors.indexOf(Palette.deepOrange);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
AppColorPreference copyWith({required int? val}) {
|
AppColorPreference copyWith({required int? val}) {
|
||||||
return AppColorPreference(val: val);
|
return AppColorPreference(val: val);
|
||||||
@ -464,10 +527,12 @@ class AppColorPreference extends IntPreference {
|
|||||||
String get title => 'Accent Color';
|
String get title => 'Accent Color';
|
||||||
}
|
}
|
||||||
|
|
||||||
class TextScaleFactorPreference extends DoublePreference {
|
final class TextScaleFactorPreference extends DoublePreference {
|
||||||
const TextScaleFactorPreference({double? val})
|
const TextScaleFactorPreference({double? val})
|
||||||
: super(val: val ?? _textScaleFactorDefaultValue);
|
: super(val: val ?? _textScaleFactorDefaultValue);
|
||||||
|
|
||||||
|
static const double _textScaleFactorDefaultValue = 1;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
TextScaleFactorPreference copyWith({required double? val}) {
|
TextScaleFactorPreference copyWith({required double? val}) {
|
||||||
return TextScaleFactorPreference(val: val);
|
return TextScaleFactorPreference(val: val);
|
||||||
@ -479,3 +544,20 @@ class TextScaleFactorPreference extends DoublePreference {
|
|||||||
@override
|
@override
|
||||||
String get title => 'Default text scale factor';
|
String get title => 'Default text scale factor';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final class DateFormatPreference extends IntPreference {
|
||||||
|
DateFormatPreference({int? val}) : super(val: val ?? _dateFormatDefaultValue);
|
||||||
|
|
||||||
|
static final int _dateFormatDefaultValue = DisplayDateFormat.timeAgo.index;
|
||||||
|
|
||||||
|
@override
|
||||||
|
DateFormatPreference copyWith({required int? val}) {
|
||||||
|
return DateFormatPreference(val: val);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get key => 'dateFormat';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get title => 'Date Format';
|
||||||
|
}
|
||||||
|
@ -1,11 +1,39 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:html/dom.dart';
|
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||||
|
import 'package:dio/dio.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:hacki/config/constants.dart';
|
||||||
|
import 'package:hacki/models/models.dart';
|
||||||
|
import 'package:hacki/utils/utils.dart';
|
||||||
|
import 'package:html/dom.dart' hide Comment;
|
||||||
import 'package:html/parser.dart';
|
import 'package:html/parser.dart';
|
||||||
import 'package:http/http.dart';
|
import 'package:html_unescape/html_unescape.dart';
|
||||||
|
|
||||||
/// For fetching anything that cannot be fetched through Hacker News API.
|
/// For fetching anything that cannot be fetched through Hacker News API.
|
||||||
class HackerNewsWebRepository {
|
class HackerNewsWebRepository {
|
||||||
HackerNewsWebRepository();
|
HackerNewsWebRepository({
|
||||||
|
Dio? dioWithCache,
|
||||||
|
Dio? dio,
|
||||||
|
}) : _dio = dio ?? Dio(),
|
||||||
|
_dioWithCache = dioWithCache ?? Dio()
|
||||||
|
..interceptors.addAll(
|
||||||
|
<Interceptor>[
|
||||||
|
if (kDebugMode) LoggerInterceptor(),
|
||||||
|
CacheInterceptor(),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
final Dio _dioWithCache;
|
||||||
|
final Dio _dio;
|
||||||
|
|
||||||
|
static const Map<String, String> _headers = <String, String>{
|
||||||
|
'accept': '*/*',
|
||||||
|
'user-agent':
|
||||||
|
'Mozilla/5.0 (iPhone; CPU iPhone OS 17_1_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.1 Mobile/15E148 Safari/604.1',
|
||||||
|
};
|
||||||
|
|
||||||
static const String _favoritesBaseUrl =
|
static const String _favoritesBaseUrl =
|
||||||
'https://news.ycombinator.com/favorites?id=';
|
'https://news.ycombinator.com/favorites?id=';
|
||||||
@ -13,25 +41,39 @@ class HackerNewsWebRepository {
|
|||||||
'#hnmain > tbody > tr:nth-child(3) > td > table > tbody > .athing';
|
'#hnmain > tbody > tr:nth-child(3) > td > table > tbody > .athing';
|
||||||
|
|
||||||
Future<Iterable<int>> fetchFavorites({required String of}) async {
|
Future<Iterable<int>> fetchFavorites({required String of}) async {
|
||||||
|
final bool isOnWifi = await _isOnWifi;
|
||||||
final String username = of;
|
final String username = of;
|
||||||
final List<int> allIds = <int>[];
|
final List<int> allIds = <int>[];
|
||||||
int page = 0;
|
int page = 1;
|
||||||
|
const int maxPage = 2;
|
||||||
|
|
||||||
Future<Iterable<int>> fetchIds(int page) async {
|
Future<Iterable<int>> fetchIds(int page, {bool isComment = false}) async {
|
||||||
final Uri url = Uri.parse('$_favoritesBaseUrl$username&p=$page');
|
try {
|
||||||
final Response response = await get(url);
|
final Uri url = Uri.parse(
|
||||||
final Document document = parse(response.body);
|
'''$_favoritesBaseUrl$username${isComment ? '&comments=t' : ''}&p=$page''',
|
||||||
final List<Element> elements = document.querySelectorAll(_aThingSelector);
|
);
|
||||||
final Iterable<int> parsedIds = elements
|
final Response<String> response =
|
||||||
.map(
|
await (isOnWifi ? _dioWithCache : _dio).getUri<String>(url);
|
||||||
(Element e) => int.tryParse(e.id),
|
|
||||||
)
|
/// Due to rate limiting, we have a short break here.
|
||||||
.whereNotNull();
|
await Future<void>.delayed(AppDurations.twoSeconds);
|
||||||
return parsedIds;
|
|
||||||
|
final Document document = parse(response.data);
|
||||||
|
final List<Element> elements =
|
||||||
|
document.querySelectorAll(_aThingSelector);
|
||||||
|
final Iterable<int> parsedIds =
|
||||||
|
elements.map((Element e) => int.tryParse(e.id)).whereNotNull();
|
||||||
|
return parsedIds;
|
||||||
|
} on DioException catch (e) {
|
||||||
|
if (e.response?.statusCode == HttpStatus.forbidden) {
|
||||||
|
throw RateLimitedException();
|
||||||
|
}
|
||||||
|
throw GenericException();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Iterable<int> ids;
|
Iterable<int> ids;
|
||||||
while (true) {
|
while (page <= maxPage) {
|
||||||
ids = await fetchIds(page);
|
ids = await fetchIds(page);
|
||||||
if (ids.isEmpty) {
|
if (ids.isEmpty) {
|
||||||
break;
|
break;
|
||||||
@ -40,6 +82,206 @@ class HackerNewsWebRepository {
|
|||||||
page++;
|
page++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
page = 1;
|
||||||
|
while (page <= maxPage) {
|
||||||
|
ids = await fetchIds(page, isComment: true);
|
||||||
|
if (ids.isEmpty) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
allIds.addAll(ids);
|
||||||
|
page++;
|
||||||
|
}
|
||||||
|
|
||||||
return allIds;
|
return allIds;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const String _itemBaseUrl = 'https://news.ycombinator.com/item?id=';
|
||||||
|
static const String _athingComtrSelector =
|
||||||
|
'#hnmain > tbody > tr > td > table > tbody > .athing.comtr';
|
||||||
|
static const String _commentTextSelector =
|
||||||
|
'''td > table > tbody > tr > td.default > div.comment''';
|
||||||
|
static const String _commentHeadSelector =
|
||||||
|
'''td > table > tbody > tr > td.default > div > span > a''';
|
||||||
|
static const String _commentAgeSelector =
|
||||||
|
'''td > table > tbody > tr > td.default > div > span > span.age''';
|
||||||
|
static const String _commentIndentSelector =
|
||||||
|
'''td > table > tbody > tr > td.ind''';
|
||||||
|
|
||||||
|
Stream<Comment> fetchCommentsStream(Item item) async* {
|
||||||
|
final bool isOnWifi = await _isOnWifi;
|
||||||
|
final int itemId = item.id;
|
||||||
|
final int? descendants = item is Story ? item.descendants : null;
|
||||||
|
int parentTextCount = 0;
|
||||||
|
|
||||||
|
Future<Iterable<Element>> fetchElements(int page) async {
|
||||||
|
try {
|
||||||
|
final Uri url = Uri.parse('$_itemBaseUrl$itemId&p=$page');
|
||||||
|
final Options option = Options(
|
||||||
|
headers: _headers,
|
||||||
|
persistentConnection: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
/// Be more conservative while user is on wifi.
|
||||||
|
final Response<String> response =
|
||||||
|
await (isOnWifi ? _dioWithCache : _dio).getUri<String>(
|
||||||
|
url,
|
||||||
|
options: option,
|
||||||
|
);
|
||||||
|
|
||||||
|
final String data = response.data ?? '';
|
||||||
|
|
||||||
|
if (page == 1) {
|
||||||
|
parentTextCount = 'parent'.allMatches(data).length;
|
||||||
|
}
|
||||||
|
|
||||||
|
final Document document = parse(data);
|
||||||
|
final List<Element> elements =
|
||||||
|
document.querySelectorAll(_athingComtrSelector);
|
||||||
|
return elements;
|
||||||
|
} on DioException catch (e) {
|
||||||
|
if (e.response?.statusCode == HttpStatus.forbidden) {
|
||||||
|
throw RateLimitedWithFallbackException();
|
||||||
|
}
|
||||||
|
throw GenericException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (descendants == 0 || item.kids.isEmpty) return;
|
||||||
|
|
||||||
|
final Set<int> fetchedCommentIds = <int>{};
|
||||||
|
int page = 1;
|
||||||
|
Iterable<Element> elements = await fetchElements(page);
|
||||||
|
final Map<int, int> indentToParentId = <int, int>{};
|
||||||
|
|
||||||
|
if (item is Story && item.descendants > 0 && elements.isEmpty) {
|
||||||
|
throw PossibleParsingException(itemId: itemId);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (elements.isNotEmpty) {
|
||||||
|
for (final Element element in elements) {
|
||||||
|
/// Get comment id.
|
||||||
|
final String cmtIdString = element.attributes['id'] ?? '';
|
||||||
|
final int? cmtId = int.tryParse(cmtIdString);
|
||||||
|
|
||||||
|
/// Get comment text.
|
||||||
|
final Element? cmtTextElement =
|
||||||
|
element.querySelector(_commentTextSelector);
|
||||||
|
final String parsedText = await compute(
|
||||||
|
_parseCommentTextHtml,
|
||||||
|
cmtTextElement?.innerHtml ?? '',
|
||||||
|
);
|
||||||
|
|
||||||
|
/// Get comment author.
|
||||||
|
final Element? cmtHeadElement =
|
||||||
|
element.querySelector(_commentHeadSelector);
|
||||||
|
final String? cmtAuthor = cmtHeadElement?.text;
|
||||||
|
|
||||||
|
/// Get comment age.
|
||||||
|
final Element? cmtAgeElement =
|
||||||
|
element.querySelector(_commentAgeSelector);
|
||||||
|
final String? ageString = cmtAgeElement?.attributes['title'];
|
||||||
|
|
||||||
|
final int? timestamp = ageString == null
|
||||||
|
? null
|
||||||
|
: DateTime.parse(ageString)
|
||||||
|
.copyWith(isUtc: true)
|
||||||
|
.millisecondsSinceEpoch;
|
||||||
|
|
||||||
|
/// Get comment indent.
|
||||||
|
final Element? cmtIndentElement =
|
||||||
|
element.querySelector(_commentIndentSelector);
|
||||||
|
final String? indentString = cmtIndentElement?.attributes['indent'];
|
||||||
|
final int indent =
|
||||||
|
indentString == null ? 0 : (int.tryParse(indentString) ?? 0);
|
||||||
|
|
||||||
|
indentToParentId[indent] = cmtId ?? 0;
|
||||||
|
final int parentId = indentToParentId[indent - 1] ?? -1;
|
||||||
|
|
||||||
|
final Comment cmt = Comment(
|
||||||
|
id: cmtId ?? 0,
|
||||||
|
time: timestamp ?? 0,
|
||||||
|
parent: parentId,
|
||||||
|
score: 0,
|
||||||
|
by: cmtAuthor ?? '',
|
||||||
|
text: parsedText,
|
||||||
|
kids: const <int>[],
|
||||||
|
dead: false,
|
||||||
|
deleted: false,
|
||||||
|
hidden: false,
|
||||||
|
level: indent,
|
||||||
|
isFromCache: false,
|
||||||
|
);
|
||||||
|
|
||||||
|
/// Skip any comment with no valid id or timestamp.
|
||||||
|
if (cmt.id == 0 || timestamp == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Duplicate comment means we are done fetching all the comments.
|
||||||
|
if (fetchedCommentIds.contains(cmt.id)) return;
|
||||||
|
|
||||||
|
fetchedCommentIds.add(cmt.id);
|
||||||
|
yield cmt;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If we didn't successfully got any comment on first page,
|
||||||
|
/// and we are sure there are comments there based on the count of
|
||||||
|
/// 'parent' text, then this might be a parsing error and possibly is
|
||||||
|
/// caused by HN changing their HTML structure, therefore here we
|
||||||
|
/// throw an error so that we can fallback to use API instead.
|
||||||
|
if (page == 1 && parentTextCount > 0 && fetchedCommentIds.isEmpty) {
|
||||||
|
throw PossibleParsingException(itemId: itemId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (descendants != null && fetchedCommentIds.length >= descendants) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Due to rate limiting, we have a short break here.
|
||||||
|
await Future<void>.delayed(AppDurations.twoSeconds);
|
||||||
|
|
||||||
|
page++;
|
||||||
|
elements = await fetchElements(page);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<bool> get _isOnWifi async {
|
||||||
|
final ConnectivityResult status = await Connectivity().checkConnectivity();
|
||||||
|
return status == ConnectivityResult.wifi;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<String> _parseCommentTextHtml(String text) async {
|
||||||
|
return HtmlUnescape()
|
||||||
|
.convert(text)
|
||||||
|
.replaceAllMapped(
|
||||||
|
RegExp(
|
||||||
|
r'\<div class="reply"\>(.*?)\<\/div\>',
|
||||||
|
dotAll: true,
|
||||||
|
),
|
||||||
|
(Match match) => '',
|
||||||
|
)
|
||||||
|
.replaceAllMapped(
|
||||||
|
RegExp(
|
||||||
|
r'\<span class="(.*?)"\>(.*?)\<\/span\>',
|
||||||
|
dotAll: true,
|
||||||
|
),
|
||||||
|
(Match match) => '${match[2]}',
|
||||||
|
)
|
||||||
|
.replaceAllMapped(
|
||||||
|
RegExp(
|
||||||
|
r'\<p\>(.*?)\<\/p\>',
|
||||||
|
dotAll: true,
|
||||||
|
),
|
||||||
|
(Match match) => '\n\n${match[1]}',
|
||||||
|
)
|
||||||
|
.replaceAllMapped(
|
||||||
|
RegExp(r'\<a href=\"(.*?)\".*?\>.*?\<\/a\>'),
|
||||||
|
(Match match) => match[1] ?? '',
|
||||||
|
)
|
||||||
|
.replaceAllMapped(
|
||||||
|
RegExp(r'\<i\>(.*?)\<\/i\>'),
|
||||||
|
(Match match) => '*${match[1]}*',
|
||||||
|
)
|
||||||
|
.trim();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,6 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:hacki/models/models.dart';
|
import 'package:hacki/models/models.dart';
|
||||||
import 'package:hacki/repositories/auth_repository.dart';
|
import 'package:hacki/repositories/auth_repository.dart';
|
||||||
import 'package:hacki/repositories/post_repository.dart';
|
import 'package:hacki/repositories/post_repository.dart';
|
||||||
import 'package:hacki/utils/service_exception.dart';
|
|
||||||
|
|
||||||
/// [PostableRepository] is solely for hosting functionalities shared between
|
/// [PostableRepository] is solely for hosting functionalities shared between
|
||||||
/// [AuthRepository] and [PostRepository].
|
/// [AuthRepository] and [PostRepository].
|
||||||
@ -40,7 +39,7 @@ class PostableRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} on ServiceException {
|
} on AppException {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -65,7 +64,7 @@ class PostableRepository {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
} on DioException catch (e) {
|
} on DioException catch (e) {
|
||||||
throw ServiceException(e.message);
|
throw AppException(message: e.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:hacki/models/models.dart';
|
import 'package:hacki/models/models.dart';
|
||||||
|
import 'package:hacki/services/services.dart';
|
||||||
import 'package:path/path.dart';
|
import 'package:path/path.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:sembast/sembast.dart';
|
import 'package:sembast/sembast.dart';
|
||||||
@ -12,23 +14,34 @@ import 'package:sembast/sembast_io.dart';
|
|||||||
/// documents directory assigned by host system which you can retrieve
|
/// documents directory assigned by host system which you can retrieve
|
||||||
/// by calling [getApplicationDocumentsDirectory].
|
/// by calling [getApplicationDocumentsDirectory].
|
||||||
class SembastRepository {
|
class SembastRepository {
|
||||||
SembastRepository({Database? database}) {
|
SembastRepository({
|
||||||
|
Database? database,
|
||||||
|
Database? cache,
|
||||||
|
}) {
|
||||||
if (database == null) {
|
if (database == null) {
|
||||||
initializeDatabase();
|
initializeDatabase();
|
||||||
} else {
|
} else {
|
||||||
_database = database;
|
_database = database;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (cache == null) {
|
||||||
|
initializeCache();
|
||||||
|
} else {
|
||||||
|
_cache = cache;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Database? _database;
|
Database? _database;
|
||||||
|
Database? _cache;
|
||||||
List<int>? _idsOfCommentsRepliedToMe;
|
List<int>? _idsOfCommentsRepliedToMe;
|
||||||
|
|
||||||
static const String _cachedCommentsKey = 'cachedComments';
|
static const String _cachedCommentsKey = 'cachedComments';
|
||||||
static const String _commentsKey = 'comments';
|
static const String _commentsKey = 'comments';
|
||||||
static const String _idsOfCommentsRepliedToMeKey = 'idsOfCommentsRepliedToMe';
|
static const String _idsOfCommentsRepliedToMeKey = 'idsOfCommentsRepliedToMe';
|
||||||
|
static const String _metadataCacheKey = 'metadata';
|
||||||
|
|
||||||
Future<Database> initializeDatabase() async {
|
Future<Database> initializeDatabase() async {
|
||||||
final Directory dir = await getApplicationDocumentsDirectory();
|
final Directory dir = await getApplicationCacheDirectory();
|
||||||
await dir.create(recursive: true);
|
await dir.create(recursive: true);
|
||||||
final String dbPath = join(dir.path, 'hacki.db');
|
final String dbPath = join(dir.path, 'hacki.db');
|
||||||
final DatabaseFactory dbFactory = databaseFactoryIo;
|
final DatabaseFactory dbFactory = databaseFactoryIo;
|
||||||
@ -37,6 +50,16 @@ class SembastRepository {
|
|||||||
return db;
|
return db;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<Database> initializeCache() async {
|
||||||
|
final Directory dir = await getTemporaryDirectory();
|
||||||
|
await dir.create(recursive: true);
|
||||||
|
final String dbPath = join(dir.path, 'hacki_cache.db');
|
||||||
|
final DatabaseFactory dbFactory = databaseFactoryIo;
|
||||||
|
final Database db = await dbFactory.openDatabase(dbPath);
|
||||||
|
_cache = db;
|
||||||
|
return db;
|
||||||
|
}
|
||||||
|
|
||||||
//#region Cached comments for time machine feature.
|
//#region Cached comments for time machine feature.
|
||||||
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();
|
||||||
@ -177,10 +200,50 @@ class SembastRepository {
|
|||||||
|
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
Future<FileSystemEntity> deleteAll() async {
|
//#region
|
||||||
|
|
||||||
|
Future<void> cacheMetadata({
|
||||||
|
required String key,
|
||||||
|
required WebInfo info,
|
||||||
|
}) async {
|
||||||
|
final Database db = _cache ?? await initializeCache();
|
||||||
|
final StoreRef<String, Map<String, Object?>> store =
|
||||||
|
stringMapStoreFactory.store(_metadataCacheKey);
|
||||||
|
|
||||||
|
return db.transaction((Transaction txn) async {
|
||||||
|
await store.record(key).put(txn, info.toJson());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<WebInfo?> getCachedMetadata({
|
||||||
|
required String key,
|
||||||
|
}) async {
|
||||||
|
final Database db = _cache ?? await initializeCache();
|
||||||
|
final StoreRef<String, Map<String, Object?>> store =
|
||||||
|
stringMapStoreFactory.store(_metadataCacheKey);
|
||||||
|
final RecordSnapshot<String, Map<String, Object?>>? snapshot =
|
||||||
|
await store.record(key).getSnapshot(db);
|
||||||
|
if (snapshot != null) {
|
||||||
|
final WebInfo info = WebInfo.fromJson(snapshot.value);
|
||||||
|
return info;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//#endregion
|
||||||
|
|
||||||
|
Future<FileSystemEntity> deleteCachedComments() async {
|
||||||
final Directory dir = await getApplicationDocumentsDirectory();
|
final Directory dir = await getApplicationDocumentsDirectory();
|
||||||
await dir.create(recursive: true);
|
await dir.create(recursive: true);
|
||||||
final String dbPath = join(dir.path, 'hacki.db');
|
final String dbPath = join(dir.path, 'hacki.db');
|
||||||
return File(dbPath).delete();
|
return File(dbPath).delete();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<FileSystemEntity> deleteCachedMetadata() async {
|
||||||
|
final Directory tempDir = await getTemporaryDirectory();
|
||||||
|
await tempDir.create(recursive: true);
|
||||||
|
final String cachePath = join(tempDir.path, 'hacki_cache.db');
|
||||||
|
return File(cachePath).delete();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -49,7 +49,7 @@ class _HomeScreenState extends State<HomeScreen>
|
|||||||
super.didPopNext();
|
super.didPopNext();
|
||||||
if (context.read<StoriesBloc>().deviceScreenType ==
|
if (context.read<StoriesBloc>().deviceScreenType ==
|
||||||
DeviceScreenType.mobile) {
|
DeviceScreenType.mobile) {
|
||||||
locator.get<Logger>().i('Resetting comments in CommentCache');
|
locator.get<Logger>().i('resetting comments in CommentCache');
|
||||||
Future<void>.delayed(
|
Future<void>.delayed(
|
||||||
AppDurations.ms500,
|
AppDurations.ms500,
|
||||||
locator.get<CommentCache>().resetComments,
|
locator.get<CommentCache>().resetComments,
|
||||||
|
@ -69,6 +69,7 @@ class ItemScreen extends StatefulWidget {
|
|||||||
BlocProvider<CommentsCubit>(
|
BlocProvider<CommentsCubit>(
|
||||||
create: (BuildContext context) => CommentsCubit(
|
create: (BuildContext context) => CommentsCubit(
|
||||||
filterCubit: context.read<FilterCubit>(),
|
filterCubit: context.read<FilterCubit>(),
|
||||||
|
preferenceCubit: context.read<PreferenceCubit>(),
|
||||||
isOfflineReading:
|
isOfflineReading:
|
||||||
context.read<StoriesBloc>().state.isOfflineReading,
|
context.read<StoriesBloc>().state.isOfflineReading,
|
||||||
item: args.item,
|
item: args.item,
|
||||||
@ -79,6 +80,8 @@ class ItemScreen extends StatefulWidget {
|
|||||||
onlyShowTargetComment: args.onlyShowTargetComment,
|
onlyShowTargetComment: args.onlyShowTargetComment,
|
||||||
targetAncestors: args.targetComments,
|
targetAncestors: args.targetComments,
|
||||||
useCommentCache: args.useCommentCache,
|
useCommentCache: args.useCommentCache,
|
||||||
|
onError: (AppException e) =>
|
||||||
|
context.showErrorSnackBar(e.message),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -110,6 +113,7 @@ class ItemScreen extends StatefulWidget {
|
|||||||
BlocProvider<CommentsCubit>(
|
BlocProvider<CommentsCubit>(
|
||||||
create: (BuildContext context) => CommentsCubit(
|
create: (BuildContext context) => CommentsCubit(
|
||||||
filterCubit: context.read<FilterCubit>(),
|
filterCubit: context.read<FilterCubit>(),
|
||||||
|
preferenceCubit: context.read<PreferenceCubit>(),
|
||||||
isOfflineReading:
|
isOfflineReading:
|
||||||
context.read<StoriesBloc>().state.isOfflineReading,
|
context.read<StoriesBloc>().state.isOfflineReading,
|
||||||
item: args.item,
|
item: args.item,
|
||||||
@ -121,6 +125,8 @@ class ItemScreen extends StatefulWidget {
|
|||||||
)..init(
|
)..init(
|
||||||
onlyShowTargetComment: args.onlyShowTargetComment,
|
onlyShowTargetComment: args.onlyShowTargetComment,
|
||||||
targetAncestors: args.targetComments,
|
targetAncestors: args.targetComments,
|
||||||
|
onError: (AppException e) =>
|
||||||
|
context.showErrorSnackBar(e.message),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -2,6 +2,7 @@ import 'package:animations/animations.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:hacki/blocs/auth/auth_bloc.dart';
|
import 'package:hacki/blocs/auth/auth_bloc.dart';
|
||||||
|
import 'package:hacki/config/constants.dart';
|
||||||
import 'package:hacki/cubits/comments/comments_cubit.dart';
|
import 'package:hacki/cubits/comments/comments_cubit.dart';
|
||||||
import 'package:hacki/models/models.dart';
|
import 'package:hacki/models/models.dart';
|
||||||
import 'package:hacki/screens/widgets/widgets.dart';
|
import 'package:hacki/screens/widgets/widgets.dart';
|
||||||
@ -65,9 +66,11 @@ class _InThreadSearchViewState extends State<_InThreadSearchView> {
|
|||||||
super.initState();
|
super.initState();
|
||||||
scrollController.addListener(onScroll);
|
scrollController.addListener(onScroll);
|
||||||
textEditingController.text = widget.commentsCubit.state.inThreadSearchQuery;
|
textEditingController.text = widget.commentsCubit.state.inThreadSearchQuery;
|
||||||
if (textEditingController.text.isEmpty) {
|
Future<void>.delayed(AppDurations.ms300, () {
|
||||||
focusNode.requestFocus();
|
if (textEditingController.text.isEmpty) {
|
||||||
}
|
focusNode.requestFocus();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -59,7 +59,12 @@ class MainView extends StatelessWidget {
|
|||||||
if (context.read<StoriesBloc>().state.isOfflineReading ==
|
if (context.read<StoriesBloc>().state.isOfflineReading ==
|
||||||
false &&
|
false &&
|
||||||
state.onlyShowTargetComment == false) {
|
state.onlyShowTargetComment == false) {
|
||||||
unawaited(context.read<CommentsCubit>().refresh());
|
unawaited(
|
||||||
|
context.read<CommentsCubit>().refresh(
|
||||||
|
onError: (AppException e) =>
|
||||||
|
context.showErrorSnackBar(e.message),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
if (state.item.isPoll) {
|
if (state.item.isPoll) {
|
||||||
context.read<PollCubit>().refresh();
|
context.read<PollCubit>().refresh();
|
||||||
@ -145,27 +150,28 @@ class MainView extends StatelessWidget {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Positioned(
|
if (context.read<PreferenceCubit>().state.devModeEnabled)
|
||||||
height: Dimens.pt4,
|
Positioned(
|
||||||
bottom: Dimens.zero,
|
height: Dimens.pt4,
|
||||||
left: Dimens.zero,
|
bottom: Dimens.zero,
|
||||||
right: Dimens.zero,
|
left: Dimens.zero,
|
||||||
child: BlocBuilder<CommentsCubit, CommentsState>(
|
right: Dimens.zero,
|
||||||
buildWhen: (CommentsState prev, CommentsState current) =>
|
child: BlocBuilder<CommentsCubit, CommentsState>(
|
||||||
prev.status != current.status,
|
buildWhen: (CommentsState prev, CommentsState current) =>
|
||||||
builder: (BuildContext context, CommentsState state) {
|
prev.status != current.status,
|
||||||
return AnimatedOpacity(
|
builder: (BuildContext context, CommentsState state) {
|
||||||
opacity: state.status == CommentsStatus.inProgress
|
return AnimatedOpacity(
|
||||||
? NumSwitch.on
|
opacity: state.status == CommentsStatus.inProgress
|
||||||
: NumSwitch.off,
|
? NumSwitch.on
|
||||||
duration: const Duration(
|
: NumSwitch.off,
|
||||||
milliseconds: _loadingIndicatorOpacityAnimationDuration,
|
duration: const Duration(
|
||||||
),
|
milliseconds: _loadingIndicatorOpacityAnimationDuration,
|
||||||
child: const LinearProgressIndicator(),
|
),
|
||||||
);
|
child: const LinearProgressIndicator(),
|
||||||
},
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -249,9 +255,13 @@ class _ParentItemSection extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
Text(
|
Text(
|
||||||
item.timeAgo,
|
context
|
||||||
style: const TextStyle(
|
.read<PreferenceCubit>()
|
||||||
color: Palette.grey,
|
.state
|
||||||
|
.displayDateFormat
|
||||||
|
.convertToString(item.time),
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context).metadataColor,
|
||||||
),
|
),
|
||||||
textScaler: MediaQuery.of(context).textScaler,
|
textScaler: MediaQuery.of(context).textScaler,
|
||||||
),
|
),
|
||||||
@ -505,6 +515,9 @@ class _ParentItemSection extends StatelessWidget {
|
|||||||
style: TextStyle(color: Palette.grey),
|
style: TextStyle(color: Palette.grey),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 120,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -147,22 +147,28 @@ class _ProfileScreenState extends State<ProfileScreen>
|
|||||||
builder: (
|
builder: (
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
Status status,
|
Status status,
|
||||||
) =>
|
) {
|
||||||
TextButton(
|
return TextButton(
|
||||||
onPressed: () {
|
onPressed: () =>
|
||||||
context.read<FavCubit>().merge();
|
context.read<FavCubit>().merge(
|
||||||
},
|
onError: (AppException e) =>
|
||||||
child: status == Status.inProgress
|
showErrorSnackBar(e.message),
|
||||||
? const SizedBox(
|
onSuccess: () => showSnackBar(
|
||||||
height: Dimens.pt12,
|
content: '''Sync completed.''',
|
||||||
width: Dimens.pt12,
|
),
|
||||||
child:
|
),
|
||||||
CustomCircularProgressIndicator(
|
child: status == Status.inProgress
|
||||||
strokeWidth: Dimens.pt2,
|
? const SizedBox(
|
||||||
),
|
height: Dimens.pt12,
|
||||||
)
|
width: Dimens.pt12,
|
||||||
: const Text('Sync from Hacker News'),
|
child:
|
||||||
),
|
CustomCircularProgressIndicator(
|
||||||
|
strokeWidth: Dimens.pt2,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: const Text('Sync from Hacker News'),
|
||||||
|
);
|
||||||
|
},
|
||||||
)
|
)
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
|
@ -89,8 +89,8 @@ class InboxView extends StatelessWidget {
|
|||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Text(
|
Text(
|
||||||
'''${e.timeAgo} from ${e.by}:''',
|
'''${e.timeAgo} from ${e.by}:''',
|
||||||
style: const TextStyle(
|
style: TextStyle(
|
||||||
color: Palette.grey,
|
color: Theme.of(context).metadataColor,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
|
@ -160,6 +160,47 @@ class _SettingsState extends State<Settings> with ItemActionMixin {
|
|||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: Dimens.pt12,
|
height: Dimens.pt12,
|
||||||
),
|
),
|
||||||
|
Row(
|
||||||
|
children: <Widget>[
|
||||||
|
const SizedBox(
|
||||||
|
width: Dimens.pt16,
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: <Widget>[
|
||||||
|
const Text(
|
||||||
|
'Date time display of comments',
|
||||||
|
),
|
||||||
|
DropdownMenu<DisplayDateFormat>(
|
||||||
|
initialSelection: preferenceState.displayDateFormat,
|
||||||
|
dropdownMenuEntries: DisplayDateFormat.values
|
||||||
|
.map(
|
||||||
|
(DisplayDateFormat val) =>
|
||||||
|
DropdownMenuEntry<DisplayDateFormat>(
|
||||||
|
value: val,
|
||||||
|
label: val.description,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
onSelected: (DisplayDateFormat? order) {
|
||||||
|
if (order != null) {
|
||||||
|
HapticFeedbackUtil.selection();
|
||||||
|
context.read<PreferenceCubit>().update(
|
||||||
|
DateFormatPreference(
|
||||||
|
val: order.index,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
DisplayDateFormat.clearCache();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: Dimens.pt12,
|
||||||
|
),
|
||||||
const TabBarSettings(),
|
const TabBarSettings(),
|
||||||
const TextScaleFactorSettings(),
|
const TextScaleFactorSettings(),
|
||||||
const Divider(),
|
const Divider(),
|
||||||
@ -301,6 +342,17 @@ class _SettingsState extends State<Settings> with ItemActionMixin {
|
|||||||
title: const Text('About'),
|
title: const Text('About'),
|
||||||
subtitle: const Text('nothing interesting here.'),
|
subtitle: const Text('nothing interesting here.'),
|
||||||
onTap: showAboutHackiDialog,
|
onTap: showAboutHackiDialog,
|
||||||
|
onLongPress: () {
|
||||||
|
final DevMode updatedDevMode =
|
||||||
|
DevMode(val: !preferenceState.devModeEnabled);
|
||||||
|
context.read<PreferenceCubit>().update(updatedDevMode);
|
||||||
|
HapticFeedbackUtil.heavy();
|
||||||
|
if (updatedDevMode.val) {
|
||||||
|
showSnackBar(content: 'You are a dev now.');
|
||||||
|
} else {
|
||||||
|
showSnackBar(content: 'Dev mode disabled');
|
||||||
|
}
|
||||||
|
},
|
||||||
),
|
),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: Dimens.pt48,
|
height: Dimens.pt48,
|
||||||
@ -498,6 +550,12 @@ class _SettingsState extends State<Settings> with ItemActionMixin {
|
|||||||
.whenComplete(
|
.whenComplete(
|
||||||
DefaultCacheManager().emptyCache,
|
DefaultCacheManager().emptyCache,
|
||||||
)
|
)
|
||||||
|
.whenComplete(
|
||||||
|
locator.get<SembastRepository>().deleteCachedComments,
|
||||||
|
)
|
||||||
|
.whenComplete(
|
||||||
|
locator.get<SembastRepository>().deleteCachedMetadata,
|
||||||
|
)
|
||||||
.whenComplete(() {
|
.whenComplete(() {
|
||||||
showSnackBar(content: 'Cache cleared!');
|
showSnackBar(content: 'Cache cleared!');
|
||||||
});
|
});
|
||||||
@ -645,6 +703,9 @@ class _SettingsState extends State<Settings> with ItemActionMixin {
|
|||||||
context: context,
|
context: context,
|
||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
|
actionsPadding: const EdgeInsets.all(
|
||||||
|
Dimens.pt16,
|
||||||
|
),
|
||||||
actions: <Widget>[
|
actions: <Widget>[
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
onPressed: onSendEmailTapped,
|
onPressed: onSendEmailTapped,
|
||||||
|
@ -188,20 +188,22 @@ class CommentTile extends StatelessWidget {
|
|||||||
color: Palette.grey,
|
color: Palette.grey,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (!comment.dead && isNew)
|
// Commented out for now, maybe review later.
|
||||||
const Padding(
|
// if (!comment.dead && isNew)
|
||||||
padding: EdgeInsets.only(left: 4),
|
// const Padding(
|
||||||
child: Icon(
|
// padding: EdgeInsets.only(left: 4),
|
||||||
Icons.sunny_snowing,
|
// child: Icon(
|
||||||
size: 16,
|
// Icons.sunny_snowing,
|
||||||
color: Palette.grey,
|
// size: 16,
|
||||||
),
|
// color: Palette.grey,
|
||||||
),
|
// ),
|
||||||
|
// ),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
Text(
|
Text(
|
||||||
comment.timeAgo,
|
prefState.displayDateFormat
|
||||||
style: const TextStyle(
|
.convertToString(comment.time),
|
||||||
color: Palette.grey,
|
style: TextStyle(
|
||||||
|
color: Theme.of(context).metadataColor,
|
||||||
),
|
),
|
||||||
textScaler: MediaQuery.of(context).textScaler,
|
textScaler: MediaQuery.of(context).textScaler,
|
||||||
),
|
),
|
||||||
|
@ -97,8 +97,8 @@ class ItemsListView<T extends Item> extends StatelessWidget {
|
|||||||
showAuthor
|
showAuthor
|
||||||
? '''${e.timeAgo} by ${e.by}'''
|
? '''${e.timeAgo} by ${e.by}'''
|
||||||
: e.timeAgo,
|
: e.timeAgo,
|
||||||
style: const TextStyle(
|
style: TextStyle(
|
||||||
color: Palette.grey,
|
color: Theme.of(context).metadataColor,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
@ -147,6 +147,11 @@ class ItemsListView<T extends Item> extends StatelessWidget {
|
|||||||
if (useSimpleTileForStory || !showWebPreviewOnStoryTile)
|
if (useSimpleTileForStory || !showWebPreviewOnStoryTile)
|
||||||
const Divider(
|
const Divider(
|
||||||
height: Dimens.zero,
|
height: Dimens.zero,
|
||||||
|
)
|
||||||
|
else if (context.read<SplitViewCubit>().state.enabled)
|
||||||
|
const Divider(
|
||||||
|
height: Dimens.pt6,
|
||||||
|
color: Palette.transparent,
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
} else if (e is Comment) {
|
} else if (e is Comment) {
|
||||||
@ -186,8 +191,8 @@ class ItemsListView<T extends Item> extends StatelessWidget {
|
|||||||
showAuthor
|
showAuthor
|
||||||
? '''${e.timeAgo} by ${e.by}'''
|
? '''${e.timeAgo} by ${e.by}'''
|
||||||
: e.timeAgo,
|
: e.timeAgo,
|
||||||
style: const TextStyle(
|
style: TextStyle(
|
||||||
color: Palette.grey,
|
color: Theme.of(context).metadataColor,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
|
@ -119,10 +119,15 @@ class LinkView extends StatelessWidget {
|
|||||||
: CachedNetworkImage(
|
: CachedNetworkImage(
|
||||||
imageUrl: imageUri!,
|
imageUrl: imageUri!,
|
||||||
fit: isIcon ? BoxFit.scaleDown : BoxFit.fitWidth,
|
fit: isIcon ? BoxFit.scaleDown : BoxFit.fitWidth,
|
||||||
memCacheHeight: layoutHeight.toInt() * 4,
|
|
||||||
cacheKey: imageUri,
|
cacheKey: imageUri,
|
||||||
errorWidget: (_, __, ___) =>
|
errorWidget: (_, __, ___) => Center(
|
||||||
const SizedBox.shrink(),
|
child: Text(
|
||||||
|
r'¯\_(ツ)_/¯',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -17,7 +17,6 @@ class _OnboardingViewState extends State<OnboardingView> {
|
|||||||
final Throttle throttle = Throttle(delay: _throttleDelay);
|
final Throttle throttle = Throttle(delay: _throttleDelay);
|
||||||
|
|
||||||
static const Duration _throttleDelay = AppDurations.ms100;
|
static const Duration _throttleDelay = AppDurations.ms100;
|
||||||
static const double _screenshotHeight = 600;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -44,7 +43,7 @@ class _OnboardingViewState extends State<OnboardingView> {
|
|||||||
left: Dimens.zero,
|
left: Dimens.zero,
|
||||||
right: Dimens.zero,
|
right: Dimens.zero,
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
height: _screenshotHeight,
|
height: MediaQuery.of(context).size.height * 0.8,
|
||||||
child: PageView(
|
child: PageView(
|
||||||
controller: pageController,
|
controller: pageController,
|
||||||
scrollDirection: Axis.vertical,
|
scrollDirection: Axis.vertical,
|
||||||
@ -69,7 +68,7 @@ class _OnboardingViewState extends State<OnboardingView> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Positioned(
|
Positioned(
|
||||||
bottom: Dimens.pt40,
|
bottom: MediaQuery.of(context).viewPadding.bottom,
|
||||||
left: Dimens.zero,
|
left: Dimens.zero,
|
||||||
right: Dimens.zero,
|
right: Dimens.zero,
|
||||||
child: ElevatedButton(
|
child: ElevatedButton(
|
||||||
@ -115,16 +114,14 @@ class _PageViewChild extends StatelessWidget {
|
|||||||
final String path;
|
final String path;
|
||||||
final String description;
|
final String description;
|
||||||
|
|
||||||
static const double _height = 500;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Column(
|
return Column(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Material(
|
Material(
|
||||||
elevation: 8,
|
elevation: Dimens.pt8,
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
height: _height,
|
height: MediaQuery.of(context).size.height * 0.5,
|
||||||
child: Image.asset(path),
|
child: Image.asset(path),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -14,9 +14,9 @@ import 'package:html/dom.dart' hide Comment, Text;
|
|||||||
import 'package:html/parser.dart' as parser;
|
import 'package:html/parser.dart' as parser;
|
||||||
import 'package:http/http.dart';
|
import 'package:http/http.dart';
|
||||||
import 'package:http/io_client.dart';
|
import 'package:http/io_client.dart';
|
||||||
|
import 'package:logger/logger.dart';
|
||||||
|
|
||||||
abstract class InfoBase {
|
abstract class InfoBase {
|
||||||
late DateTime _timeout;
|
|
||||||
late bool _shouldRetry;
|
late bool _shouldRetry;
|
||||||
|
|
||||||
Map<String, dynamic> toJson();
|
Map<String, dynamic> toJson();
|
||||||
@ -97,13 +97,10 @@ class WebAnalyzer {
|
|||||||
/// Get web information
|
/// Get web information
|
||||||
/// return [InfoBase]
|
/// return [InfoBase]
|
||||||
static InfoBase? getInfoFromCache(String? cacheKey) {
|
static InfoBase? getInfoFromCache(String? cacheKey) {
|
||||||
|
if (cacheKey == null) return null;
|
||||||
|
|
||||||
final InfoBase? info = cacheMap[cacheKey];
|
final InfoBase? info = cacheMap[cacheKey];
|
||||||
|
|
||||||
if (info != null) {
|
|
||||||
if (!info._timeout.isAfter(DateTime.now())) {
|
|
||||||
cacheMap.remove(cacheKey);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -118,23 +115,31 @@ class WebAnalyzer {
|
|||||||
final String key = getKey(story);
|
final String key = getKey(story);
|
||||||
final String url = story.url;
|
final String url = story.url;
|
||||||
|
|
||||||
|
/// [1] Try to fetch from mem cache.
|
||||||
InfoBase? info = getInfoFromCache(key);
|
InfoBase? info = getInfoFromCache(key);
|
||||||
|
|
||||||
if (info != null) return info;
|
if (info != null) {
|
||||||
|
locator.get<Logger>().d('''
|
||||||
|
fetched mem cached metadata using key $key for $story:
|
||||||
|
${info.toJson()}
|
||||||
|
''');
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// [2] If story doesn't have a url and text is not empty,
|
||||||
|
/// just use story title and text.
|
||||||
if (story.url.isEmpty && story.text.isNotEmpty) {
|
if (story.url.isEmpty && story.text.isNotEmpty) {
|
||||||
info = WebInfo(
|
info = WebInfo(
|
||||||
title: story.title,
|
title: story.title,
|
||||||
description: story.text,
|
description: story.text,
|
||||||
)
|
).._shouldRetry = false;
|
||||||
.._timeout = DateTime.now().add(cache)
|
|
||||||
.._shouldRetry = false;
|
|
||||||
|
|
||||||
cacheMap[key] = info;
|
cacheMap[key] = info;
|
||||||
|
|
||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// [3] If in offline mode, use comment text for description.
|
||||||
if (offlineReading) {
|
if (offlineReading) {
|
||||||
int index = 0;
|
int index = 0;
|
||||||
Comment? comment;
|
Comment? comment;
|
||||||
@ -149,9 +154,7 @@ class WebAnalyzer {
|
|||||||
info = WebInfo(
|
info = WebInfo(
|
||||||
title: story.title,
|
title: story.title,
|
||||||
description: comment != null ? '${comment.by}: ${comment.text}' : null,
|
description: comment != null ? '${comment.by}: ${comment.text}' : null,
|
||||||
)
|
).._shouldRetry = false;
|
||||||
.._shouldRetry = false
|
|
||||||
.._timeout = DateTime.now();
|
|
||||||
|
|
||||||
cacheMap[key] = info;
|
cacheMap[key] = info;
|
||||||
|
|
||||||
@ -159,15 +162,41 @@ class WebAnalyzer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
/// [4] Try to fetch from file cache.
|
||||||
|
info = await locator.get<SembastRepository>().getCachedMetadata(key: key);
|
||||||
|
|
||||||
|
/// [5] If there is file cache, move it to mem cache for later retrieval.
|
||||||
|
if (info != null) {
|
||||||
|
locator.get<Logger>().d('''
|
||||||
|
fetched file cached metadata using key $key for $story:
|
||||||
|
${info.toJson()}
|
||||||
|
''');
|
||||||
|
cacheMap[key] = info;
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// [6] Try to analyze the web for metadata.
|
||||||
info = await _getInfoByIsolate(
|
info = await _getInfoByIsolate(
|
||||||
url: url,
|
url: url,
|
||||||
multimedia: multimedia,
|
multimedia: multimedia,
|
||||||
story: story,
|
story: story,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/// [7] If web analyzing was successful, cache it in both mem and file.
|
||||||
if (info != null && !info._shouldRetry) {
|
if (info != null && !info._shouldRetry) {
|
||||||
info._timeout = DateTime.now().add(cache);
|
|
||||||
cacheMap[key] = info;
|
cacheMap[key] = info;
|
||||||
|
|
||||||
|
if (info is WebInfo) {
|
||||||
|
locator
|
||||||
|
.get<Logger>()
|
||||||
|
.d('caching metadata using key $key for $story.');
|
||||||
|
unawaited(
|
||||||
|
locator.get<SembastRepository>().cacheMetadata(
|
||||||
|
key: key,
|
||||||
|
info: info,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return info;
|
return info;
|
||||||
@ -175,9 +204,7 @@ class WebAnalyzer {
|
|||||||
return WebInfo(
|
return WebInfo(
|
||||||
title: story.title,
|
title: story.title,
|
||||||
description: story.text,
|
description: story.text,
|
||||||
)
|
).._shouldRetry = true;
|
||||||
.._shouldRetry = true
|
|
||||||
.._timeout = DateTime.now();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -393,10 +420,9 @@ class WebAnalyzer {
|
|||||||
try {
|
try {
|
||||||
html = gbk.decode(response.bodyBytes);
|
html = gbk.decode(response.bodyBytes);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// locator.get<Logger>().log(
|
locator
|
||||||
// Level.error,
|
.get<Logger>()
|
||||||
// 'Web page resolution failure from:$url Error:$e',
|
.e('''web page resolution failure from:$url Error:$e''');
|
||||||
// );
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
extension ThemeDataExtension on ThemeData {
|
extension ThemeDataExtension on ThemeData {
|
||||||
Color get readGrey => colorScheme.onSurface.withOpacity(0.4);
|
Color get readGrey => colorScheme.onSurface.withOpacity(0.6);
|
||||||
|
|
||||||
Color get metadataColor => colorScheme.onSurface.withOpacity(0.6);
|
Color get metadataColor => colorScheme.onSurface.withOpacity(0.8);
|
||||||
}
|
}
|
||||||
|
46
lib/utils/dio_interceptors/cache_interceptor.dart
Normal file
46
lib/utils/dio_interceptors/cache_interceptor.dart
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:dio/dio.dart';
|
||||||
|
import 'package:hacki/config/constants.dart';
|
||||||
|
import 'package:hacki/models/dio/cached_response.dart';
|
||||||
|
|
||||||
|
class CacheInterceptor extends InterceptorsWrapper {
|
||||||
|
CacheInterceptor()
|
||||||
|
: super(
|
||||||
|
onResponse: (
|
||||||
|
Response<dynamic> response,
|
||||||
|
ResponseInterceptorHandler handler,
|
||||||
|
) async {
|
||||||
|
final String key = response.requestOptions.uri.toString();
|
||||||
|
|
||||||
|
if (response.statusCode == HttpStatus.ok) {
|
||||||
|
final CachedResponse<dynamic> cachedResponse =
|
||||||
|
CachedResponse<dynamic>.fromResponse(response);
|
||||||
|
_cache[key] = cachedResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
return handler.next(response);
|
||||||
|
},
|
||||||
|
onRequest: (
|
||||||
|
RequestOptions options,
|
||||||
|
RequestInterceptorHandler handler,
|
||||||
|
) async {
|
||||||
|
final String key = options.uri.toString();
|
||||||
|
final CachedResponse<dynamic>? cachedResponse = _cache[key];
|
||||||
|
|
||||||
|
if (cachedResponse != null &&
|
||||||
|
DateTime.now()
|
||||||
|
.difference(cachedResponse.setDateTime)
|
||||||
|
.inSeconds <
|
||||||
|
_delay.inSeconds) {
|
||||||
|
return handler.resolve(cachedResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
return handler.next(options);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
static const Duration _delay = AppDurations.oneMinute;
|
||||||
|
static final Map<String, CachedResponse<dynamic>> _cache =
|
||||||
|
<String, CachedResponse<dynamic>>{};
|
||||||
|
}
|
2
lib/utils/dio_interceptors/interceptors.dart
Normal file
2
lib/utils/dio_interceptors/interceptors.dart
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export 'cache_interceptor.dart';
|
||||||
|
export 'logger_interceptor.dart';
|
14
lib/utils/dio_interceptors/logger_interceptor.dart
Normal file
14
lib/utils/dio_interceptors/logger_interceptor.dart
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import 'package:pretty_dio_logger/pretty_dio_logger.dart';
|
||||||
|
|
||||||
|
class LoggerInterceptor extends PrettyDioLogger {
|
||||||
|
LoggerInterceptor()
|
||||||
|
: super(
|
||||||
|
requestHeader: true,
|
||||||
|
requestBody: true,
|
||||||
|
responseBody: false,
|
||||||
|
responseHeader: true,
|
||||||
|
error: true,
|
||||||
|
compact: true,
|
||||||
|
maxWidth: 90,
|
||||||
|
);
|
||||||
|
}
|
@ -14,4 +14,10 @@ abstract class HapticFeedbackUtil {
|
|||||||
HapticFeedback.lightImpact();
|
HapticFeedback.lightImpact();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void heavy() {
|
||||||
|
if (enabled) {
|
||||||
|
HapticFeedback.heavyImpact();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -79,16 +79,15 @@ abstract class LinkUtil {
|
|||||||
final Color primaryColor = Theme.of(context).colorScheme.primary;
|
final Color primaryColor = Theme.of(context).colorScheme.primary;
|
||||||
_browser
|
_browser
|
||||||
.open(
|
.open(
|
||||||
url: uri,
|
url: WebUri.uri(uri),
|
||||||
options: ChromeSafariBrowserClassOptions(
|
settings: Platform.isAndroid
|
||||||
ios: IOSSafariOptions(
|
? ChromeSafariBrowserSettings(
|
||||||
entersReaderIfAvailable: useReader,
|
toolbarBackgroundColor: primaryColor,
|
||||||
preferredControlTintColor: primaryColor,
|
)
|
||||||
),
|
: ChromeSafariBrowserSettings(
|
||||||
android: AndroidChromeCustomTabsOptions(
|
entersReaderIfAvailable: useReader,
|
||||||
toolbarBackgroundColor: primaryColor,
|
preferredControlTintColor: primaryColor,
|
||||||
),
|
),
|
||||||
),
|
|
||||||
)
|
)
|
||||||
.onError((_, __) => launchUrl(uri));
|
.onError((_, __) => launchUrl(uri));
|
||||||
}
|
}
|
||||||
|
@ -1,14 +0,0 @@
|
|||||||
class ServiceException implements Exception {
|
|
||||||
ServiceException([this.message]);
|
|
||||||
|
|
||||||
final String? message;
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() {
|
|
||||||
String result = 'ServiceException';
|
|
||||||
if (message != null) {
|
|
||||||
result = '$result: $message';
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,9 +1,9 @@
|
|||||||
export 'debouncer.dart';
|
export 'debouncer.dart';
|
||||||
|
export 'dio_interceptors/interceptors.dart';
|
||||||
export 'haptic_feedback_util.dart';
|
export 'haptic_feedback_util.dart';
|
||||||
export 'html_util.dart';
|
export 'html_util.dart';
|
||||||
export 'link_util.dart';
|
export 'link_util.dart';
|
||||||
export 'linkifier_util.dart';
|
export 'linkifier_util.dart';
|
||||||
export 'log_util.dart';
|
export 'log_util.dart';
|
||||||
export 'service_exception.dart';
|
|
||||||
export 'theme_util.dart';
|
export 'theme_util.dart';
|
||||||
export 'throttle.dart';
|
export 'throttle.dart';
|
||||||
|
404
pubspec.lock
404
pubspec.lock
@ -5,34 +5,34 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: _fe_analyzer_shared
|
name: _fe_analyzer_shared
|
||||||
sha256: ae92f5d747aee634b87f89d9946000c2de774be1d6ac3e58268224348cd0101a
|
sha256: "0b2f2bd91ba804e53a61d757b986f89f1f9eaed5b11e4b2f5a2468d86d6c9fc7"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "61.0.0"
|
version: "67.0.0"
|
||||||
adaptive_theme:
|
adaptive_theme:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: adaptive_theme
|
name: adaptive_theme
|
||||||
sha256: "28df95a6b86993b38a51ee97d33a9f1d845fd1c7320c21c5d5e2183b5605e152"
|
sha256: f4ee609b464e5efc68131d9d15ba9aa1de4e3b5ede64be17781c6e19a52d637d
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.4.0"
|
version: "3.6.0"
|
||||||
analyzer:
|
analyzer:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: analyzer
|
name: analyzer
|
||||||
sha256: ea3d8652bda62982addfd92fdc2d0214e5f82e43325104990d4f4c4a2a313562
|
sha256: "37577842a27e4338429a1cbc32679d508836510b056f1eedf0c8d20e39c1383d"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.13.0"
|
version: "6.4.1"
|
||||||
animations:
|
animations:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: animations
|
name: animations
|
||||||
sha256: ef57563eed3620bd5d75ad96189846aca1e033c0c45fc9a7d26e80ab02b88a70
|
sha256: d3d6dcfb218225bbe68e87ccf6378bbb2e32a94900722c5f81611dad089911cb
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.8"
|
version: "2.0.11"
|
||||||
args:
|
args:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -61,26 +61,26 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: bloc
|
name: bloc
|
||||||
sha256: "3820f15f502372d979121de1f6b97bfcf1630ebff8fe1d52fb2b0bfa49be5b49"
|
sha256: f53a110e3b48dcd78136c10daa5d51512443cea5e1348c9d80a320095fa2db9e
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "8.1.2"
|
version: "8.1.3"
|
||||||
bloc_concurrency:
|
bloc_concurrency:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: bloc_concurrency
|
name: bloc_concurrency
|
||||||
sha256: "44535c9f429cd7e91d548cf89fde1c23a8b4b3637decdb1865bb583091a00d4e"
|
sha256: "5857eb6653b4dd5e30e1ffab91037957fd64a9b9c5e53d26714ef25a46c04679"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.2.2"
|
version: "0.2.4"
|
||||||
bloc_test:
|
bloc_test:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: bloc_test
|
name: bloc_test
|
||||||
sha256: af0de1a1e16a7536e95dcd7491e0a6d6078e11d2d691988e862280b74f5c7968
|
sha256: "55a48f69e0d480717067c5377c8485a3fcd41f1701a820deef72fa0f4ee7215f"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "9.1.4"
|
version: "9.1.6"
|
||||||
boolean_selector:
|
boolean_selector:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -93,26 +93,26 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: cached_network_image
|
name: cached_network_image
|
||||||
sha256: f98972704692ba679db144261172a8e20feb145636c617af0eb4022132a6797f
|
sha256: "28ea9690a8207179c319965c13cd8df184d5ee721ae2ce60f398ced1219cea1f"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.3.0"
|
version: "3.3.1"
|
||||||
cached_network_image_platform_interface:
|
cached_network_image_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: cached_network_image_platform_interface
|
name: cached_network_image_platform_interface
|
||||||
sha256: "56aa42a7a01e3c9db8456d9f3f999931f1e05535b5a424271e9a38cabf066613"
|
sha256: "9e90e78ae72caa874a323d78fa6301b3fb8fa7ea76a8f96dc5b5bf79f283bf2f"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.0"
|
version: "4.0.0"
|
||||||
cached_network_image_web:
|
cached_network_image_web:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: cached_network_image_web
|
name: cached_network_image_web
|
||||||
sha256: "759b9a9f8f6ccbb66c185df805fac107f05730b1dab9c64626d1008cca532257"
|
sha256: "42a835caa27c220d1294311ac409a43361088625a4f23c820b006dd9bffb3316"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.0"
|
version: "1.1.1"
|
||||||
characters:
|
characters:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -149,10 +149,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: connectivity_plus
|
name: connectivity_plus
|
||||||
sha256: b502a681ba415272ecc41400bd04fe543ed1a62632137dc84d25a91e7746f55f
|
sha256: "224a77051d52a11fbad53dd57827594d3bd24f945af28bd70bab376d68d437f0"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.0.1"
|
version: "5.0.2"
|
||||||
connectivity_plus_platform_interface:
|
connectivity_plus_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -173,18 +173,18 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: coverage
|
name: coverage
|
||||||
sha256: "2fb815080e44a09b85e0f2ca8a820b15053982b2e714b59267719e8a9ff17097"
|
sha256: "8acabb8306b57a409bf4c83522065672ee13179297a6bb0cb9ead73948df7c76"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.6.3"
|
version: "1.7.2"
|
||||||
cross_file:
|
cross_file:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: cross_file
|
name: cross_file
|
||||||
sha256: fd832b5384d0d6da4f6df60b854d33accaaeb63aa9e10e736a87381f08dee2cb
|
sha256: fedaadfa3a6996f75211d835aaeb8fede285dae94262485698afd832371b9a5e
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.3.3+5"
|
version: "0.3.3+8"
|
||||||
crypto:
|
crypto:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -205,18 +205,18 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: dbus
|
name: dbus
|
||||||
sha256: "6f07cba3f7b3448d42d015bfd3d53fe12e5b36da2423f23838efc1d5fb31a263"
|
sha256: "365c771ac3b0e58845f39ec6deebc76e3276aa9922b0cc60840712094d9047ac"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.8"
|
version: "0.7.10"
|
||||||
device_info_plus:
|
device_info_plus:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: device_info_plus
|
name: device_info_plus
|
||||||
sha256: "7035152271ff67b072a211152846e9f1259cf1be41e34cd3e0b5463d2d6b8419"
|
sha256: "77f757b789ff68e4eaf9c56d1752309bd9f7ad557cb105b938a7f8eb89e59110"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "9.1.0"
|
version: "9.1.2"
|
||||||
device_info_plus_platform_interface:
|
device_info_plus_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -237,10 +237,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: dio
|
name: dio
|
||||||
sha256: "417e2a6f9d83ab396ec38ff4ea5da6c254da71e4db765ad737a42af6930140b7"
|
sha256: "797e1e341c3dd2f69f2dad42564a6feff3bfb87187d05abb93b9609e6f1645c3"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.3.3"
|
version: "5.4.0"
|
||||||
equatable:
|
equatable:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -278,18 +278,26 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: ffi
|
name: ffi
|
||||||
sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878"
|
sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.0"
|
version: "2.1.2"
|
||||||
file:
|
file:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: file
|
name: file
|
||||||
sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d"
|
sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.1.4"
|
version: "7.0.0"
|
||||||
|
fixnum:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: fixnum
|
||||||
|
sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.0"
|
||||||
flutter:
|
flutter:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description: flutter
|
description: flutter
|
||||||
@ -299,10 +307,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: flutter_bloc
|
name: flutter_bloc
|
||||||
sha256: e74efb89ee6945bcbce74a5b3a5a3376b088e5f21f55c263fc38cbdc6237faae
|
sha256: "87325da1ac757fcc4813e6b34ed5dd61169973871fdf181d6c2109dd6935ece1"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "8.1.3"
|
version: "8.1.4"
|
||||||
flutter_cache_manager:
|
flutter_cache_manager:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -344,18 +352,66 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: flutter_inappwebview
|
name: flutter_inappwebview
|
||||||
sha256: d198297060d116b94048301ee6749cd2e7d03c1f2689783f52d210a6b7aba350
|
sha256: "3e9a443a18ecef966fb930c3a76ca5ab6a7aafc0c7b5e14a4a850cf107b09959"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.8.0"
|
version: "6.0.0"
|
||||||
|
flutter_inappwebview_android:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_inappwebview_android
|
||||||
|
sha256: d247f6ed417f1f8c364612fa05a2ecba7f775c8d0c044c1d3b9ee33a6515c421
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.13"
|
||||||
|
flutter_inappwebview_internal_annotations:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_inappwebview_internal_annotations
|
||||||
|
sha256: "5f80fd30e208ddded7dbbcd0d569e7995f9f63d45ea3f548d8dd4c0b473fb4c8"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.1"
|
||||||
|
flutter_inappwebview_ios:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_inappwebview_ios
|
||||||
|
sha256: f363577208b97b10b319cd0c428555cd8493e88b468019a8c5635a0e4312bd0f
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.13"
|
||||||
|
flutter_inappwebview_macos:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_inappwebview_macos
|
||||||
|
sha256: b55b9e506c549ce88e26580351d2c71d54f4825901666bd6cfa4be9415bb2636
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.11"
|
||||||
|
flutter_inappwebview_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_inappwebview_platform_interface
|
||||||
|
sha256: "545fd4c25a07d2775f7d5af05a979b2cac4fbf79393b0a7f5d33ba39ba4f6187"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.10"
|
||||||
|
flutter_inappwebview_web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_inappwebview_web
|
||||||
|
sha256: d8c680abfb6fec71609a700199635d38a744df0febd5544c5a020bd73de8ee07
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.8"
|
||||||
flutter_local_notifications:
|
flutter_local_notifications:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: flutter_local_notifications
|
name: flutter_local_notifications
|
||||||
sha256: "6d11ea777496061e583623aaf31923f93a9409ef8fcaeeefdd6cd78bf4fe5bb3"
|
sha256: c18f1de98fe0bb9dd5ba91e1330d4febc8b6a7de6aae3ffe475ef423723e72f3
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "16.1.0"
|
version: "16.3.2"
|
||||||
flutter_local_notifications_linux:
|
flutter_local_notifications_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -440,10 +496,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: flutter_slidable
|
name: flutter_slidable
|
||||||
sha256: cc4231579e3eae41ae166660df717f4bad1359c87f4a4322ad8ba1befeb3d2be
|
sha256: "19ed4813003a6ff4e9c6bcce37e792a2a358919d7603b2b31ff200229191e44c"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.0"
|
version: "3.0.1"
|
||||||
flutter_test:
|
flutter_test:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description: flutter
|
description: flutter
|
||||||
@ -458,10 +514,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: font_awesome_flutter
|
name: font_awesome_flutter
|
||||||
sha256: "5fb789145cae1f4c3245c58b3f8fb287d055c26323879eab57a7bf0cfd1e45f3"
|
sha256: "275ff26905134bcb59417cf60ad979136f1f8257f2f449914b2c3e05bbb4cd6f"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "10.5.0"
|
version: "10.7.0"
|
||||||
frontend_server_client:
|
frontend_server_client:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -479,10 +535,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: get_it
|
name: get_it
|
||||||
sha256: f79870884de16d689cf9a7d15eedf31ed61d750e813c538a6efb92660fea83c3
|
sha256: e6017ce7fdeaf218dc51a100344d8cb70134b80e28b760f8bb23c242437bafd7
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "7.6.4"
|
version: "7.6.7"
|
||||||
glob:
|
glob:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -495,10 +551,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: go_router
|
name: go_router
|
||||||
sha256: c247a4f76071c3b97bb5ae8912968870d5565644801c5e09f3bc961b4d874895
|
sha256: "170c46e237d6eb0e6e9f0e8b3f56101e14fb64f787016e42edd74c39cf8b176a"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "12.1.1"
|
version: "13.2.0"
|
||||||
hive:
|
hive:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -527,10 +583,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: http
|
name: http
|
||||||
sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525"
|
sha256: a2bbf9d017fcced29139daa8ed2bba4ece450ab222871df93ca9eec6f80c34ba
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.0"
|
version: "1.2.0"
|
||||||
http_multi_server:
|
http_multi_server:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -551,10 +607,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: hydrated_bloc
|
name: hydrated_bloc
|
||||||
sha256: "24994e61f64904d911683cce1a31dc4ef611619da5253f1de2b7b8fc6f79a118"
|
sha256: "00a2099680162e74b5a836b8a7f446e478520a9cae9f6032e028ad8129f4432d"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "9.1.2"
|
version: "9.1.4"
|
||||||
in_app_review:
|
in_app_review:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -566,10 +622,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: in_app_review_platform_interface
|
name: in_app_review_platform_interface
|
||||||
sha256: b12ec9aaf6b34d3a72aa95895eb252b381896246bdad4ef378d444affe8410ef
|
sha256: fed2c755f2125caa9ae10495a3c163aa7fab5af3585a9c62ef4a6920c5b45f10
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.4"
|
version: "2.0.5"
|
||||||
integration_test:
|
integration_test:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description: flutter
|
description: flutter
|
||||||
@ -579,10 +635,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: intl
|
name: intl
|
||||||
sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d"
|
sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.18.1"
|
version: "0.19.0"
|
||||||
io:
|
io:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -599,6 +655,30 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.6.7"
|
version: "0.6.7"
|
||||||
|
leak_tracker:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: leak_tracker
|
||||||
|
sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "10.0.0"
|
||||||
|
leak_tracker_flutter_testing:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: leak_tracker_flutter_testing
|
||||||
|
sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.1"
|
||||||
|
leak_tracker_testing:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: leak_tracker_testing
|
||||||
|
sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.1"
|
||||||
linkify:
|
linkify:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -627,18 +707,18 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: matcher
|
name: matcher
|
||||||
sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e"
|
sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.12.16"
|
version: "0.12.16+1"
|
||||||
material_color_utilities:
|
material_color_utilities:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: material_color_utilities
|
name: material_color_utilities
|
||||||
sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41"
|
sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.5.0"
|
version: "0.8.0"
|
||||||
memoize:
|
memoize:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -651,26 +731,26 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: meta
|
name: meta
|
||||||
sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e
|
sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.10.0"
|
version: "1.11.0"
|
||||||
mime:
|
mime:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: mime
|
name: mime
|
||||||
sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e
|
sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.4"
|
version: "1.0.5"
|
||||||
mocktail:
|
mocktail:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: mocktail
|
name: mocktail
|
||||||
sha256: "9503969a7c2c78c7292022c70c0289ed6241df7a9ba720010c0b215af29a5a58"
|
sha256: c4b5007d91ca4f67256e720cb1b6d704e79a510183a12fa551021f652577dce6
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.0"
|
version: "1.0.3"
|
||||||
nested:
|
nested:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -715,10 +795,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: package_info_plus
|
name: package_info_plus
|
||||||
sha256: "6ff267fcd9d48cb61c8df74a82680e8b82e940231bb5f68356672fde0397334a"
|
sha256: "88bc797f44a94814f2213db1c9bd5badebafdfb8290ca9f78d4b9ee2a3db4d79"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.1.0"
|
version: "5.0.1"
|
||||||
package_info_plus_platform_interface:
|
package_info_plus_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -731,34 +811,34 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: path
|
name: path
|
||||||
sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917"
|
sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.8.3"
|
version: "1.9.0"
|
||||||
path_provider:
|
path_provider:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: path_provider
|
name: path_provider
|
||||||
sha256: a1aa8aaa2542a6bc57e381f132af822420216c80d4781f7aa085ca3229208aaa
|
sha256: b27217933eeeba8ff24845c34003b003b2b22151de3c908d0e679e8fe1aa078b
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.1"
|
version: "2.1.2"
|
||||||
path_provider_android:
|
path_provider_android:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: path_provider_android
|
name: path_provider_android
|
||||||
sha256: "6b8b19bd80da4f11ce91b2d1fb931f3006911477cec227cce23d3253d80df3f1"
|
sha256: "477184d672607c0a3bf68fbbf601805f92ef79c82b64b4d6eb318cbca4c48668"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.0"
|
version: "2.2.2"
|
||||||
path_provider_foundation:
|
path_provider_foundation:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: path_provider_foundation
|
name: path_provider_foundation
|
||||||
sha256: "19314d595120f82aca0ba62787d58dde2cc6b5df7d2f0daf72489e38d1b57f2d"
|
sha256: "5a7999be66e000916500be4f15a3633ebceb8302719b47b9cc49ce924125350f"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.1"
|
version: "2.3.2"
|
||||||
path_provider_linux:
|
path_provider_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -771,10 +851,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: path_provider_platform_interface
|
name: path_provider_platform_interface
|
||||||
sha256: "94b1e0dd80970c1ce43d5d4e050a9918fce4f4a775e6142424c30a29a363265c"
|
sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.1"
|
version: "2.1.2"
|
||||||
path_provider_windows:
|
path_provider_windows:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -787,26 +867,26 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: petitparser
|
name: petitparser
|
||||||
sha256: cb3798bef7fc021ac45b308f4b51208a152792445cce0448c9a4ba5879dd8750
|
sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.4.0"
|
version: "6.0.2"
|
||||||
platform:
|
platform:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: platform
|
name: platform
|
||||||
sha256: ae68c7bfcd7383af3629daafb32fb4e8681c7154428da4febcff06200585f102
|
sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.2"
|
version: "3.1.4"
|
||||||
plugin_platform_interface:
|
plugin_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: plugin_platform_interface
|
name: plugin_platform_interface
|
||||||
sha256: da3fdfeccc4d4ff2da8f8c556704c08f912542c5fb3cf2233ed75372384a034d
|
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.6"
|
version: "2.1.8"
|
||||||
pool:
|
pool:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -815,22 +895,30 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.5.1"
|
version: "1.5.1"
|
||||||
|
pretty_dio_logger:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: pretty_dio_logger
|
||||||
|
sha256: "00b80053063935cf9a6190da344c5373b9d0e92da4c944c878ff2fbef0ef6dc2"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.3.1"
|
||||||
process:
|
process:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: process
|
name: process
|
||||||
sha256: "53fd8db9cec1d37b0574e12f07520d582019cb6c44abf5479a01505099a34a09"
|
sha256: "21e54fd2faf1b5bdd5102afd25012184a6793927648ea81eea80552ac9405b32"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.2.4"
|
version: "5.0.2"
|
||||||
provider:
|
provider:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: provider
|
name: provider
|
||||||
sha256: cdbe7530b12ecd9eb455bdaa2fcb8d4dad22e80b8afb4798b41479d5ce26847f
|
sha256: "9a96a0a19b594dbc5bf0f1f27d2bc67d5f95957359b461cd9feb44ed6ae75096"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.0.5"
|
version: "6.1.1"
|
||||||
pub_semver:
|
pub_semver:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -876,10 +964,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: receive_sharing_intent
|
name: receive_sharing_intent
|
||||||
sha256: "912bebb551bce75a14098891fd750305b30d53eba0d61cc70cd9973be9866e8d"
|
sha256: "252e5b5018aebfa93a068bdf08dc58152b3ac5958a22b1027e9ccbbe71912115"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.4.5"
|
version: "1.5.4"
|
||||||
responsive_builder:
|
responsive_builder:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -908,18 +996,18 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: sembast
|
name: sembast
|
||||||
sha256: "85ff944434f7b566fdc388be4f85b23e954736b7d6e51f965f4f419d966c15b1"
|
sha256: "9a9f0c7aca07043fef857b8b365f41592e48832b61462292699b57978e241c11"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.5.0+1"
|
version: "3.6.0"
|
||||||
share_plus:
|
share_plus:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: share_plus
|
name: share_plus
|
||||||
sha256: f74fc3f1cbd99f39760182e176802f693fa0ec9625c045561cfad54681ea93dd
|
sha256: "3ef39599b00059db0990ca2e30fca0a29d8b37aae924d60063f8e0184cf20900"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "7.2.1"
|
version: "7.2.2"
|
||||||
share_plus_platform_interface:
|
share_plus_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -948,42 +1036,42 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: shared_preferences_foundation
|
name: shared_preferences_foundation
|
||||||
sha256: "7bf53a9f2d007329ee6f3df7268fd498f8373602f943c975598bbb34649b62a7"
|
sha256: "7708d83064f38060c7b39db12aefe449cb8cdc031d6062280087bc4cdb988f5c"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.4"
|
version: "2.3.5"
|
||||||
shared_preferences_linux:
|
shared_preferences_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: shared_preferences_linux
|
name: shared_preferences_linux
|
||||||
sha256: c2eb5bf57a2fe9ad6988121609e47d3e07bb3bdca5b6f8444e4cf302428a128a
|
sha256: "9f2cbcf46d4270ea8be39fa156d86379077c8a5228d9dfdb1164ae0bb93f1faa"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.1"
|
version: "2.3.2"
|
||||||
shared_preferences_platform_interface:
|
shared_preferences_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: shared_preferences_platform_interface
|
name: shared_preferences_platform_interface
|
||||||
sha256: d4ec5fc9ebb2f2e056c617112aa75dcf92fc2e4faaf2ae999caa297473f75d8a
|
sha256: "22e2ecac9419b4246d7c22bfbbda589e3acf5c0351137d87dd2939d984d37c3b"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.1"
|
version: "2.3.2"
|
||||||
shared_preferences_web:
|
shared_preferences_web:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: shared_preferences_web
|
name: shared_preferences_web
|
||||||
sha256: d762709c2bbe80626ecc819143013cc820fa49ca5e363620ee20a8b15a3e3daf
|
sha256: "7b15ffb9387ea3e237bb7a66b8a23d2147663d391cafc5c8f37b2e7b4bde5d21"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.1"
|
version: "2.2.2"
|
||||||
shared_preferences_windows:
|
shared_preferences_windows:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: shared_preferences_windows
|
name: shared_preferences_windows
|
||||||
sha256: f763a101313bd3be87edffe0560037500967de9c394a714cd598d945517f694f
|
sha256: "841ad54f3c8381c480d0c9b508b89a34036f512482c407e6df7a9c4aa2ef8f59"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.1"
|
version: "2.3.2"
|
||||||
shelf:
|
shelf:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -1053,22 +1141,30 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.10.0"
|
version: "1.10.0"
|
||||||
|
sprintf:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: sprintf
|
||||||
|
sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "7.0.0"
|
||||||
sqflite:
|
sqflite:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: sqflite
|
name: sqflite
|
||||||
sha256: "591f1602816e9c31377d5f008c2d9ef7b8aca8941c3f89cc5fd9d84da0c38a9a"
|
sha256: a9016f495c927cb90557c909ff26a6d92d9bd54fc42ba92e19d4e79d61e798c6
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.0"
|
version: "2.3.2"
|
||||||
sqflite_common:
|
sqflite_common:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: sqflite_common
|
name: sqflite_common
|
||||||
sha256: "1b92f368f44b0dee2425bb861cfa17b6f6cf3961f762ff6f941d20b33355660a"
|
sha256: "28d8c66baee4968519fb8bd6cdbedad982d6e53359091f0b74544a9f32ec72d5"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.5.0"
|
version: "2.5.3"
|
||||||
stack_trace:
|
stack_trace:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -1120,10 +1216,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: synchronized
|
name: synchronized
|
||||||
sha256: "5fcbd27688af6082f5abd611af56ee575342c30e87541d0245f7ff99faa02c60"
|
sha256: "539ef412b170d65ecdafd780f924e5be3f60032a1128df156adad6c5b373d558"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.0"
|
version: "3.1.0+1"
|
||||||
term_glyph:
|
term_glyph:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -1184,34 +1280,34 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: url_launcher
|
name: url_launcher
|
||||||
sha256: b1c9e98774adf8820c96fbc7ae3601231d324a7d5ebd8babe27b6dfac91357ba
|
sha256: c512655380d241a337521703af62d2c122bf7b77a46ff7dd750092aa9433499c
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.2.1"
|
version: "6.2.4"
|
||||||
url_launcher_android:
|
url_launcher_android:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: url_launcher_android
|
name: url_launcher_android
|
||||||
sha256: "31222ffb0063171b526d3e569079cf1f8b294075ba323443fdc690842bfd4def"
|
sha256: d4ed0711849dd8e33eb2dd69c25db0d0d3fdc37e0a62e629fe32f57a22db2745
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.2.0"
|
version: "6.3.0"
|
||||||
url_launcher_ios:
|
url_launcher_ios:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: url_launcher_ios
|
name: url_launcher_ios
|
||||||
sha256: "4ac97281cf60e2e8c5cc703b2b28528f9b50c8f7cebc71df6bdf0845f647268a"
|
sha256: "75bb6fe3f60070407704282a2d295630cab232991eb52542b18347a8a941df03"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.2.0"
|
version: "6.2.4"
|
||||||
url_launcher_linux:
|
url_launcher_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: url_launcher_linux
|
name: url_launcher_linux
|
||||||
sha256: "9f2d390e096fdbe1e6e6256f97851e51afc2d9c423d3432f1d6a02a8a9a8b9fd"
|
sha256: ab360eb661f8879369acac07b6bb3ff09d9471155357da8443fd5d3cf7363811
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.0"
|
version: "3.1.1"
|
||||||
url_launcher_macos:
|
url_launcher_macos:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -1224,34 +1320,34 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: url_launcher_platform_interface
|
name: url_launcher_platform_interface
|
||||||
sha256: "980e8d9af422f477be6948bdfb68df8433be71f5743a188968b0c1b887807e50"
|
sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.0"
|
version: "2.3.2"
|
||||||
url_launcher_web:
|
url_launcher_web:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: url_launcher_web
|
name: url_launcher_web
|
||||||
sha256: "7fd2f55fe86cea2897b963e864dc01a7eb0719ecc65fcef4c1cc3d686d718bb2"
|
sha256: fff0932192afeedf63cdd50ecbb1bc825d31aed259f02bb8dba0f3b729a5e88b
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.0"
|
version: "2.2.3"
|
||||||
url_launcher_windows:
|
url_launcher_windows:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: url_launcher_windows
|
name: url_launcher_windows
|
||||||
sha256: "7754a1ad30ee896b265f8d14078b0513a4dba28d358eabb9d5f339886f4a1adc"
|
sha256: ecf9725510600aa2bb6d7ddabe16357691b6d2805f66216a97d1b881e21beff7
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.0"
|
version: "3.1.1"
|
||||||
uuid:
|
uuid:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: uuid
|
name: uuid
|
||||||
sha256: "648e103079f7c64a36dc7d39369cabb358d377078a051d6ae2ad3aa539519313"
|
sha256: cd210a09f7c18cbe5a02511718e0334de6559871052c90a90c0cca46a4aa81c8
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.7"
|
version: "4.3.3"
|
||||||
vector_math:
|
vector_math:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -1280,10 +1376,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: vm_service
|
name: vm_service
|
||||||
sha256: c538be99af830f478718b51630ec1b6bee5e74e52c8a802d328d9e71d35d2583
|
sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "11.10.0"
|
version: "13.0.0"
|
||||||
wakelock:
|
wakelock:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -1336,26 +1432,26 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: web
|
name: web
|
||||||
sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152
|
sha256: "4188706108906f002b3a293509234588823c8c979dc83304e229ff400c996b05"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.3.0"
|
version: "0.4.2"
|
||||||
web_socket_channel:
|
web_socket_channel:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: web_socket_channel
|
name: web_socket_channel
|
||||||
sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b
|
sha256: "939ab60734a4f8fa95feacb55804fa278de28bdeef38e616dc08e44a84adea23"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.0"
|
version: "2.4.3"
|
||||||
webdriver:
|
webdriver:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: webdriver
|
name: webdriver
|
||||||
sha256: "3c923e918918feeb90c4c9fdf1fe39220fa4c0e8e2c0fffaded174498ef86c49"
|
sha256: "003d7da9519e1e5f329422b36c4dcdf18d7d2978d1ba099ea4e45ba490ed845e"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.2"
|
version: "3.0.3"
|
||||||
webkit_inspection_protocol:
|
webkit_inspection_protocol:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -1368,42 +1464,42 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: webview_flutter
|
name: webview_flutter
|
||||||
sha256: c1ab9b81090705c6069197d9fdc1625e587b52b8d70cdde2339d177ad0dbb98e
|
sha256: "25e1b6e839e8cbfbd708abc6f85ed09d1727e24e08e08c6b8590d7c65c9a8932"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.4.1"
|
version: "4.7.0"
|
||||||
webview_flutter_android:
|
webview_flutter_android:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: webview_flutter_android
|
name: webview_flutter_android
|
||||||
sha256: b0cd33dd7d3dd8e5f664e11a19e17ba12c352647269921a3b568406b001f1dff
|
sha256: "3e5f4e9d818086b0d01a66fb1ff9cc72ab0cc58c71980e3d3661c5685ea0efb0"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.12.0"
|
version: "3.15.0"
|
||||||
webview_flutter_platform_interface:
|
webview_flutter_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: webview_flutter_platform_interface
|
name: webview_flutter_platform_interface
|
||||||
sha256: "6d9213c65f1060116757a7c473247c60f3f7f332cac33dc417c9e362a9a13e4f"
|
sha256: d937581d6e558908d7ae3dc1989c4f87b786891ab47bb9df7de548a151779d8d
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.6.0"
|
version: "2.10.0"
|
||||||
webview_flutter_wkwebview:
|
webview_flutter_wkwebview:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: webview_flutter_wkwebview
|
name: webview_flutter_wkwebview
|
||||||
sha256: "30b9af6bdd457b44c08748b9190d23208b5165357cc2eb57914fee1366c42974"
|
sha256: "9bf168bccdf179ce90450b5f37e36fe263f591c9338828d6bf09b6f8d0f57f86"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.9.1"
|
version: "3.12.0"
|
||||||
win32:
|
win32:
|
||||||
dependency: "direct overridden"
|
dependency: "direct overridden"
|
||||||
description:
|
description:
|
||||||
name: win32
|
name: win32
|
||||||
sha256: "350a11abd2d1d97e0cc7a28a81b781c08002aa2864d9e3f192ca0ffa18b06ed3"
|
sha256: "464f5674532865248444b4c3daca12bd9bf2d7c47f759ce2617986e7229494a8"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.0.9"
|
version: "5.2.0"
|
||||||
win32_registry:
|
win32_registry:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -1416,26 +1512,26 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: workmanager
|
name: workmanager
|
||||||
sha256: e0be7e35d644643f164ee45d2ce14414f0e0fdde19456aa66065f35a0b1d2ea1
|
sha256: ed13530cccd28c5c9959ad42d657cd0666274ca74c56dea0ca183ddd527d3a00
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.5.1"
|
version: "0.5.2"
|
||||||
xdg_directories:
|
xdg_directories:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: xdg_directories
|
name: xdg_directories
|
||||||
sha256: "589ada45ba9e39405c198fe34eb0f607cddb2108527e658136120892beac46d2"
|
sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.3"
|
version: "1.0.4"
|
||||||
xml:
|
xml:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: xml
|
name: xml
|
||||||
sha256: "5bc72e1e45e941d825fd7468b9b4cc3b9327942649aeb6fc5cdbf135f0a86e84"
|
sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.3.0"
|
version: "6.5.0"
|
||||||
yaml:
|
yaml:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -1445,5 +1541,5 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.2"
|
version: "3.1.2"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.2.0-194.0.dev <4.0.0"
|
dart: ">=3.3.0-279.1.beta <4.0.0"
|
||||||
flutter: ">=3.16.2"
|
flutter: ">=3.19.0"
|
||||||
|
15
pubspec.yaml
15
pubspec.yaml
@ -1,11 +1,11 @@
|
|||||||
name: hacki
|
name: hacki
|
||||||
description: A Hacker News reader.
|
description: A Hacker News reader.
|
||||||
version: 2.5.0+134
|
version: 2.7.0+138
|
||||||
publish_to: none
|
publish_to: none
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=3.0.0 <4.0.0"
|
sdk: ">=3.0.0 <4.0.0"
|
||||||
flutter: "3.16.2"
|
flutter: "3.19.0"
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
adaptive_theme: ^3.2.0
|
adaptive_theme: ^3.2.0
|
||||||
@ -32,7 +32,7 @@ dependencies:
|
|||||||
flutter_email_sender: ^6.0.1
|
flutter_email_sender: ^6.0.1
|
||||||
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: ^5.8.0
|
flutter_inappwebview: ^6.0.0
|
||||||
flutter_local_notifications: ^16.1.0
|
flutter_local_notifications: ^16.1.0
|
||||||
flutter_material_color_picker: ^1.2.0
|
flutter_material_color_picker: ^1.2.0
|
||||||
flutter_secure_storage: ^9.0.0
|
flutter_secure_storage: ^9.0.0
|
||||||
@ -40,7 +40,7 @@ dependencies:
|
|||||||
flutter_slidable: ^3.0.0
|
flutter_slidable: ^3.0.0
|
||||||
font_awesome_flutter: ^10.3.0
|
font_awesome_flutter: ^10.3.0
|
||||||
get_it: ^7.2.0
|
get_it: ^7.2.0
|
||||||
go_router: ^12.1.1
|
go_router: ^13.2.0
|
||||||
hive: ^2.2.3
|
hive: ^2.2.3
|
||||||
html: ^0.15.1
|
html: ^0.15.1
|
||||||
html_unescape: ^2.0.0
|
html_unescape: ^2.0.0
|
||||||
@ -48,22 +48,23 @@ dependencies:
|
|||||||
hydrated_bloc: ^9.1.0
|
hydrated_bloc: ^9.1.0
|
||||||
in_app_review:
|
in_app_review:
|
||||||
path: components/in_app_review
|
path: components/in_app_review
|
||||||
intl: ^0.18.0
|
intl: ^0.19.0
|
||||||
linkify: ^5.0.0
|
linkify: ^5.0.0
|
||||||
logger: ^2.0.1
|
logger: ^2.0.1
|
||||||
memoize: ^3.0.0
|
memoize: ^3.0.0
|
||||||
package_info_plus: ^4.0.0
|
package_info_plus: ^5.0.1
|
||||||
path: ^1.8.2
|
path: ^1.8.2
|
||||||
path_provider: ^2.0.12
|
path_provider: ^2.0.12
|
||||||
path_provider_android: ^2.0.22
|
path_provider_android: ^2.0.22
|
||||||
path_provider_foundation: ^2.1.1
|
path_provider_foundation: ^2.1.1
|
||||||
|
pretty_dio_logger: ^1.3.1
|
||||||
pull_to_refresh:
|
pull_to_refresh:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/livinglist/flutter_pulltorefresh
|
url: https://github.com/livinglist/flutter_pulltorefresh
|
||||||
ref: master
|
ref: master
|
||||||
qr_code_scanner: ^1.0.1
|
qr_code_scanner: ^1.0.1
|
||||||
qr_flutter: ^4.1.0
|
qr_flutter: ^4.1.0
|
||||||
receive_sharing_intent: ^1.4.5
|
receive_sharing_intent: 1.5.4
|
||||||
responsive_builder: ^0.7.0
|
responsive_builder: ^0.7.0
|
||||||
rxdart: ^0.27.7
|
rxdart: ^0.27.7
|
||||||
scrollable_positioned_list: ^0.3.5
|
scrollable_positioned_list: ^0.3.5
|
||||||
|
Submodule submodules/flutter updated: 9e1c857886...bae5e49bc2
Reference in New Issue
Block a user