mirror of
https://github.com/Livinglist/Hacki.git
synced 2025-08-06 18:24:42 +08:00
Compare commits
25 Commits
Author | SHA1 | Date | |
---|---|---|---|
03c01a0b78 | |||
f823fdf241 | |||
fe87ddd8ff | |||
613ba12b05 | |||
8d7f66ecbc | |||
461aae253b | |||
a1b491cf0d | |||
edf0c82040 | |||
946a3c5a9a | |||
d8bc60c071 | |||
48477cd5c8 | |||
38df6293fe | |||
a5fe9e45fc | |||
9de5baa77a | |||
2daccd64e8 | |||
d0c68f9419 | |||
5f1dbfc510 | |||
90eee37c17 | |||
5630e61a74 | |||
eaad4b01dd | |||
3ab172f3d3 | |||
5450eba64b | |||
e2d6bb44d0 | |||
ffbd3a2449 | |||
2405a6d30c |
3
.github/workflows/commit_check.yml
vendored
3
.github/workflows/commit_check.yml
vendored
@ -4,11 +4,13 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- "**"
|
- "**"
|
||||||
|
- '!master'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
releases:
|
releases:
|
||||||
name: Check commit
|
name: Check commit
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 30
|
||||||
env:
|
env:
|
||||||
FLUTTER_VERSION: "3.3.10"
|
FLUTTER_VERSION: "3.3.10"
|
||||||
steps:
|
steps:
|
||||||
@ -20,3 +22,4 @@ jobs:
|
|||||||
- run: flutter pub get
|
- run: flutter pub get
|
||||||
- run: flutter format --set-exit-if-changed .
|
- run: flutter format --set-exit-if-changed .
|
||||||
- run: flutter analyze
|
- run: flutter analyze
|
||||||
|
- run: flutter test
|
7
.github/workflows/publish_ios.yml
vendored
7
.github/workflows/publish_ios.yml
vendored
@ -6,13 +6,12 @@ on:
|
|||||||
# Run the workflow whenever a new tag named 'v*' is pushed
|
# Run the workflow whenever a new tag named 'v*' is pushed
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- "!*"
|
- master
|
||||||
tags:
|
|
||||||
- "v*"
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build_and_publish:
|
build_and_publish:
|
||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
|
timeout-minutes: 30
|
||||||
|
|
||||||
env:
|
env:
|
||||||
# Point the `ruby/setup-ruby` action at this Gemfile, so it
|
# Point the `ruby/setup-ruby` action at this Gemfile, so it
|
||||||
@ -51,4 +50,4 @@ jobs:
|
|||||||
APP_STORE_CONNECT_API_KEY_KEY: ${{ secrets.APP_STORE_CONNECT_API_KEY_KEY }}
|
APP_STORE_CONNECT_API_KEY_KEY: ${{ secrets.APP_STORE_CONNECT_API_KEY_KEY }}
|
||||||
MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
|
MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
|
||||||
SSH_AUTH_SOCK: /tmp/ssh_agent.sock
|
SSH_AUTH_SOCK: /tmp/ssh_agent.sock
|
||||||
run: cd ios && bundle exec fastlane beta "build_name:${{ github.ref_name }}"
|
run: cd ios && bundle exec fastlane beta
|
||||||
|
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
[submodule "flutter"]
|
||||||
|
path = submodules/flutter
|
||||||
|
url = https://github.com/flutter/flutter
|
@ -7,3 +7,7 @@ linter:
|
|||||||
omit_local_variable_types: false
|
omit_local_variable_types: false
|
||||||
one_member_abstracts: false
|
one_member_abstracts: false
|
||||||
always_specify_types: true
|
always_specify_types: true
|
||||||
|
|
||||||
|
analyzer:
|
||||||
|
exclude:
|
||||||
|
- "submodules/**"
|
||||||
|
2
fastlane/metadata/android/en-US/changelogs/77.txt
Normal file
2
fastlane/metadata/android/en-US/changelogs/77.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
- Fixed app icon.
|
||||||
|
- Added font size setting to comments screen.
|
1
fastlane/metadata/android/en-US/changelogs/78.txt
Normal file
1
fastlane/metadata/android/en-US/changelogs/78.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
- Fixed time machine.
|
1
fastlane/metadata/android/en-US/changelogs/79.txt
Normal file
1
fastlane/metadata/android/en-US/changelogs/79.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
- Fixed time machine.
|
@ -17,20 +17,20 @@ GEM
|
|||||||
artifactory (3.0.15)
|
artifactory (3.0.15)
|
||||||
atomos (0.1.3)
|
atomos (0.1.3)
|
||||||
aws-eventstream (1.2.0)
|
aws-eventstream (1.2.0)
|
||||||
aws-partitions (1.636.0)
|
aws-partitions (1.680.0)
|
||||||
aws-sdk-core (3.154.0)
|
aws-sdk-core (3.168.4)
|
||||||
aws-eventstream (~> 1, >= 1.0.2)
|
aws-eventstream (~> 1, >= 1.0.2)
|
||||||
aws-partitions (~> 1, >= 1.525.0)
|
aws-partitions (~> 1, >= 1.651.0)
|
||||||
aws-sigv4 (~> 1.1)
|
aws-sigv4 (~> 1.5)
|
||||||
jmespath (~> 1, >= 1.6.1)
|
jmespath (~> 1, >= 1.6.1)
|
||||||
aws-sdk-kms (1.58.0)
|
aws-sdk-kms (1.61.0)
|
||||||
aws-sdk-core (~> 3, >= 3.127.0)
|
aws-sdk-core (~> 3, >= 3.165.0)
|
||||||
aws-sigv4 (~> 1.1)
|
aws-sigv4 (~> 1.1)
|
||||||
aws-sdk-s3 (1.114.0)
|
aws-sdk-s3 (1.117.2)
|
||||||
aws-sdk-core (~> 3, >= 3.127.0)
|
aws-sdk-core (~> 3, >= 3.165.0)
|
||||||
aws-sdk-kms (~> 1)
|
aws-sdk-kms (~> 1)
|
||||||
aws-sigv4 (~> 1.4)
|
aws-sigv4 (~> 1.4)
|
||||||
aws-sigv4 (1.5.1)
|
aws-sigv4 (1.5.2)
|
||||||
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)
|
||||||
@ -86,7 +86,7 @@ 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.92.5)
|
excon (0.95.0)
|
||||||
faraday (1.10.2)
|
faraday (1.10.2)
|
||||||
faraday-em_http (~> 1.0)
|
faraday-em_http (~> 1.0)
|
||||||
faraday-em_synchrony (~> 1.0)
|
faraday-em_synchrony (~> 1.0)
|
||||||
@ -116,7 +116,7 @@ GEM
|
|||||||
faraday_middleware (1.2.0)
|
faraday_middleware (1.2.0)
|
||||||
faraday (~> 1.0)
|
faraday (~> 1.0)
|
||||||
fastimage (2.2.6)
|
fastimage (2.2.6)
|
||||||
fastlane (2.210.1)
|
fastlane (2.211.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)
|
||||||
@ -159,9 +159,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.27.0)
|
google-apis-androidpublisher_v3 (0.32.0)
|
||||||
google-apis-core (>= 0.7.2, < 2.a)
|
google-apis-core (>= 0.9.1, < 2.a)
|
||||||
google-apis-core (0.9.0)
|
google-apis-core (0.9.2)
|
||||||
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)
|
||||||
@ -170,27 +170,27 @@ GEM
|
|||||||
retriable (>= 2.0, < 4.a)
|
retriable (>= 2.0, < 4.a)
|
||||||
rexml
|
rexml
|
||||||
webrick
|
webrick
|
||||||
google-apis-iamcredentials_v1 (0.14.0)
|
google-apis-iamcredentials_v1 (0.16.0)
|
||||||
google-apis-core (>= 0.7.2, < 2.a)
|
google-apis-core (>= 0.9.1, < 2.a)
|
||||||
google-apis-playcustomapp_v1 (0.10.0)
|
google-apis-playcustomapp_v1 (0.12.0)
|
||||||
google-apis-core (>= 0.7, < 2.a)
|
google-apis-core (>= 0.9.1, < 2.a)
|
||||||
google-apis-storage_v1 (0.17.0)
|
google-apis-storage_v1 (0.19.0)
|
||||||
google-apis-core (>= 0.7, < 2.a)
|
google-apis-core (>= 0.9.0, < 2.a)
|
||||||
google-cloud-core (1.6.0)
|
google-cloud-core (1.6.0)
|
||||||
google-cloud-env (~> 1.0)
|
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.0)
|
||||||
google-cloud-storage (1.42.0)
|
google-cloud-storage (1.44.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.17.0)
|
google-apis-storage_v1 (~> 0.19.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.2.0)
|
googleauth (1.3.0)
|
||||||
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)
|
memoist (~> 0.16)
|
||||||
@ -203,11 +203,11 @@ GEM
|
|||||||
httpclient (2.8.3)
|
httpclient (2.8.3)
|
||||||
i18n (1.12.0)
|
i18n (1.12.0)
|
||||||
concurrent-ruby (~> 1.0)
|
concurrent-ruby (~> 1.0)
|
||||||
jmespath (1.6.1)
|
jmespath (1.6.2)
|
||||||
json (2.6.2)
|
json (2.6.3)
|
||||||
jwt (2.5.0)
|
jwt (2.5.0)
|
||||||
memoist (0.16.2)
|
memoist (0.16.2)
|
||||||
mini_magick (4.11.0)
|
mini_magick (4.12.0)
|
||||||
mini_mime (1.1.2)
|
mini_mime (1.1.2)
|
||||||
minitest (5.16.3)
|
minitest (5.16.3)
|
||||||
molinillo (0.8.0)
|
molinillo (0.8.0)
|
||||||
|
@ -1,6 +1,3 @@
|
|||||||
# Uncomment this line to define a global platform for your project
|
|
||||||
# platform :ios, '11.0'
|
|
||||||
|
|
||||||
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
||||||
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
||||||
|
|
||||||
|
@ -22,6 +22,8 @@ PODS:
|
|||||||
- integration_test (0.0.1):
|
- integration_test (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- OrderedSet (5.0.0)
|
- OrderedSet (5.0.0)
|
||||||
|
- package_info_plus (0.4.5):
|
||||||
|
- Flutter
|
||||||
- path_provider_ios (0.0.1):
|
- path_provider_ios (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- ReachabilitySwift (5.0.0)
|
- ReachabilitySwift (5.0.0)
|
||||||
@ -53,6 +55,7 @@ DEPENDENCIES:
|
|||||||
- 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`)
|
||||||
- integration_test (from `.symlinks/plugins/integration_test/ios`)
|
- integration_test (from `.symlinks/plugins/integration_test/ios`)
|
||||||
|
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
||||||
- path_provider_ios (from `.symlinks/plugins/path_provider_ios/ios`)
|
- path_provider_ios (from `.symlinks/plugins/path_provider_ios/ios`)
|
||||||
- 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`)
|
||||||
@ -85,6 +88,8 @@ EXTERNAL SOURCES:
|
|||||||
:path: ".symlinks/plugins/flutter_siri_suggestions/ios"
|
:path: ".symlinks/plugins/flutter_siri_suggestions/ios"
|
||||||
integration_test:
|
integration_test:
|
||||||
:path: ".symlinks/plugins/integration_test/ios"
|
:path: ".symlinks/plugins/integration_test/ios"
|
||||||
|
package_info_plus:
|
||||||
|
:path: ".symlinks/plugins/package_info_plus/ios"
|
||||||
path_provider_ios:
|
path_provider_ios:
|
||||||
:path: ".symlinks/plugins/path_provider_ios/ios"
|
:path: ".symlinks/plugins/path_provider_ios/ios"
|
||||||
receive_sharing_intent:
|
receive_sharing_intent:
|
||||||
@ -116,6 +121,7 @@ SPEC CHECKSUMS:
|
|||||||
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
|
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
|
||||||
integration_test: a1e7d09bd98eca2fc37aefd79d4f41ad37bdbbe5
|
integration_test: a1e7d09bd98eca2fc37aefd79d4f41ad37bdbbe5
|
||||||
OrderedSet: aaeb196f7fef5a9edf55d89760da9176ad40b93c
|
OrderedSet: aaeb196f7fef5a9edf55d89760da9176ad40b93c
|
||||||
|
package_info_plus: 6c92f08e1f853dc01228d6f553146438dafcd14e
|
||||||
path_provider_ios: 14f3d2fd28c4fdb42f44e0f751d12861c43cee02
|
path_provider_ios: 14f3d2fd28c4fdb42f44e0f751d12861c43cee02
|
||||||
ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
|
ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
|
||||||
receive_sharing_intent: c0d87310754e74c0f9542947e7cbdf3a0335a3b1
|
receive_sharing_intent: c0d87310754e74c0f9542947e7cbdf3a0335a3b1
|
||||||
|
@ -56,7 +56,7 @@
|
|||||||
};
|
};
|
||||||
E51D52B8283B464E00FC8DD8 /* Embed App Extensions */ = {
|
E51D52B8283B464E00FC8DD8 /* Embed App Extensions */ = {
|
||||||
isa = PBXCopyFilesBuildPhase;
|
isa = PBXCopyFilesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 8;
|
||||||
dstPath = "";
|
dstPath = "";
|
||||||
dstSubfolderSpec = 13;
|
dstSubfolderSpec = 13;
|
||||||
files = (
|
files = (
|
||||||
@ -64,7 +64,7 @@
|
|||||||
E530B1B4283B54DA004E8EB6 /* Action Extension.appex in Embed App Extensions */,
|
E530B1B4283B54DA004E8EB6 /* Action Extension.appex in Embed App Extensions */,
|
||||||
);
|
);
|
||||||
name = "Embed App Extensions";
|
name = "Embed App Extensions";
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 1;
|
||||||
};
|
};
|
||||||
/* End PBXCopyFilesBuildPhase section */
|
/* End PBXCopyFilesBuildPhase section */
|
||||||
|
|
||||||
@ -569,17 +569,19 @@
|
|||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
CODE_SIGN_STYLE = Manual;
|
CODE_SIGN_STYLE = Manual;
|
||||||
CURRENT_PROJECT_VERSION = 4;
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
DEVELOPMENT_TEAM = "";
|
DEVELOPMENT_TEAM = "";
|
||||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = QMWX3X2NF7;
|
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = QMWX3X2NF7;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
|
INFOPLIST_KEY_CFBundleDisplayName = Hacki;
|
||||||
|
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.news";
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 0.2.33;
|
MARKETING_VERSION = 0.3.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.jiaqi.hacki;
|
PRODUCT_BUNDLE_IDENTIFIER = com.jiaqi.hacki;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
@ -709,17 +711,19 @@
|
|||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
CODE_SIGN_STYLE = Manual;
|
CODE_SIGN_STYLE = Manual;
|
||||||
CURRENT_PROJECT_VERSION = 4;
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
DEVELOPMENT_TEAM = "";
|
DEVELOPMENT_TEAM = "";
|
||||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = QMWX3X2NF7;
|
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = QMWX3X2NF7;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
|
INFOPLIST_KEY_CFBundleDisplayName = Hacki;
|
||||||
|
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.news";
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 0.2.33;
|
MARKETING_VERSION = 0.3.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.jiaqi.hacki;
|
PRODUCT_BUNDLE_IDENTIFIER = com.jiaqi.hacki;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
@ -743,17 +747,19 @@
|
|||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
|
||||||
CODE_SIGN_STYLE = Manual;
|
CODE_SIGN_STYLE = Manual;
|
||||||
CURRENT_PROJECT_VERSION = 4;
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
DEVELOPMENT_TEAM = "";
|
DEVELOPMENT_TEAM = "";
|
||||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = QMWX3X2NF7;
|
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = QMWX3X2NF7;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
|
INFOPLIST_KEY_CFBundleDisplayName = Hacki;
|
||||||
|
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.news";
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 0.2.33;
|
MARKETING_VERSION = 0.3.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.jiaqi.hacki;
|
PRODUCT_BUNDLE_IDENTIFIER = com.jiaqi.hacki;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
@ -778,7 +784,7 @@
|
|||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
CODE_SIGN_STYLE = Manual;
|
CODE_SIGN_STYLE = Manual;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
DEVELOPMENT_TEAM = "";
|
DEVELOPMENT_TEAM = "";
|
||||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = QMWX3X2NF7;
|
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = QMWX3X2NF7;
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||||
@ -821,7 +827,7 @@
|
|||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
|
||||||
CODE_SIGN_STYLE = Manual;
|
CODE_SIGN_STYLE = Manual;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
DEVELOPMENT_TEAM = "";
|
DEVELOPMENT_TEAM = "";
|
||||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = QMWX3X2NF7;
|
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = QMWX3X2NF7;
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||||
@ -861,7 +867,7 @@
|
|||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
CODE_SIGN_STYLE = Manual;
|
CODE_SIGN_STYLE = Manual;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
DEVELOPMENT_TEAM = "";
|
DEVELOPMENT_TEAM = "";
|
||||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = QMWX3X2NF7;
|
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = QMWX3X2NF7;
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||||
@ -903,7 +909,7 @@
|
|||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
CODE_SIGN_STYLE = Manual;
|
CODE_SIGN_STYLE = Manual;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
DEVELOPMENT_TEAM = "";
|
DEVELOPMENT_TEAM = "";
|
||||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = QMWX3X2NF7;
|
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = QMWX3X2NF7;
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||||
@ -948,7 +954,7 @@
|
|||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
|
||||||
CODE_SIGN_STYLE = Manual;
|
CODE_SIGN_STYLE = Manual;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
DEVELOPMENT_TEAM = "";
|
DEVELOPMENT_TEAM = "";
|
||||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = QMWX3X2NF7;
|
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = QMWX3X2NF7;
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||||
@ -990,7 +996,7 @@
|
|||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
CODE_SIGN_STYLE = Manual;
|
CODE_SIGN_STYLE = Manual;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
DEVELOPMENT_TEAM = "";
|
DEVELOPMENT_TEAM = "";
|
||||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = QMWX3X2NF7;
|
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = QMWX3X2NF7;
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||||
|
@ -23,7 +23,7 @@
|
|||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>$(MARKETING_VERSION)</string>
|
<string>$(FLUTTER_BUILD_NAME)</string>
|
||||||
<key>CFBundleSignature</key>
|
<key>CFBundleSignature</key>
|
||||||
<string>????</string>
|
<string>????</string>
|
||||||
<key>CFBundleURLTypes</key>
|
<key>CFBundleURLTypes</key>
|
||||||
@ -38,7 +38,7 @@
|
|||||||
</dict>
|
</dict>
|
||||||
</array>
|
</array>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
||||||
<key>LSApplicationQueriesSchemes</key>
|
<key>LSApplicationQueriesSchemes</key>
|
||||||
<array>
|
<array>
|
||||||
<string>https</string>
|
<string>https</string>
|
||||||
@ -72,5 +72,7 @@
|
|||||||
<array>
|
<array>
|
||||||
<string>applinks:example.com</string>
|
<string>applinks:example.com</string>
|
||||||
</array>
|
</array>
|
||||||
|
<key>ITSAppUsesNonExemptEncryption</key>
|
||||||
|
<false/>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
@ -31,10 +31,6 @@ platform :ios do
|
|||||||
|
|
||||||
is_example_repo = ENV['CI'] && ENV['GITHUB_REPOSITORY'] == 'jorgenpt/flutter_github_example'
|
is_example_repo = ENV['CI'] && ENV['GITHUB_REPOSITORY'] == 'jorgenpt/flutter_github_example'
|
||||||
|
|
||||||
if !is_example_repo && APP_IDENTIFIER == 'no.tjer.HelloWorld' then
|
|
||||||
UI.user_error! "You need to update your Fastfile to use your own `APP_IDENTIFIER`"
|
|
||||||
end
|
|
||||||
|
|
||||||
# Download code signing certificates using `match` (and the `MATCH_PASSWORD` secret)
|
# Download code signing certificates using `match` (and the `MATCH_PASSWORD` secret)
|
||||||
sync_code_signing(
|
sync_code_signing(
|
||||||
type: "appstore",
|
type: "appstore",
|
||||||
@ -42,15 +38,6 @@ platform :ios do
|
|||||||
readonly: true
|
readonly: true
|
||||||
)
|
)
|
||||||
|
|
||||||
if !is_example_repo then
|
|
||||||
if APPSTORECONNECT_ISSUER_ID == '69a6de83-feb7-47e3-e053-5b8c7c11a4d1' then
|
|
||||||
UI.user_error! "You need to update your Fastfile to use your own `APPSTORECONNECT_ISSUER_ID`"
|
|
||||||
end
|
|
||||||
if APPSTORECONNECT_KEY_ID == 'YRQDJRKMR9' then
|
|
||||||
UI.user_error! "You need to update your Fastfile to use your own `APPSTORECONNECT_KEY_ID`"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# We expose the key data using `APP_STORE_CONNECT_API_KEY_KEY` secret on GH
|
# We expose the key data using `APP_STORE_CONNECT_API_KEY_KEY` secret on GH
|
||||||
app_store_connect_api_key(
|
app_store_connect_api_key(
|
||||||
key_id: APPSTORECONNECT_KEY_ID,
|
key_id: APPSTORECONNECT_KEY_ID,
|
||||||
@ -59,21 +46,18 @@ platform :ios do
|
|||||||
latest_testflight_build_number
|
latest_testflight_build_number
|
||||||
# Figure out the build number (and optionally build name)
|
# Figure out the build number (and optionally build name)
|
||||||
new_build_number = ( + 1)
|
new_build_number = ( + 1)
|
||||||
extra_config_args = []
|
|
||||||
if options.key?(:build_name) then
|
|
||||||
extra_config_args = ["--build-name", options[:build_name].delete_prefix('v')]
|
|
||||||
end
|
|
||||||
|
|
||||||
# Prep the xcodeproject from Flutter without building (`--config-only`)
|
# Prep the xcodeproject from Flutter without building (`--config-only`)
|
||||||
sh(
|
sh(
|
||||||
"flutter", "build", "ios", "--config-only",
|
"flutter", "build", "ios", "--config-only",
|
||||||
"--release", "--no-pub", "--no-codesign",
|
"--release", "--no-pub", "--no-codesign",
|
||||||
"--build-number", new_build_number.to_s,
|
"--build-number", new_build_number.to_s
|
||||||
*extra_config_args
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
version = get_version_number(xcodeproj: "Runner.xcodeproj", target: 'Runner')
|
||||||
|
|
||||||
increment_version_number(
|
increment_version_number(
|
||||||
version_number: options[:build_name].delete_prefix('v').delete_suffix('-rc')
|
version_number: version
|
||||||
)
|
)
|
||||||
|
|
||||||
increment_build_number({
|
increment_build_number({
|
||||||
|
@ -20,7 +20,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
|||||||
storiesRepository ?? locator.get<StoriesRepository>(),
|
storiesRepository ?? locator.get<StoriesRepository>(),
|
||||||
_sembastRepository =
|
_sembastRepository =
|
||||||
sembastRepository ?? locator.get<SembastRepository>(),
|
sembastRepository ?? locator.get<SembastRepository>(),
|
||||||
super(AuthState.init()) {
|
super(const AuthState.init()) {
|
||||||
on<AuthInitialize>(onInitialize);
|
on<AuthInitialize>(onInitialize);
|
||||||
on<AuthLogin>(onLogin);
|
on<AuthLogin>(onLogin);
|
||||||
on<AuthLogout>(onLogout);
|
on<AuthLogout>(onLogout);
|
||||||
@ -101,7 +101,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
|||||||
Future<void> onLogout(AuthLogout event, Emitter<AuthState> emit) async {
|
Future<void> onLogout(AuthLogout event, Emitter<AuthState> emit) async {
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
user: User.empty(),
|
user: const User.empty(),
|
||||||
isLoggedIn: false,
|
isLoggedIn: false,
|
||||||
agreedToEULA: false,
|
agreedToEULA: false,
|
||||||
),
|
),
|
||||||
|
@ -14,8 +14,8 @@ class AuthState extends Equatable {
|
|||||||
required this.agreedToEULA,
|
required this.agreedToEULA,
|
||||||
});
|
});
|
||||||
|
|
||||||
AuthState.init()
|
const AuthState.init()
|
||||||
: user = User.empty(),
|
: user = const User.empty(),
|
||||||
isLoggedIn = false,
|
isLoggedIn = false,
|
||||||
status = AuthStatus.loaded,
|
status = AuthStatus.loaded,
|
||||||
agreedToEULA = false;
|
agreedToEULA = false;
|
||||||
|
@ -48,3 +48,8 @@ abstract class Constants {
|
|||||||
'(ㆆ_ㆆ)',
|
'(ㆆ_ㆆ)',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
abstract class RegExpConstants {
|
||||||
|
static const String linkSuffix = r'(\)|])(.)*$';
|
||||||
|
static const String number = '[0-9]+';
|
||||||
|
}
|
||||||
|
@ -3,6 +3,7 @@ import 'dart:async';
|
|||||||
import 'package:bloc/bloc.dart';
|
import 'package:bloc/bloc.dart';
|
||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:hacki/config/locator.dart';
|
import 'package:hacki/config/locator.dart';
|
||||||
|
import 'package:hacki/cubits/cubits.dart';
|
||||||
import 'package:hacki/services/services.dart';
|
import 'package:hacki/services/services.dart';
|
||||||
|
|
||||||
part 'collapse_state.dart';
|
part 'collapse_state.dart';
|
||||||
@ -10,13 +11,16 @@ part 'collapse_state.dart';
|
|||||||
class CollapseCubit extends Cubit<CollapseState> {
|
class CollapseCubit extends Cubit<CollapseState> {
|
||||||
CollapseCubit({
|
CollapseCubit({
|
||||||
required int commentId,
|
required int commentId,
|
||||||
|
required CommentsCubit? commentsCubit,
|
||||||
CollapseCache? collapseCache,
|
CollapseCache? collapseCache,
|
||||||
}) : _commentId = commentId,
|
}) : _commentId = commentId,
|
||||||
_collapseCache = collapseCache ?? locator.get<CollapseCache>(),
|
_collapseCache = collapseCache ?? locator.get<CollapseCache>(),
|
||||||
|
_commentsCubit = commentsCubit,
|
||||||
super(const CollapseState.init());
|
super(const CollapseState.init());
|
||||||
|
|
||||||
final int _commentId;
|
final int _commentId;
|
||||||
final CollapseCache _collapseCache;
|
final CollapseCache _collapseCache;
|
||||||
|
final CommentsCubit? _commentsCubit;
|
||||||
late final StreamSubscription<Map<int, Set<int>>> _streamSubscription;
|
late final StreamSubscription<Map<int, Set<int>>> _streamSubscription;
|
||||||
|
|
||||||
void init() {
|
void init() {
|
||||||
@ -43,12 +47,21 @@ class CollapseCubit extends Cubit<CollapseState> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
final int count = _collapseCache.collapse(_commentId);
|
if (_commentsCubit == null) return;
|
||||||
|
|
||||||
|
final Set<int> collapsedCommentIds = _collapseCache.collapse(_commentId);
|
||||||
|
final int lastCommentId = _commentsCubit!.state.comments.last.id;
|
||||||
|
final bool shouldLoadMore = _commentId == lastCommentId ||
|
||||||
|
collapsedCommentIds.contains(lastCommentId);
|
||||||
|
|
||||||
|
if (shouldLoadMore) {
|
||||||
|
_commentsCubit!.loadMore();
|
||||||
|
}
|
||||||
|
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
collapsed: true,
|
collapsed: true,
|
||||||
collapsedCount: state.collapsed ? 0 : count,
|
collapsedCount: state.collapsed ? 0 : collapsedCommentIds.length,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -213,6 +213,8 @@ class CommentsCubit extends Cubit<CommentsState> {
|
|||||||
|
|
||||||
/// [comment] is only used for lazy fetching.
|
/// [comment] is only used for lazy fetching.
|
||||||
void loadMore({Comment? comment}) {
|
void loadMore({Comment? comment}) {
|
||||||
|
if (comment == null && state.status == CommentsStatus.loading) return;
|
||||||
|
|
||||||
switch (state.fetchMode) {
|
switch (state.fetchMode) {
|
||||||
case FetchMode.lazy:
|
case FetchMode.lazy:
|
||||||
if (comment == null) return;
|
if (comment == null) return;
|
||||||
|
@ -10,7 +10,7 @@ class UserCubit extends Cubit<UserState> {
|
|||||||
UserCubit({StoriesRepository? storiesRepository})
|
UserCubit({StoriesRepository? storiesRepository})
|
||||||
: _storiesRepository =
|
: _storiesRepository =
|
||||||
storiesRepository ?? locator.get<StoriesRepository>(),
|
storiesRepository ?? locator.get<StoriesRepository>(),
|
||||||
super(UserState.init());
|
super(const UserState.init());
|
||||||
|
|
||||||
final StoriesRepository _storiesRepository;
|
final StoriesRepository _storiesRepository;
|
||||||
|
|
||||||
|
@ -13,8 +13,8 @@ class UserState extends Equatable {
|
|||||||
required this.status,
|
required this.status,
|
||||||
});
|
});
|
||||||
|
|
||||||
UserState.init()
|
const UserState.init()
|
||||||
: user = User.empty(),
|
: user = const User.empty(),
|
||||||
status = UserStatus.initial;
|
status = UserStatus.initial;
|
||||||
|
|
||||||
final User user;
|
final User user;
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
|
import 'package:hacki/config/constants.dart';
|
||||||
|
|
||||||
extension StringExtension on String {
|
extension StringExtension on String {
|
||||||
int? get itemId {
|
int? get itemId {
|
||||||
final RegExp regex = RegExp(r'\d+$');
|
final RegExp regex = RegExp(RegExpConstants.number);
|
||||||
final RegExp exception = RegExp(r'\)|].*$');
|
final RegExp exception = RegExp(RegExpConstants.linkSuffix);
|
||||||
final String match = regex.stringMatch(replaceAll(exception, '')) ?? '';
|
final String match = regex.stringMatch(replaceAll(exception, '')) ?? '';
|
||||||
return int.tryParse(match);
|
return int.tryParse(match);
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import 'package:hacki/styles/styles.dart';
|
import 'package:hacki/styles/styles.dart';
|
||||||
|
|
||||||
enum FontSize {
|
enum FontSize {
|
||||||
regular('Regular', TextDimens.pt15),
|
small('Small', TextDimens.pt15),
|
||||||
large('Large', TextDimens.pt16),
|
regular('Regular', TextDimens.pt16),
|
||||||
|
large('Large', TextDimens.pt17),
|
||||||
xlarge('XLarge', TextDimens.pt18);
|
xlarge('XLarge', TextDimens.pt18);
|
||||||
|
|
||||||
const FontSize(this.description, this.fontSize);
|
const FontSize(this.description, this.fontSize);
|
||||||
|
@ -155,7 +155,7 @@ class NavigationModePreference extends BooleanPreference {
|
|||||||
String get title => 'Show Web Page First';
|
String get title => 'Show Web Page First';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get subtitle => ''''show web page first after tapping on story.''';
|
String get subtitle => '''show web page first after tapping on story.''';
|
||||||
}
|
}
|
||||||
|
|
||||||
class ReaderModePreference extends BooleanPreference {
|
class ReaderModePreference extends BooleanPreference {
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
class User {
|
class User extends Equatable {
|
||||||
User({
|
const User({
|
||||||
required this.about,
|
required this.about,
|
||||||
required this.created,
|
required this.created,
|
||||||
required this.delay,
|
required this.delay,
|
||||||
@ -9,7 +10,7 @@ class User {
|
|||||||
required this.karma,
|
required this.karma,
|
||||||
});
|
});
|
||||||
|
|
||||||
User.empty()
|
const User.empty()
|
||||||
: about = '',
|
: about = '',
|
||||||
created = 0,
|
created = 0,
|
||||||
delay = 0,
|
delay = 0,
|
||||||
@ -39,4 +40,13 @@ class User {
|
|||||||
String toString() {
|
String toString() {
|
||||||
return 'User $about, $created, $delay, $id, $karma';
|
return 'User $about, $created, $delay, $id, $karma';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => <Object?>[
|
||||||
|
about,
|
||||||
|
created,
|
||||||
|
delay,
|
||||||
|
id,
|
||||||
|
karma,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
@ -171,12 +171,26 @@ class MainView extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
BlocBuilder<PreferenceCubit, PreferenceState>(
|
||||||
|
buildWhen: (
|
||||||
|
PreferenceState previous,
|
||||||
|
PreferenceState current,
|
||||||
|
) =>
|
||||||
|
previous.fontSize != current.fontSize,
|
||||||
|
builder: (
|
||||||
|
BuildContext context,
|
||||||
|
PreferenceState prefState,
|
||||||
|
) {
|
||||||
|
return Column(
|
||||||
|
children: <Widget>[
|
||||||
if (state.item is Story)
|
if (state.item is Story)
|
||||||
InkWell(
|
InkWell(
|
||||||
onTap: () => LinkUtil.launch(
|
onTap: () => LinkUtil.launch(
|
||||||
state.item.url,
|
state.item.url,
|
||||||
useReader:
|
useReader: context
|
||||||
context.read<PreferenceCubit>().state.useReader,
|
.read<PreferenceCubit>()
|
||||||
|
.state
|
||||||
|
.useReader,
|
||||||
offlineReading: context
|
offlineReading: context
|
||||||
.read<StoriesBloc>()
|
.read<StoriesBloc>()
|
||||||
.state
|
.state
|
||||||
@ -194,6 +208,9 @@ class MainView extends StatelessWidget {
|
|||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: MediaQuery.of(context)
|
||||||
|
.textScaleFactor *
|
||||||
|
prefState.fontSize.fontSize,
|
||||||
color: state.item.url.isNotEmpty
|
color: state.item.url.isNotEmpty
|
||||||
? Palette.orange
|
? Palette.orange
|
||||||
: null,
|
: null,
|
||||||
@ -206,25 +223,15 @@ class MainView extends StatelessWidget {
|
|||||||
height: Dimens.pt6,
|
height: Dimens.pt6,
|
||||||
),
|
),
|
||||||
if (state.item.text.isNotEmpty)
|
if (state.item.text.isNotEmpty)
|
||||||
BlocBuilder<PreferenceCubit, PreferenceState>(
|
Padding(
|
||||||
buildWhen: (
|
|
||||||
PreferenceState previous,
|
|
||||||
PreferenceState current,
|
|
||||||
) =>
|
|
||||||
previous.fontSize != current.fontSize,
|
|
||||||
builder: (
|
|
||||||
BuildContext context,
|
|
||||||
PreferenceState prefState,
|
|
||||||
) {
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
horizontal: Dimens.pt10,
|
horizontal: Dimens.pt10,
|
||||||
),
|
),
|
||||||
child: SelectableLinkify(
|
child: SelectableLinkify(
|
||||||
text: state.item.text,
|
text: state.item.text,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize:
|
fontSize: MediaQuery.of(context)
|
||||||
MediaQuery.of(context).textScaleFactor *
|
.textScaleFactor *
|
||||||
context
|
context
|
||||||
.read<PreferenceCubit>()
|
.read<PreferenceCubit>()
|
||||||
.state
|
.state
|
||||||
@ -232,8 +239,8 @@ class MainView extends StatelessWidget {
|
|||||||
.fontSize,
|
.fontSize,
|
||||||
),
|
),
|
||||||
linkStyle: TextStyle(
|
linkStyle: TextStyle(
|
||||||
fontSize:
|
fontSize: MediaQuery.of(context)
|
||||||
MediaQuery.of(context).textScaleFactor *
|
.textScaleFactor *
|
||||||
context
|
context
|
||||||
.read<PreferenceCubit>()
|
.read<PreferenceCubit>()
|
||||||
.state
|
.state
|
||||||
@ -249,6 +256,8 @@ class MainView extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
@ -20,6 +20,7 @@ import 'package:hacki/screens/screens.dart';
|
|||||||
import 'package:hacki/screens/widgets/widgets.dart';
|
import 'package:hacki/screens/widgets/widgets.dart';
|
||||||
import 'package:hacki/styles/styles.dart';
|
import 'package:hacki/styles/styles.dart';
|
||||||
import 'package:hacki/utils/utils.dart';
|
import 'package:hacki/utils/utils.dart';
|
||||||
|
import 'package:package_info_plus/package_info_plus.dart';
|
||||||
import 'package:pull_to_refresh/pull_to_refresh.dart';
|
import 'package:pull_to_refresh/pull_to_refresh.dart';
|
||||||
import 'package:tuple/tuple.dart';
|
import 'package:tuple/tuple.dart';
|
||||||
|
|
||||||
@ -422,93 +423,7 @@ class _ProfileScreenState extends State<ProfileScreen>
|
|||||||
title: const Text('About'),
|
title: const Text('About'),
|
||||||
subtitle:
|
subtitle:
|
||||||
const Text('nothing interesting here.'),
|
const Text('nothing interesting here.'),
|
||||||
onTap: () {
|
onTap: showAboutHackiDialog,
|
||||||
showAboutDialog(
|
|
||||||
context: context,
|
|
||||||
applicationName: 'Hacki',
|
|
||||||
applicationVersion: 'v0.2.33',
|
|
||||||
applicationIcon: ClipRRect(
|
|
||||||
borderRadius: const BorderRadius.all(
|
|
||||||
Radius.circular(
|
|
||||||
Dimens.pt12,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Image.asset(
|
|
||||||
Constants.hackiIconPath,
|
|
||||||
height: Dimens.pt50,
|
|
||||||
width: Dimens.pt50,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
children: <Widget>[
|
|
||||||
ElevatedButton(
|
|
||||||
onPressed: () => LinkUtil.launch(
|
|
||||||
Constants.portfolioLink,
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
children: const <Widget>[
|
|
||||||
Icon(
|
|
||||||
FontAwesomeIcons.addressCard,
|
|
||||||
),
|
|
||||||
SizedBox(
|
|
||||||
width: Dimens.pt12,
|
|
||||||
),
|
|
||||||
Text('Developer'),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
ElevatedButton(
|
|
||||||
onPressed: () => LinkUtil.launch(
|
|
||||||
Constants.githubLink,
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
children: const <Widget>[
|
|
||||||
Icon(
|
|
||||||
FontAwesomeIcons.github,
|
|
||||||
),
|
|
||||||
SizedBox(
|
|
||||||
width: Dimens.pt12,
|
|
||||||
),
|
|
||||||
Text('Source code'),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
ElevatedButton(
|
|
||||||
onPressed: () => LinkUtil.launch(
|
|
||||||
Platform.isIOS
|
|
||||||
? Constants.appStoreLink
|
|
||||||
: Constants.googlePlayLink,
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
children: const <Widget>[
|
|
||||||
Icon(
|
|
||||||
Icons.thumb_up,
|
|
||||||
),
|
|
||||||
SizedBox(
|
|
||||||
width: Dimens.pt12,
|
|
||||||
),
|
|
||||||
Text('Like the app?'),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
ElevatedButton(
|
|
||||||
onPressed: () => LinkUtil.launch(
|
|
||||||
Constants.sponsorLink,
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
children: const <Widget>[
|
|
||||||
Icon(
|
|
||||||
FeatherIcons.coffee,
|
|
||||||
),
|
|
||||||
SizedBox(
|
|
||||||
width: Dimens.pt12,
|
|
||||||
),
|
|
||||||
Text('Buy me a coffee'),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: Dimens.pt48,
|
height: Dimens.pt48,
|
||||||
@ -718,6 +633,95 @@ class _ProfileScreenState extends State<ProfileScreen>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> showAboutHackiDialog() async {
|
||||||
|
final PackageInfo packageInfo = await PackageInfo.fromPlatform();
|
||||||
|
final String version = packageInfo.version;
|
||||||
|
|
||||||
|
showAboutDialog(
|
||||||
|
context: context,
|
||||||
|
applicationName: 'Hacki',
|
||||||
|
applicationVersion: 'v$version',
|
||||||
|
applicationIcon: ClipRRect(
|
||||||
|
borderRadius: const BorderRadius.all(
|
||||||
|
Radius.circular(
|
||||||
|
Dimens.pt12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Image.asset(
|
||||||
|
Constants.hackiIconPath,
|
||||||
|
height: Dimens.pt50,
|
||||||
|
width: Dimens.pt50,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
children: <Widget>[
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () => LinkUtil.launch(
|
||||||
|
Constants.portfolioLink,
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: const <Widget>[
|
||||||
|
Icon(
|
||||||
|
FontAwesomeIcons.addressCard,
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
width: Dimens.pt12,
|
||||||
|
),
|
||||||
|
Text('Developer'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () => LinkUtil.launch(
|
||||||
|
Constants.githubLink,
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: const <Widget>[
|
||||||
|
Icon(
|
||||||
|
FontAwesomeIcons.github,
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
width: Dimens.pt12,
|
||||||
|
),
|
||||||
|
Text('Source code'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () => LinkUtil.launch(
|
||||||
|
Platform.isIOS ? Constants.appStoreLink : Constants.googlePlayLink,
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: const <Widget>[
|
||||||
|
Icon(
|
||||||
|
Icons.thumb_up,
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
width: Dimens.pt12,
|
||||||
|
),
|
||||||
|
Text('Like the app?'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () => LinkUtil.launch(
|
||||||
|
Constants.sponsorLink,
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: const <Widget>[
|
||||||
|
Icon(
|
||||||
|
FeatherIcons.coffee,
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
width: Dimens.pt12,
|
||||||
|
),
|
||||||
|
Text('Buy me a coffee'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
void onCommentTapped(Comment comment, {VoidCallback? then}) {
|
void onCommentTapped(Comment comment, {VoidCallback? then}) {
|
||||||
throttle.run(() {
|
throttle.run(() {
|
||||||
locator
|
locator
|
||||||
|
62
lib/screens/widgets/bloc_builder_3.dart
Normal file
62
lib/screens/widgets/bloc_builder_3.dart
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
|
typedef BlocBuilderCondition<S> = bool Function(S previous, S current);
|
||||||
|
typedef BlocWidgetBuilder3<StateA, StateB, StateC> = Widget Function(
|
||||||
|
BuildContext,
|
||||||
|
StateA,
|
||||||
|
StateB,
|
||||||
|
StateC,
|
||||||
|
);
|
||||||
|
|
||||||
|
class BlocBuilder3<
|
||||||
|
BlocA extends StateStreamable<BlocAState>,
|
||||||
|
BlocAState,
|
||||||
|
BlocB extends StateStreamable<BlocBState>,
|
||||||
|
BlocBState,
|
||||||
|
BlocC extends StateStreamable<BlocCState>,
|
||||||
|
BlocCState> extends StatelessWidget {
|
||||||
|
const BlocBuilder3({
|
||||||
|
Key? key,
|
||||||
|
required this.builder,
|
||||||
|
this.blocA,
|
||||||
|
this.blocB,
|
||||||
|
this.blocC,
|
||||||
|
this.buildWhenA,
|
||||||
|
this.buildWhenB,
|
||||||
|
this.buildWhenC,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
final BlocWidgetBuilder3<BlocAState, BlocBState, BlocCState> builder;
|
||||||
|
|
||||||
|
final BlocA? blocA;
|
||||||
|
final BlocB? blocB;
|
||||||
|
final BlocC? blocC;
|
||||||
|
|
||||||
|
final BlocBuilderCondition<BlocAState>? buildWhenA;
|
||||||
|
final BlocBuilderCondition<BlocBState>? buildWhenB;
|
||||||
|
final BlocBuilderCondition<BlocCState>? buildWhenC;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BlocBuilder<BlocA, BlocAState>(
|
||||||
|
bloc: blocA,
|
||||||
|
buildWhen: buildWhenA,
|
||||||
|
builder: (BuildContext context, BlocAState blocAState) {
|
||||||
|
return BlocBuilder<BlocB, BlocBState>(
|
||||||
|
bloc: blocB,
|
||||||
|
buildWhen: buildWhenB,
|
||||||
|
builder: (BuildContext context, BlocBState blocBState) {
|
||||||
|
return BlocBuilder<BlocC, BlocCState>(
|
||||||
|
bloc: blocC,
|
||||||
|
buildWhen: buildWhenC,
|
||||||
|
builder: (BuildContext context, BlocCState blocCState) {
|
||||||
|
return builder(context, blocAState, blocBState, blocCState);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -7,6 +7,7 @@ import 'package:hacki/blocs/blocs.dart';
|
|||||||
import 'package:hacki/cubits/cubits.dart';
|
import 'package:hacki/cubits/cubits.dart';
|
||||||
import 'package:hacki/extensions/extensions.dart';
|
import 'package:hacki/extensions/extensions.dart';
|
||||||
import 'package:hacki/models/models.dart';
|
import 'package:hacki/models/models.dart';
|
||||||
|
import 'package:hacki/screens/widgets/bloc_builder_3.dart';
|
||||||
import 'package:hacki/services/services.dart';
|
import 'package:hacki/services/services.dart';
|
||||||
import 'package:hacki/styles/styles.dart';
|
import 'package:hacki/styles/styles.dart';
|
||||||
import 'package:hacki/utils/utils.dart';
|
import 'package:hacki/utils/utils.dart';
|
||||||
@ -46,16 +47,19 @@ class CommentTile extends StatelessWidget {
|
|||||||
lazy: false,
|
lazy: false,
|
||||||
create: (_) => CollapseCubit(
|
create: (_) => CollapseCubit(
|
||||||
commentId: comment.id,
|
commentId: comment.id,
|
||||||
|
commentsCubit: context.tryRead<CommentsCubit>(),
|
||||||
collapseCache: context.tryRead<CollapseCache>() ?? CollapseCache(),
|
collapseCache: context.tryRead<CollapseCache>() ?? CollapseCache(),
|
||||||
)..init(),
|
)..init(),
|
||||||
child: BlocBuilder<CollapseCubit, CollapseState>(
|
child: BlocBuilder3<CollapseCubit, CollapseState, PreferenceCubit,
|
||||||
builder: (BuildContext context, CollapseState state) {
|
PreferenceState, BlocklistCubit, BlocklistState>(
|
||||||
|
builder: (
|
||||||
|
BuildContext context,
|
||||||
|
CollapseState state,
|
||||||
|
PreferenceState prefState,
|
||||||
|
BlocklistState blocklistState,
|
||||||
|
) {
|
||||||
if (actionable && state.hidden) return const SizedBox.shrink();
|
if (actionable && state.hidden) return const SizedBox.shrink();
|
||||||
|
|
||||||
return BlocBuilder<PreferenceCubit, PreferenceState>(
|
|
||||||
builder: (BuildContext context, PreferenceState prefState) {
|
|
||||||
return BlocBuilder<BlocklistCubit, BlocklistState>(
|
|
||||||
builder: (BuildContext context, BlocklistState blocklistState) {
|
|
||||||
const Color orange = Color.fromRGBO(255, 152, 0, 1);
|
const Color orange = Color.fromRGBO(255, 152, 0, 1);
|
||||||
final Color color = _getColor(level);
|
final Color color = _getColor(level);
|
||||||
|
|
||||||
@ -70,21 +74,15 @@ class CommentTile extends StatelessWidget {
|
|||||||
motion: const StretchMotion(),
|
motion: const StretchMotion(),
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
SlidableAction(
|
SlidableAction(
|
||||||
onPressed: (_) =>
|
onPressed: (_) => onReplyTapped?.call(comment),
|
||||||
onReplyTapped?.call(comment),
|
|
||||||
backgroundColor: Palette.orange,
|
backgroundColor: Palette.orange,
|
||||||
foregroundColor: Palette.white,
|
foregroundColor: Palette.white,
|
||||||
icon: Icons.message,
|
icon: Icons.message,
|
||||||
),
|
),
|
||||||
if (context
|
if (context.read<AuthBloc>().state.user.id ==
|
||||||
.read<AuthBloc>()
|
|
||||||
.state
|
|
||||||
.user
|
|
||||||
.id ==
|
|
||||||
comment.by)
|
comment.by)
|
||||||
SlidableAction(
|
SlidableAction(
|
||||||
onPressed: (_) =>
|
onPressed: (_) => onEditTapped?.call(comment),
|
||||||
onEditTapped?.call(comment),
|
|
||||||
backgroundColor: Palette.orange,
|
backgroundColor: Palette.orange,
|
||||||
foregroundColor: Palette.white,
|
foregroundColor: Palette.white,
|
||||||
icon: Icons.edit,
|
icon: Icons.edit,
|
||||||
@ -137,9 +135,8 @@ class CommentTile extends StatelessWidget {
|
|||||||
Text(
|
Text(
|
||||||
comment.by,
|
comment.by,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: prefState.showEyeCandy
|
color:
|
||||||
? orange
|
prefState.showEyeCandy ? orange : color,
|
||||||
: color,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (comment.by == opUsername)
|
if (comment.by == opUsername)
|
||||||
@ -202,8 +199,7 @@ class CommentTile extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
else if (blocklistState.blocklist
|
else if (blocklistState.blocklist.contains(comment.by))
|
||||||
.contains(comment.by))
|
|
||||||
const Center(
|
const Center(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: EdgeInsets.only(
|
padding: EdgeInsets.only(
|
||||||
@ -229,8 +225,7 @@ class CommentTile extends StatelessWidget {
|
|||||||
? SelectableText.rich(
|
? SelectableText.rich(
|
||||||
key: ValueKey<int>(comment.id),
|
key: ValueKey<int>(comment.id),
|
||||||
buildTextSpan(
|
buildTextSpan(
|
||||||
(comment as BuildableComment)
|
(comment as BuildableComment).elements,
|
||||||
.elements,
|
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: MediaQuery.of(
|
fontSize: MediaQuery.of(
|
||||||
context,
|
context,
|
||||||
@ -242,14 +237,12 @@ class CommentTile extends StatelessWidget {
|
|||||||
context,
|
context,
|
||||||
).textScaleFactor *
|
).textScaleFactor *
|
||||||
prefState.fontSize.fontSize,
|
prefState.fontSize.fontSize,
|
||||||
decoration:
|
decoration: TextDecoration.underline,
|
||||||
TextDecoration.underline,
|
|
||||||
color: Palette.orange,
|
color: Palette.orange,
|
||||||
),
|
),
|
||||||
onOpen: (LinkableElement link) {
|
onOpen: (LinkableElement link) {
|
||||||
if (link.url.isStoryLink) {
|
if (link.url.isStoryLink) {
|
||||||
onStoryLinkTapped
|
onStoryLinkTapped.call(link.url);
|
||||||
.call(link.url);
|
|
||||||
} else {
|
} else {
|
||||||
LinkUtil.launch(link.url);
|
LinkUtil.launch(link.url);
|
||||||
}
|
}
|
||||||
@ -273,8 +266,7 @@ class CommentTile extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
onOpen: (LinkableElement link) {
|
onOpen: (LinkableElement link) {
|
||||||
if (link.url.isStoryLink) {
|
if (link.url.isStoryLink) {
|
||||||
onStoryLinkTapped
|
onStoryLinkTapped.call(link.url);
|
||||||
.call(link.url);
|
|
||||||
} else {
|
} else {
|
||||||
LinkUtil.launch(link.url);
|
LinkUtil.launch(link.url);
|
||||||
}
|
}
|
||||||
@ -300,16 +292,13 @@ class CommentTile extends StatelessWidget {
|
|||||||
horizontal: Dimens.pt12,
|
horizontal: Dimens.pt12,
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment:
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
MainAxisAlignment.center,
|
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Expanded(
|
Expanded(
|
||||||
child: TextButton(
|
child: TextButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
HapticFeedback.selectionClick();
|
HapticFeedback.selectionClick();
|
||||||
context
|
context.read<CommentsCubit>().loadMore(
|
||||||
.read<CommentsCubit>()
|
|
||||||
.loadMore(
|
|
||||||
comment: comment,
|
comment: comment,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -337,9 +326,7 @@ class CommentTile extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
|
|
||||||
final double commentBackgroundColorOpacity =
|
final double commentBackgroundColorOpacity =
|
||||||
Theme.of(context).brightness == Brightness.dark
|
Theme.of(context).brightness == Brightness.dark ? 0.03 : 0.15;
|
||||||
? 0.03
|
|
||||||
: 0.15;
|
|
||||||
|
|
||||||
final Color commentColor = prefState.showEyeCandy
|
final Color commentColor = prefState.showEyeCandy
|
||||||
? color.withOpacity(commentBackgroundColorOpacity)
|
? color.withOpacity(commentBackgroundColorOpacity)
|
||||||
@ -350,7 +337,10 @@ class CommentTile extends StatelessWidget {
|
|||||||
|
|
||||||
if (isMyComment && level == 0) {
|
if (isMyComment && level == 0) {
|
||||||
return Container(
|
return Container(
|
||||||
|
clipBehavior: Clip.hardEdge,
|
||||||
|
decoration: BoxDecoration(
|
||||||
color: Palette.orange.withOpacity(0.2),
|
color: Palette.orange.withOpacity(0.2),
|
||||||
|
),
|
||||||
child: wrapper,
|
child: wrapper,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -361,7 +351,7 @@ class CommentTile extends StatelessWidget {
|
|||||||
wrapper = Container(
|
wrapper = Container(
|
||||||
clipBehavior: Clip.hardEdge,
|
clipBehavior: Clip.hardEdge,
|
||||||
margin: const EdgeInsets.only(
|
margin: const EdgeInsets.only(
|
||||||
left: Dimens.pt12,
|
left: Dimens.pt8,
|
||||||
),
|
),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
border: i != 0
|
border: i != 0
|
||||||
@ -381,10 +371,6 @@ class CommentTile extends StatelessWidget {
|
|||||||
|
|
||||||
return wrapper;
|
return wrapper;
|
||||||
},
|
},
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -42,7 +42,87 @@ class StoryTile extends StatelessWidget {
|
|||||||
story: story,
|
story: story,
|
||||||
link: story.url,
|
link: story.url,
|
||||||
offlineReading: context.read<StoriesBloc>().state.offlineReading,
|
offlineReading: context.read<StoriesBloc>().state.offlineReading,
|
||||||
placeholderWidget: FadeIn(
|
placeholderWidget: _LinkPreviewPlaceholder(
|
||||||
|
height: height,
|
||||||
|
),
|
||||||
|
errorImage: Constants.hackerNewsLogoLink,
|
||||||
|
backgroundColor: Palette.transparent,
|
||||||
|
borderRadius: Dimens.zero,
|
||||||
|
removeElevation: true,
|
||||||
|
bodyMaxLines: context.storyTileMaxLines,
|
||||||
|
errorTitle: story.title,
|
||||||
|
titleStyle: TextStyle(
|
||||||
|
color: hasRead
|
||||||
|
? Palette.grey[500]
|
||||||
|
: Theme.of(context).textTheme.subtitle1?.color,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
showMetadata: showMetadata,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return InkWell(
|
||||||
|
onTap: onTap,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(left: Dimens.pt12),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: <Widget>[
|
||||||
|
const SizedBox(
|
||||||
|
height: Dimens.pt8,
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
children: <Widget>[
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
story.title,
|
||||||
|
style: TextStyle(
|
||||||
|
color: hasRead ? Palette.grey[500] : null,
|
||||||
|
fontSize: simpleTileFontSize,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (showMetadata)
|
||||||
|
Row(
|
||||||
|
children: <Widget>[
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
story.metadata,
|
||||||
|
style: TextStyle(
|
||||||
|
color: Palette.grey,
|
||||||
|
fontSize: simpleTileFontSize - 2,
|
||||||
|
),
|
||||||
|
maxLines: 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: Dimens.pt8,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _LinkPreviewPlaceholder extends StatelessWidget {
|
||||||
|
const _LinkPreviewPlaceholder({
|
||||||
|
Key? key,
|
||||||
|
required this.height,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
final double height;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return FadeIn(
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
height: height,
|
height: height,
|
||||||
child: Shimmer.fromColors(
|
child: Shimmer.fromColors(
|
||||||
@ -126,70 +206,6 @@ class StoryTile extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
errorImage: Constants.hackerNewsLogoLink,
|
|
||||||
backgroundColor: Palette.transparent,
|
|
||||||
borderRadius: Dimens.zero,
|
|
||||||
removeElevation: true,
|
|
||||||
bodyMaxLines: context.storyTileMaxLines,
|
|
||||||
errorTitle: story.title,
|
|
||||||
titleStyle: TextStyle(
|
|
||||||
color: hasRead
|
|
||||||
? Palette.grey[500]
|
|
||||||
: Theme.of(context).textTheme.subtitle1!.color,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
showMetadata: showMetadata,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return InkWell(
|
|
||||||
onTap: onTap,
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.only(left: Dimens.pt12),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: <Widget>[
|
|
||||||
const SizedBox(
|
|
||||||
height: Dimens.pt8,
|
|
||||||
),
|
|
||||||
Row(
|
|
||||||
children: <Widget>[
|
|
||||||
Expanded(
|
|
||||||
child: Text(
|
|
||||||
story.title,
|
|
||||||
style: TextStyle(
|
|
||||||
color: hasRead ? Palette.grey[500] : null,
|
|
||||||
fontSize: simpleTileFontSize,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
if (showMetadata)
|
|
||||||
Row(
|
|
||||||
children: <Widget>[
|
|
||||||
Expanded(
|
|
||||||
child: Text(
|
|
||||||
story.metadata,
|
|
||||||
style: TextStyle(
|
|
||||||
color: Palette.grey,
|
|
||||||
fontSize: simpleTileFontSize - 2,
|
|
||||||
),
|
|
||||||
maxLines: 1,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(
|
|
||||||
height: Dimens.pt8,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
export 'bloc_builder_3.dart';
|
||||||
export 'circle_tab_indicator.dart';
|
export 'circle_tab_indicator.dart';
|
||||||
export 'comment_tile.dart';
|
export 'comment_tile.dart';
|
||||||
export 'countdown_reminder.dart';
|
export 'countdown_reminder.dart';
|
||||||
|
@ -15,7 +15,7 @@ class CollapseCache {
|
|||||||
addIfParentIsHiddenOrCollapsed(commentId, to);
|
addIfParentIsHiddenOrCollapsed(commentId, to);
|
||||||
}
|
}
|
||||||
|
|
||||||
int collapse(int commentId) {
|
Set<int> collapse(int commentId) {
|
||||||
_collapsed.add(commentId);
|
_collapsed.add(commentId);
|
||||||
|
|
||||||
Set<int> findHiddenComments(int commentId) {
|
Set<int> findHiddenComments(int commentId) {
|
||||||
@ -35,7 +35,7 @@ class CollapseCache {
|
|||||||
|
|
||||||
_hiddenCommentsSubject.add(_hidden);
|
_hiddenCommentsSubject.add(_hidden);
|
||||||
|
|
||||||
return hiddenComments.length;
|
return hiddenComments;
|
||||||
}
|
}
|
||||||
|
|
||||||
void uncollapse(int commentId) {
|
void uncollapse(int commentId) {
|
||||||
|
@ -31,6 +31,7 @@ abstract class TextDimens {
|
|||||||
static const double pt14 = 14;
|
static const double pt14 = 14;
|
||||||
static const double pt15 = 15;
|
static const double pt15 = 15;
|
||||||
static const double pt16 = 16;
|
static const double pt16 = 16;
|
||||||
|
static const double pt17 = 17;
|
||||||
static const double pt18 = 18;
|
static const double pt18 = 18;
|
||||||
static const double pt20 = 20;
|
static const double pt20 = 20;
|
||||||
static const double pt24 = 24;
|
static const double pt24 = 24;
|
||||||
|
@ -2,6 +2,7 @@ import 'dart:io';
|
|||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
|
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
|
||||||
|
import 'package:hacki/config/constants.dart';
|
||||||
import 'package:hacki/config/locator.dart';
|
import 'package:hacki/config/locator.dart';
|
||||||
import 'package:hacki/main.dart';
|
import 'package:hacki/main.dart';
|
||||||
import 'package:hacki/repositories/repositories.dart';
|
import 'package:hacki/repositories/repositories.dart';
|
||||||
@ -35,7 +36,7 @@ abstract class LinkUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Uri rinseLink(String link) {
|
Uri rinseLink(String link) {
|
||||||
final RegExp regex = RegExp(r'\)|].*$');
|
final RegExp regex = RegExp(RegExpConstants.linkSuffix);
|
||||||
if (!link.contains('en.wikipedia.org') && link.contains(regex)) {
|
if (!link.contains('en.wikipedia.org') && link.contains(regex)) {
|
||||||
final String match = regex.stringMatch(link) ?? '';
|
final String match = regex.stringMatch(link) ?? '';
|
||||||
return Uri.parse(link.replaceAll(match, ''));
|
return Uri.parse(link.replaceAll(match, ''));
|
||||||
|
14
pubspec.lock
14
pubspec.lock
@ -607,6 +607,20 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.0"
|
version: "2.1.0"
|
||||||
|
package_info_plus:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: package_info_plus
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.2"
|
||||||
|
package_info_plus_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: package_info_plus_platform_interface
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.1"
|
||||||
path:
|
path:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
name: hacki
|
name: hacki
|
||||||
description: A Hacker News reader.
|
description: A Hacker News reader.
|
||||||
version: 0.2.33+76
|
version: 1.0.1+79
|
||||||
publish_to: none
|
publish_to: none
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
@ -18,7 +18,6 @@ dependencies:
|
|||||||
dio: ^4.0.4
|
dio: ^4.0.4
|
||||||
equatable: ^2.0.5
|
equatable: ^2.0.5
|
||||||
fast_gbk: ^1.0.0
|
fast_gbk: ^1.0.0
|
||||||
# feature_discovery: ^0.14.0
|
|
||||||
feature_discovery:
|
feature_discovery:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/livinglist/feature_discovery
|
url: https://github.com/livinglist/feature_discovery
|
||||||
@ -45,11 +44,11 @@ dependencies:
|
|||||||
hydrated_bloc: ^9.0.0-dev.3
|
hydrated_bloc: ^9.0.0-dev.3
|
||||||
intl: ^0.17.0
|
intl: ^0.17.0
|
||||||
logger: ^1.1.0
|
logger: ^1.1.0
|
||||||
|
package_info_plus: ^3.0.2
|
||||||
path: ^1.8.0
|
path: ^1.8.0
|
||||||
path_provider: ^2.0.8
|
path_provider: ^2.0.8
|
||||||
path_provider_android: ^2.0.8
|
path_provider_android: ^2.0.8
|
||||||
path_provider_ios: ^2.0.8
|
path_provider_ios: ^2.0.8
|
||||||
# pull_to_refresh: ^2.0.0
|
|
||||||
pull_to_refresh:
|
pull_to_refresh:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/livinglist/flutter_pulltorefresh
|
url: https://github.com/livinglist/flutter_pulltorefresh
|
||||||
|
1
submodules/flutter
Submodule
1
submodules/flutter
Submodule
Submodule submodules/flutter added at 135454af32
158
test/blocs/auth/auth_bloc_test.dart
Normal file
158
test/blocs/auth/auth_bloc_test.dart
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
import 'package:bloc_test/bloc_test.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:hacki/blocs/blocs.dart';
|
||||||
|
import 'package:hacki/models/models.dart';
|
||||||
|
import 'package:hacki/repositories/repositories.dart';
|
||||||
|
import 'package:mocktail/mocktail.dart';
|
||||||
|
|
||||||
|
class MockAuthRepository extends Mock implements AuthRepository {}
|
||||||
|
|
||||||
|
class MockPreferenceRepository extends Mock implements PreferenceRepository {}
|
||||||
|
|
||||||
|
class MockStoriesRepository extends Mock implements StoriesRepository {}
|
||||||
|
|
||||||
|
class MockSembastRepository extends Mock implements SembastRepository {}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
final MockAuthRepository mockAuthRepository = MockAuthRepository();
|
||||||
|
final MockPreferenceRepository mockPreferenceRepository =
|
||||||
|
MockPreferenceRepository();
|
||||||
|
final MockStoriesRepository mockStoriesRepository = MockStoriesRepository();
|
||||||
|
final MockSembastRepository mockSembastRepository = MockSembastRepository();
|
||||||
|
|
||||||
|
const int created = 0, delay = 1, karma = 2;
|
||||||
|
const String about = 'about', id = 'id';
|
||||||
|
|
||||||
|
const User tUser = User(
|
||||||
|
about: about,
|
||||||
|
created: created,
|
||||||
|
delay: delay,
|
||||||
|
id: id,
|
||||||
|
karma: karma,
|
||||||
|
);
|
||||||
|
|
||||||
|
group(
|
||||||
|
'AuthBloc',
|
||||||
|
() {
|
||||||
|
setUp(() {
|
||||||
|
when(() => mockAuthRepository.loggedIn)
|
||||||
|
.thenAnswer((_) => Future<bool>.value(false));
|
||||||
|
});
|
||||||
|
|
||||||
|
test(
|
||||||
|
'initial state is AuthState.init',
|
||||||
|
() {
|
||||||
|
expect(
|
||||||
|
AuthBloc(
|
||||||
|
authRepository: mockAuthRepository,
|
||||||
|
preferenceRepository: mockPreferenceRepository,
|
||||||
|
storiesRepository: mockStoriesRepository,
|
||||||
|
sembastRepository: mockSembastRepository,
|
||||||
|
).state,
|
||||||
|
equals(const AuthState.init()),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
group('AuthAppStarted', () {
|
||||||
|
const String username = 'username', password = 'password';
|
||||||
|
setUp(() {
|
||||||
|
when(() => mockAuthRepository.username)
|
||||||
|
.thenAnswer((_) => Future<String?>.value(username));
|
||||||
|
when(() => mockAuthRepository.password)
|
||||||
|
.thenAnswer((_) => Future<String>.value(password));
|
||||||
|
when(() => mockStoriesRepository.fetchUserBy(userId: username))
|
||||||
|
.thenAnswer((_) => Future<User>.value(tUser));
|
||||||
|
when(() => mockAuthRepository.loggedIn)
|
||||||
|
.thenAnswer((_) => Future<bool>.value(false));
|
||||||
|
});
|
||||||
|
|
||||||
|
blocTest<AuthBloc, AuthState>(
|
||||||
|
'initialize',
|
||||||
|
build: () {
|
||||||
|
return AuthBloc(
|
||||||
|
authRepository: mockAuthRepository,
|
||||||
|
preferenceRepository: mockPreferenceRepository,
|
||||||
|
storiesRepository: mockStoriesRepository,
|
||||||
|
sembastRepository: mockSembastRepository,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
expect: () => <AuthState>[
|
||||||
|
const AuthState.init().copyWith(
|
||||||
|
status: AuthStatus.loaded,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
verify: (_) {
|
||||||
|
verify(() => mockAuthRepository.loggedIn).called(2);
|
||||||
|
verifyNever(() => mockAuthRepository.username);
|
||||||
|
verifyNever(() => mockStoriesRepository.fetchUserBy(userId: username));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
blocTest<AuthBloc, AuthState>(
|
||||||
|
'sign in',
|
||||||
|
build: () {
|
||||||
|
when(
|
||||||
|
() => mockAuthRepository.login(
|
||||||
|
username: username,
|
||||||
|
password: password,
|
||||||
|
),
|
||||||
|
).thenAnswer((_) => Future<bool>.value(true));
|
||||||
|
return AuthBloc(
|
||||||
|
authRepository: mockAuthRepository,
|
||||||
|
preferenceRepository: mockPreferenceRepository,
|
||||||
|
storiesRepository: mockStoriesRepository,
|
||||||
|
sembastRepository: mockSembastRepository,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
act: (AuthBloc bloc) => bloc
|
||||||
|
..add(
|
||||||
|
AuthToggleAgreeToEULA(),
|
||||||
|
)
|
||||||
|
..add(
|
||||||
|
AuthLogin(
|
||||||
|
username: username,
|
||||||
|
password: password,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
expect: () => <AuthState>[
|
||||||
|
const AuthState(
|
||||||
|
user: User.empty(),
|
||||||
|
isLoggedIn: false,
|
||||||
|
status: AuthStatus.loaded,
|
||||||
|
agreedToEULA: false,
|
||||||
|
),
|
||||||
|
const AuthState(
|
||||||
|
user: User.empty(),
|
||||||
|
isLoggedIn: false,
|
||||||
|
status: AuthStatus.loaded,
|
||||||
|
agreedToEULA: true,
|
||||||
|
),
|
||||||
|
const AuthState(
|
||||||
|
user: User.empty(),
|
||||||
|
isLoggedIn: false,
|
||||||
|
status: AuthStatus.loading,
|
||||||
|
agreedToEULA: true,
|
||||||
|
),
|
||||||
|
const AuthState(
|
||||||
|
user: tUser,
|
||||||
|
isLoggedIn: true,
|
||||||
|
status: AuthStatus.loaded,
|
||||||
|
agreedToEULA: true,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
verify: (_) {
|
||||||
|
verify(
|
||||||
|
() => mockAuthRepository.login(
|
||||||
|
username: username,
|
||||||
|
password: password,
|
||||||
|
),
|
||||||
|
).called(1);
|
||||||
|
verify(() => mockStoriesRepository.fetchUserBy(userId: username))
|
||||||
|
.called(1);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
@ -1,28 +0,0 @@
|
|||||||
import 'package:adaptive_theme/adaptive_theme.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
import 'package:hacki/main.dart';
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
|
|
||||||
// Build our app and trigger a frame.
|
|
||||||
await tester.pumpWidget(
|
|
||||||
const HackiApp(
|
|
||||||
savedThemeMode: AdaptiveThemeMode.light,
|
|
||||||
trueDarkMode: false,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Verify that our counter starts at 0.
|
|
||||||
expect(find.text('0'), findsOneWidget);
|
|
||||||
expect(find.text('1'), findsNothing);
|
|
||||||
|
|
||||||
// Tap the '+' icon and trigger a frame.
|
|
||||||
await tester.tap(find.byIcon(Icons.add));
|
|
||||||
await tester.pump();
|
|
||||||
|
|
||||||
// Verify that our counter has incremented.
|
|
||||||
expect(find.text('0'), findsNothing);
|
|
||||||
expect(find.text('1'), findsOneWidget);
|
|
||||||
});
|
|
||||||
}
|
|
Reference in New Issue
Block a user