Compare commits

...

25 Commits

Author SHA1 Message Date
03c01a0b78 bump versions. (#97) 2022-12-27 13:56:21 -08:00
f823fdf241 use version from pubspec.yaml (#95) (#96) 2022-12-27 13:40:41 -08:00
fe87ddd8ff add (#95) 2022-12-27 13:36:14 -08:00
613ba12b05 fix time machine. (#94)
* bumped version.

* fix time machine.
2022-12-27 12:54:18 -08:00
8d7f66ecbc add timeout. (#93) 2022-12-27 01:17:16 -08:00
461aae253b update publish_ios.yml (#92) 2022-12-27 00:22:04 -08:00
a1b491cf0d fix regex for getting item id. (#91) 2022-12-27 00:00:10 -08:00
edf0c82040 Improve loading mechanism. (#90)
* load more comments when user folds the last comment.

* improvements.

* improve loading experience.
2022-12-26 22:55:50 -08:00
946a3c5a9a Improve loading mechanism. (#89)
* load more comments when user folds the last comment.

* improvements.
2022-12-26 22:09:31 -08:00
d8bc60c071 Add tests. (#88)
* update fontsize.

* fix title.

* fix info list.

* add small.

* nit.

* nit.

* test.

* add tests.

* update github action.
2022-12-25 20:12:11 -08:00
48477cd5c8 Fix comment tile. (#87)
* fix comment not correctly collapsing.

* fix comment tile overflow.

* bumped version to 1.0.0
2022-12-25 00:58:14 -08:00
38df6293fe update comment tile. (#86) 2022-12-22 19:32:57 -08:00
a5fe9e45fc fix NavigationModePreference 2022-12-21 11:09:59 -08:00
9de5baa77a bumped version. (#85) 2022-12-20 23:34:45 -08:00
2daccd64e8 update fontsize. (#84)
* update fontsize.

* fix title.

* fix info list.

* add small.

* nit.

* nit.
2022-12-20 22:49:33 -08:00
d0c68f9419 update Fastfile. 2022-12-20 22:10:49 -08:00
5f1dbfc510 update Fastfile 2022-12-20 21:58:45 -08:00
90eee37c17 update Fastfile 2022-12-20 21:25:21 -08:00
5630e61a74 update Fastfile 2022-12-20 21:02:34 -08:00
eaad4b01dd fix ci. (#83)
* fix ci.

* update project.

* update github checks.

* update github checks.

* nit.

* nit.

* update fastfile.

* fix info.plist

* nit.

* nit.

* nit.

* nit.

* nit.

* nit.

* nit.

* update publish_ios.yml
2022-12-20 20:37:49 -08:00
3ab172f3d3 update publish_ios.yml 2022-12-19 13:51:57 -08:00
5450eba64b fix RegExp. (#82)
* fix regexp.

* bump version.
2022-12-19 13:51:10 -08:00
e2d6bb44d0 update publish_ios.yml 2022-12-19 13:46:00 -08:00
ffbd3a2449 add flutter as submodule (#80)
* add flutter as submodule

* move flutter to submodules.

* removed unused file.

* nit.
2022-12-18 18:33:46 -08:00
2405a6d30c update publish_ios.yml 2022-12-17 18:48:16 -08:00
38 changed files with 951 additions and 686 deletions

View File

@ -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

View File

@ -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
View File

@ -0,0 +1,3 @@
[submodule "flutter"]
path = submodules/flutter
url = https://github.com/flutter/flutter

View File

@ -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/**"

View File

@ -0,0 +1,2 @@
- Fixed app icon.
- Added font size setting to comments screen.

View File

@ -0,0 +1 @@
- Fixed time machine.

View File

@ -0,0 +1 @@
- Fixed time machine.

View File

@ -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)

View File

@ -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'

View File

@ -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

View File

@ -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;

View File

@ -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>

View File

@ -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({

View File

@ -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,
), ),

View File

@ -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;

View File

@ -48,3 +48,8 @@ abstract class Constants {
'(ㆆ_ㆆ)', '(ㆆ_ㆆ)',
]; ];
} }
abstract class RegExpConstants {
static const String linkSuffix = r'(\)|])(.)*$';
static const String number = '[0-9]+';
}

View File

@ -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,
), ),
); );
} }

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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);
} }

View File

@ -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);

View File

@ -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 {

View File

@ -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,
];
} }

View File

@ -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 {
} }
}, },
), ),
),
],
); );
}, },
), ),

View File

@ -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

View 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);
},
);
},
);
},
);
}
}

View File

@ -7,6 +7,7 @@ import 'package:hacki/blocs/blocs.dart';
import 'package:hacki/cubits/cubits.dart'; import 'package:hacki/cubits/cubits.dart';
import 'package:hacki/extensions/extensions.dart'; import 'package:hacki/extensions/extensions.dart';
import 'package:hacki/models/models.dart'; import 'package:hacki/models/models.dart';
import 'package:hacki/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;
}, },
);
},
);
},
), ),
); );
} }

View File

@ -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,
),
],
),
),
); );
} }
} }
}

View File

@ -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';

View File

@ -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) {

View File

@ -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;

View File

@ -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, ''));

View File

@ -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:

View File

@ -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

Submodule submodules/flutter added at 135454af32

View 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);
},
);
});
}

View File

@ -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);
});
}