Compare commits
8 Commits
fastlane-s
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
84c02cc6dd | ||
|
|
fac6560531 | ||
|
|
33b7e491c2 | ||
|
|
209750c4e5 | ||
|
|
b75f93d950 | ||
|
|
fb7d5376f8 | ||
|
|
806341cdd6 | ||
|
|
2c539d3a05 |
10
.github/workflows/Xcode-build-analyze.yml
vendored
10
.github/workflows/Xcode-build-analyze.yml
vendored
@ -22,20 +22,20 @@ jobs:
|
||||
xcodebuild -version
|
||||
|
||||
- name: Resolve packages
|
||||
run: xcodebuild -resolvePackageDependencies -project hellbender.xcodeproj -scheme hellbender
|
||||
run: xcodebuild -resolvePackageDependencies -project birch.xcodeproj -scheme birch
|
||||
|
||||
- name: Validate Package.resolved
|
||||
run: |
|
||||
if ! git diff --quiet hellbender.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved; then
|
||||
if ! git diff --quiet birch.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved; then
|
||||
echo "::error::Package.resolved has uncommitted changes after resolution. Commit the updated Package.resolved."
|
||||
git diff hellbender.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
|
||||
git diff birch.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Build
|
||||
env:
|
||||
scheme: hellbender
|
||||
file_to_build: hellbender.xcodeproj
|
||||
scheme: birch
|
||||
file_to_build: birch.xcodeproj
|
||||
filetype_parameter: project
|
||||
run: |
|
||||
xcodebuild clean build analyze -scheme "$scheme" -"$filetype_parameter" "$file_to_build" CODE_SIGNING_ALLOWED=NO | xcpretty && exit ${PIPESTATUS[0]}
|
||||
|
||||
2
.github/workflows/Xcode-unit-tests.yml
vendored
2
.github/workflows/Xcode-unit-tests.yml
vendored
@ -17,4 +17,4 @@ jobs:
|
||||
|
||||
- name: Run Unit Tests
|
||||
run: |
|
||||
xcodebuild test -project hellbender.xcodeproj -scheme hellbender -destination 'platform=iOS Simulator,name=iPhone 17 Pro' -only-testing:hellbenderTests -parallel-testing-enabled NO CODE_SIGNING_ALLOWED=NO | xcpretty && exit ${PIPESTATUS[0]}
|
||||
xcodebuild test -project birch.xcodeproj -scheme birch -destination 'platform=iOS Simulator,name=iPhone 17 Pro' -only-testing:birchTests -parallel-testing-enabled NO CODE_SIGNING_ALLOWED=NO | xcpretty && exit ${PIPESTATUS[0]}
|
||||
|
||||
24
.github/workflows/reproducible-build-check.yml
vendored
24
.github/workflows/reproducible-build-check.yml
vendored
@ -23,12 +23,12 @@ jobs:
|
||||
|
||||
- name: Build 1
|
||||
run: |
|
||||
DERIVED_DATA="/tmp/hellbender-build-1"
|
||||
DERIVED_DATA="/tmp/birch-build-1"
|
||||
rm -rf "$DERIVED_DATA"
|
||||
xcodebuild archive \
|
||||
-scheme hellbender \
|
||||
-project hellbender.xcodeproj \
|
||||
-archivePath "$DERIVED_DATA/hellbender.xcarchive" \
|
||||
-scheme birch \
|
||||
-project birch.xcodeproj \
|
||||
-archivePath "$DERIVED_DATA/birch.xcarchive" \
|
||||
-derivedDataPath "$DERIVED_DATA" \
|
||||
-configuration Release \
|
||||
CODE_SIGNING_ALLOWED=NO \
|
||||
@ -37,12 +37,12 @@ jobs:
|
||||
|
||||
- name: Build 2
|
||||
run: |
|
||||
DERIVED_DATA="/tmp/hellbender-build-2"
|
||||
DERIVED_DATA="/tmp/birch-build-2"
|
||||
rm -rf "$DERIVED_DATA"
|
||||
xcodebuild archive \
|
||||
-scheme hellbender \
|
||||
-project hellbender.xcodeproj \
|
||||
-archivePath "$DERIVED_DATA/hellbender.xcarchive" \
|
||||
-scheme birch \
|
||||
-project birch.xcodeproj \
|
||||
-archivePath "$DERIVED_DATA/birch.xcarchive" \
|
||||
-derivedDataPath "$DERIVED_DATA" \
|
||||
-configuration Release \
|
||||
CODE_SIGNING_ALLOWED=NO \
|
||||
@ -51,13 +51,13 @@ jobs:
|
||||
|
||||
- name: Normalize both builds
|
||||
run: |
|
||||
APP1="/tmp/hellbender-build-1/hellbender.xcarchive/Products/Applications/hellbender.app"
|
||||
APP2="/tmp/hellbender-build-2/hellbender.xcarchive/Products/Applications/hellbender.app"
|
||||
APP1="/tmp/birch-build-1/birch.xcarchive/Products/Applications/birch.app"
|
||||
APP2="/tmp/birch-build-2/birch.xcarchive/Products/Applications/birch.app"
|
||||
./scripts/normalize-app.sh "$APP1"
|
||||
./scripts/normalize-app.sh "$APP2"
|
||||
|
||||
- name: Compare builds
|
||||
run: |
|
||||
APP1="/tmp/hellbender-build-1/hellbender.xcarchive/Products/Applications/hellbender.app"
|
||||
APP2="/tmp/hellbender-build-2/hellbender.xcarchive/Products/Applications/hellbender.app"
|
||||
APP1="/tmp/birch-build-1/birch.xcarchive/Products/Applications/birch.app"
|
||||
APP2="/tmp/birch-build-2/birch.xcarchive/Products/Applications/birch.app"
|
||||
./scripts/compare-builds.sh "$APP1" "$APP2"
|
||||
|
||||
10
.gitignore
vendored
10
.gitignore
vendored
@ -33,3 +33,13 @@ Pods/
|
||||
# Xcode temporary build files
|
||||
build/
|
||||
DerivedData/
|
||||
|
||||
fastlane/report.xml
|
||||
fastlane/Preview.html
|
||||
fastlane/test_output/
|
||||
fastlane/README.md
|
||||
fastlane/screenshots/
|
||||
|
||||
# Bundler (Gemfile.lock is OK to commit; uncomment next line to ignore vendor dir if added later)
|
||||
vendor/bundle/
|
||||
.bundle/
|
||||
|
||||
@ -1,12 +1,14 @@
|
||||
// Hellbender.xcconfig — Target-level settings for the hellbender app target
|
||||
// Birch.xcconfig — Target-level settings for the birch app target
|
||||
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon
|
||||
ASSETCATALOG_COMPILER_ALTERNATE_APPICON_NAMES = AppIcon-Dark
|
||||
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor
|
||||
CODE_SIGN_STYLE = Automatic
|
||||
CURRENT_PROJECT_VERSION = 23
|
||||
DEVELOPMENT_ASSET_PATHS = "hellbender/Preview Content"
|
||||
DEVELOPMENT_ASSET_PATHS = "birch/Preview Content"
|
||||
GENERATE_INFOPLIST_FILE = NO
|
||||
INFOPLIST_FILE = hellbender/Info.plist
|
||||
INFOPLIST_FILE = birch/Info.plist
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 18.6
|
||||
LD_RUNPATH_SEARCH_PATHS = $(inherited) @executable_path/Frameworks
|
||||
MARKETING_VERSION = 0.1.2
|
||||
338
Gemfile.lock
Normal file
338
Gemfile.lock
Normal file
@ -0,0 +1,338 @@
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
CFPropertyList (3.0.8)
|
||||
abbrev (0.1.2)
|
||||
addressable (2.9.0)
|
||||
public_suffix (>= 2.0.2, < 8.0)
|
||||
artifactory (3.0.17)
|
||||
atomos (0.1.3)
|
||||
aws-eventstream (1.4.0)
|
||||
aws-partitions (1.1237.0)
|
||||
aws-sdk-core (3.244.0)
|
||||
aws-eventstream (~> 1, >= 1.3.0)
|
||||
aws-partitions (~> 1, >= 1.992.0)
|
||||
aws-sigv4 (~> 1.9)
|
||||
base64
|
||||
bigdecimal
|
||||
jmespath (~> 1, >= 1.6.1)
|
||||
logger
|
||||
aws-sdk-kms (1.123.0)
|
||||
aws-sdk-core (~> 3, >= 3.244.0)
|
||||
aws-sigv4 (~> 1.5)
|
||||
aws-sdk-s3 (1.219.0)
|
||||
aws-sdk-core (~> 3, >= 3.244.0)
|
||||
aws-sdk-kms (~> 1)
|
||||
aws-sigv4 (~> 1.5)
|
||||
aws-sigv4 (1.12.1)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
babosa (1.0.4)
|
||||
base64 (0.2.0)
|
||||
benchmark (0.5.0)
|
||||
bigdecimal (4.1.1)
|
||||
claide (1.1.0)
|
||||
colored (1.2)
|
||||
colored2 (3.1.2)
|
||||
commander (4.6.0)
|
||||
highline (~> 2.0.0)
|
||||
csv (3.3.5)
|
||||
declarative (0.0.20)
|
||||
digest-crc (0.7.0)
|
||||
rake (>= 12.0.0, < 14.0.0)
|
||||
domain_name (0.6.20240107)
|
||||
dotenv (2.8.1)
|
||||
emoji_regex (3.2.3)
|
||||
excon (0.112.0)
|
||||
faraday (1.10.5)
|
||||
faraday-em_http (~> 1.0)
|
||||
faraday-em_synchrony (~> 1.0)
|
||||
faraday-excon (~> 1.1)
|
||||
faraday-httpclient (~> 1.0)
|
||||
faraday-multipart (~> 1.0)
|
||||
faraday-net_http (~> 1.0)
|
||||
faraday-net_http_persistent (~> 1.0)
|
||||
faraday-patron (~> 1.0)
|
||||
faraday-rack (~> 1.0)
|
||||
faraday-retry (~> 1.0)
|
||||
ruby2_keywords (>= 0.0.4)
|
||||
faraday-cookie_jar (0.0.8)
|
||||
faraday (>= 0.8.0)
|
||||
http-cookie (>= 1.0.0)
|
||||
faraday-em_http (1.0.0)
|
||||
faraday-em_synchrony (1.0.1)
|
||||
faraday-excon (1.1.0)
|
||||
faraday-httpclient (1.0.1)
|
||||
faraday-multipart (1.2.0)
|
||||
multipart-post (~> 2.0)
|
||||
faraday-net_http (1.0.2)
|
||||
faraday-net_http_persistent (1.2.0)
|
||||
faraday-patron (1.0.0)
|
||||
faraday-rack (1.0.0)
|
||||
faraday-retry (1.0.4)
|
||||
faraday_middleware (1.2.1)
|
||||
faraday (~> 1.0)
|
||||
fastimage (2.4.1)
|
||||
fastlane (2.232.2)
|
||||
CFPropertyList (>= 2.3, < 4.0.0)
|
||||
abbrev (~> 0.1.2)
|
||||
addressable (>= 2.8, < 3.0.0)
|
||||
artifactory (~> 3.0)
|
||||
aws-sdk-s3 (~> 1.197)
|
||||
babosa (>= 1.0.3, < 2.0.0)
|
||||
base64 (~> 0.2.0)
|
||||
benchmark (>= 0.1.0)
|
||||
bundler (>= 1.17.3, < 5.0.0)
|
||||
colored (~> 1.2)
|
||||
commander (~> 4.6)
|
||||
csv (~> 3.3)
|
||||
dotenv (>= 2.1.1, < 3.0.0)
|
||||
emoji_regex (>= 0.1, < 4.0)
|
||||
excon (>= 0.71.0, < 1.0.0)
|
||||
faraday (~> 1.0)
|
||||
faraday-cookie_jar (~> 0.0.6)
|
||||
faraday_middleware (~> 1.0)
|
||||
fastimage (>= 2.1.0, < 3.0.0)
|
||||
fastlane-sirp (>= 1.0.0)
|
||||
gh_inspector (>= 1.1.2, < 2.0.0)
|
||||
google-apis-androidpublisher_v3 (~> 0.3)
|
||||
google-apis-playcustomapp_v1 (~> 0.1)
|
||||
google-cloud-env (>= 1.6.0, <= 2.1.1)
|
||||
google-cloud-storage (~> 1.31)
|
||||
highline (~> 2.0)
|
||||
http-cookie (~> 1.0.5)
|
||||
json (< 3.0.0)
|
||||
jwt (>= 2.1.0, < 3)
|
||||
logger (>= 1.6, < 2.0)
|
||||
mini_magick (>= 4.9.4, < 5.0.0)
|
||||
multipart-post (>= 2.0.0, < 3.0.0)
|
||||
mutex_m (~> 0.3.0)
|
||||
naturally (~> 2.2)
|
||||
nkf (~> 0.2.0)
|
||||
optparse (>= 0.1.1, < 1.0.0)
|
||||
ostruct (>= 0.1.0)
|
||||
plist (>= 3.1.0, < 4.0.0)
|
||||
rubyzip (>= 2.0.0, < 3.0.0)
|
||||
security (= 0.1.5)
|
||||
simctl (~> 1.6.3)
|
||||
terminal-notifier (>= 2.0.0, < 3.0.0)
|
||||
terminal-table (~> 3)
|
||||
tty-screen (>= 0.6.3, < 1.0.0)
|
||||
tty-spinner (>= 0.8.0, < 1.0.0)
|
||||
word_wrap (~> 1.0.0)
|
||||
xcodeproj (>= 1.13.0, < 2.0.0)
|
||||
xcpretty (~> 0.4.1)
|
||||
xcpretty-travis-formatter (>= 0.0.3, < 2.0.0)
|
||||
fastlane-sirp (1.0.0)
|
||||
sysrandom (~> 1.0)
|
||||
gh_inspector (1.1.3)
|
||||
google-apis-androidpublisher_v3 (0.98.0)
|
||||
google-apis-core (>= 0.15.0, < 2.a)
|
||||
google-apis-core (0.18.0)
|
||||
addressable (~> 2.5, >= 2.5.1)
|
||||
googleauth (~> 1.9)
|
||||
httpclient (>= 2.8.3, < 3.a)
|
||||
mini_mime (~> 1.0)
|
||||
mutex_m
|
||||
representable (~> 3.0)
|
||||
retriable (>= 2.0, < 4.a)
|
||||
google-apis-iamcredentials_v1 (0.26.0)
|
||||
google-apis-core (>= 0.15.0, < 2.a)
|
||||
google-apis-playcustomapp_v1 (0.17.0)
|
||||
google-apis-core (>= 0.15.0, < 2.a)
|
||||
google-apis-storage_v1 (0.61.0)
|
||||
google-apis-core (>= 0.15.0, < 2.a)
|
||||
google-cloud-core (1.8.0)
|
||||
google-cloud-env (>= 1.0, < 3.a)
|
||||
google-cloud-errors (~> 1.0)
|
||||
google-cloud-env (2.1.1)
|
||||
faraday (>= 1.0, < 3.a)
|
||||
google-cloud-errors (1.6.0)
|
||||
google-cloud-storage (1.59.0)
|
||||
addressable (~> 2.8)
|
||||
digest-crc (~> 0.4)
|
||||
google-apis-core (>= 0.18, < 2)
|
||||
google-apis-iamcredentials_v1 (~> 0.18)
|
||||
google-apis-storage_v1 (>= 0.42)
|
||||
google-cloud-core (~> 1.6)
|
||||
googleauth (~> 1.9)
|
||||
mini_mime (~> 1.0)
|
||||
googleauth (1.11.2)
|
||||
faraday (>= 1.0, < 3.a)
|
||||
google-cloud-env (~> 2.1)
|
||||
jwt (>= 1.4, < 3.0)
|
||||
multi_json (~> 1.11)
|
||||
os (>= 0.9, < 2.0)
|
||||
signet (>= 0.16, < 2.a)
|
||||
highline (2.0.3)
|
||||
http-cookie (1.0.8)
|
||||
domain_name (~> 0.5)
|
||||
httpclient (2.9.0)
|
||||
mutex_m
|
||||
jmespath (1.6.2)
|
||||
json (2.19.3)
|
||||
jwt (2.10.2)
|
||||
base64
|
||||
logger (1.7.0)
|
||||
mini_magick (4.13.2)
|
||||
mini_mime (1.1.5)
|
||||
multi_json (1.20.0)
|
||||
multipart-post (2.4.1)
|
||||
mutex_m (0.3.0)
|
||||
nanaimo (0.4.0)
|
||||
naturally (2.3.0)
|
||||
nkf (0.2.0)
|
||||
optparse (0.8.1)
|
||||
os (1.1.4)
|
||||
ostruct (0.6.3)
|
||||
plist (3.7.2)
|
||||
public_suffix (7.0.5)
|
||||
rake (13.3.1)
|
||||
representable (3.2.0)
|
||||
declarative (< 0.1.0)
|
||||
trailblazer-option (>= 0.1.1, < 0.2.0)
|
||||
uber (< 0.2.0)
|
||||
retriable (3.4.1)
|
||||
rexml (3.4.4)
|
||||
rouge (3.28.0)
|
||||
ruby2_keywords (0.0.5)
|
||||
rubyzip (2.4.1)
|
||||
security (0.1.5)
|
||||
signet (0.21.0)
|
||||
addressable (~> 2.8)
|
||||
faraday (>= 0.17.5, < 3.a)
|
||||
jwt (>= 1.5, < 4.0)
|
||||
multi_json (~> 1.10)
|
||||
simctl (1.6.10)
|
||||
CFPropertyList
|
||||
naturally
|
||||
sysrandom (1.0.5)
|
||||
terminal-notifier (2.0.0)
|
||||
terminal-table (3.0.2)
|
||||
unicode-display_width (>= 1.1.1, < 3)
|
||||
trailblazer-option (0.1.2)
|
||||
tty-cursor (0.7.1)
|
||||
tty-screen (0.8.2)
|
||||
tty-spinner (0.9.3)
|
||||
tty-cursor (~> 0.7)
|
||||
uber (0.1.0)
|
||||
unicode-display_width (2.6.0)
|
||||
word_wrap (1.0.0)
|
||||
xcodeproj (1.27.0)
|
||||
CFPropertyList (>= 2.3.3, < 4.0)
|
||||
atomos (~> 0.1.3)
|
||||
claide (>= 1.0.2, < 2.0)
|
||||
colored2 (~> 3.1)
|
||||
nanaimo (~> 0.4.0)
|
||||
rexml (>= 3.3.6, < 4.0)
|
||||
xcpretty (0.4.1)
|
||||
rouge (~> 3.28.0)
|
||||
xcpretty-travis-formatter (1.0.1)
|
||||
xcpretty (~> 0.2, >= 0.0.7)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
x86_64-darwin-24
|
||||
|
||||
DEPENDENCIES
|
||||
fastlane
|
||||
|
||||
CHECKSUMS
|
||||
CFPropertyList (3.0.8) sha256=2c99d0d980536d3d7ab252f7bd59ac8be50fbdd1ff487c98c949bb66bb114261
|
||||
abbrev (0.1.2) sha256=ad1b4eaaaed4cb722d5684d63949e4bde1d34f2a95e20db93aecfe7cbac74242
|
||||
addressable (2.9.0) sha256=7fdf6ac3660f7f4e867a0838be3f6cf722ace541dd97767fa42bc6cfa980c7af
|
||||
artifactory (3.0.17) sha256=3023d5c964c31674090d655a516f38ca75665c15084140c08b7f2841131af263
|
||||
atomos (0.1.3) sha256=7d43b22f2454a36bace5532d30785b06de3711399cb1c6bf932573eda536789f
|
||||
aws-eventstream (1.4.0) sha256=116bf85c436200d1060811e6f5d2d40c88f65448f2125bc77ffce5121e6e183b
|
||||
aws-partitions (1.1237.0) sha256=9b82f529b69ad83a8e4c5e123038924ed5e8f59bd6064a293ef20efc63364841
|
||||
aws-sdk-core (3.244.0) sha256=3e458c078b0c5bdee95bc370c3a483374b3224cf730c1f9f0faf849a5d9a18ea
|
||||
aws-sdk-kms (1.123.0) sha256=d405f37e82f8fa32045ca8980be266c0b45b37aaf2012afe0254321a1e811f20
|
||||
aws-sdk-s3 (1.219.0) sha256=6a755d7377978525758b3c29185ca6a10128ce2b07555ca37c4549de10c2f1c7
|
||||
aws-sigv4 (1.12.1) sha256=6973ff95cb0fd0dc58ba26e90e9510a2219525d07620c8babeb70ef831826c00
|
||||
babosa (1.0.4) sha256=18dea450f595462ed7cb80595abd76b2e535db8c91b350f6c4b3d73986c5bc99
|
||||
base64 (0.2.0) sha256=0f25e9b21a02a0cc0cea8ef92b2041035d39350946e8789c562b2d1a3da01507
|
||||
benchmark (0.5.0) sha256=465df122341aedcb81a2a24b4d3bd19b6c67c1530713fd533f3ff034e419236c
|
||||
bigdecimal (4.1.1) sha256=1c09efab961da45203c8316b0cdaec0ff391dfadb952dd459584b63ebf8054ca
|
||||
claide (1.1.0) sha256=6d3c5c089dde904d96aa30e73306d0d4bd444b1accb9b3125ce14a3c0183f82e
|
||||
colored (1.2) sha256=9d82b47ac589ce7f6cab64b1f194a2009e9fd00c326a5357321f44afab2c1d2c
|
||||
colored2 (3.1.2) sha256=b13c2bd7eeae2cf7356a62501d398e72fde78780bd26aec6a979578293c28b4a
|
||||
commander (4.6.0) sha256=7d1ddc3fccae60cc906b4131b916107e2ef0108858f485fdda30610c0f2913d9
|
||||
csv (3.3.5) sha256=6e5134ac3383ef728b7f02725d9872934f523cb40b961479f69cf3afa6c8e73f
|
||||
declarative (0.0.20) sha256=8021dd6cb17ab2b61233c56903d3f5a259c5cf43c80ff332d447d395b17d9ff9
|
||||
digest-crc (0.7.0) sha256=64adc23a26a241044cbe6732477ca1b3c281d79e2240bcff275a37a5a0d78c07
|
||||
domain_name (0.6.20240107) sha256=5f693b2215708476517479bf2b3802e49068ad82167bcd2286f899536a17d933
|
||||
dotenv (2.8.1) sha256=c5944793349ae03c432e1780a2ca929d60b88c7d14d52d630db0508c3a8a17d8
|
||||
emoji_regex (3.2.3) sha256=ecd8be856b7691406c6bf3bb3a5e55d6ed683ffab98b4aa531bb90e1ddcc564b
|
||||
excon (0.112.0) sha256=daf9ac3a4c2fc9aa48383a33da77ecb44fa395111e973084d5c52f6f214ae0f0
|
||||
faraday (1.10.5) sha256=b144f1d2b045652fa820b5f532723e1643cc28b93dae911d784e5c5f88e8f6ed
|
||||
faraday-cookie_jar (0.0.8) sha256=0140605823f8cc63c7028fccee486aaed8e54835c360cffc1f7c8c07c4299dbb
|
||||
faraday-em_http (1.0.0) sha256=7a3d4c7079789121054f57e08cd4ef7e40ad1549b63101f38c7093a9d6c59689
|
||||
faraday-em_synchrony (1.0.1) sha256=bf3ce45dcf543088d319ab051f80985ea6d294930635b7a0b966563179f81750
|
||||
faraday-excon (1.1.0) sha256=b055c842376734d7f74350fe8611542ae2000c5387348d9ba9708109d6e40940
|
||||
faraday-httpclient (1.0.1) sha256=4c8ff1f0973ff835be8d043ef16aaf54f47f25b7578f6d916deee8399a04d33b
|
||||
faraday-multipart (1.2.0) sha256=7d89a949693714176f612323ca13746a2ded204031a6ba528adee788694ef757
|
||||
faraday-net_http (1.0.2) sha256=63992efea42c925a20818cf3c0830947948541fdcf345842755510d266e4c682
|
||||
faraday-net_http_persistent (1.2.0) sha256=0b0cbc8f03dab943c3e1cc58d8b7beb142d9df068b39c718cd83e39260348335
|
||||
faraday-patron (1.0.0) sha256=dc2cd7b340bb3cc8e36bcb9e6e7eff43d134b6d526d5f3429c7a7680ddd38fa7
|
||||
faraday-rack (1.0.0) sha256=ef60ec969a2bb95b8dbf24400155aee64a00fc8ba6c6a4d3968562bcc92328c0
|
||||
faraday-retry (1.0.4) sha256=dc659233777fabf96c69c2ffe56c0a5d2c102af90321a42cc6c90157bcd716aa
|
||||
faraday_middleware (1.2.1) sha256=d45b78c8ee864c4783fbc276f845243d4a7918a67301c052647bacabec0529e9
|
||||
fastimage (2.4.1) sha256=c64bebd46b6fd8943ab70c1e6e85ff728f970f2e48f92ecd249b6bc3a540ad20
|
||||
fastlane (2.232.2) sha256=978689f60f0fc3d54699de86ef12be4eda9f5b52217c1798965257c390d2b112
|
||||
fastlane-sirp (1.0.0) sha256=66478f25bcd039ec02ccf65625373fca29646fa73d655eb533c915f106c5e641
|
||||
gh_inspector (1.1.3) sha256=04cca7171b87164e053aa43147971d3b7f500fcb58177698886b48a9fc4a1939
|
||||
google-apis-androidpublisher_v3 (0.98.0) sha256=094fb952419c1131c16c4dfa66e0c96e6a2fa33adbe266f614b84b22cbc8c5cb
|
||||
google-apis-core (0.18.0) sha256=96b057816feeeab448139ed5b5c78eab7fc2a9d8958f0fbc8217dedffad054ee
|
||||
google-apis-iamcredentials_v1 (0.26.0) sha256=3ff70a10a1d6cddf2554e95b7c5df2c26afdeaeb64100048a355194da19e48a3
|
||||
google-apis-playcustomapp_v1 (0.17.0) sha256=d5bc90b705f3f862bab4998086449b0abe704ee1685a84821daa90ca7fa95a78
|
||||
google-apis-storage_v1 (0.61.0) sha256=b330e599b58e6a01533c189525398d6dbdbaf101ffb0c60145940b57e1c982e8
|
||||
google-cloud-core (1.8.0) sha256=e572edcbf189cfcab16590628a516cec3f4f63454b730e59f0b36575120281cf
|
||||
google-cloud-env (2.1.1) sha256=cf4bb8c7d517ee1ea692baedf06e0b56ce68007549d8d5a66481aa9f97f46999
|
||||
google-cloud-errors (1.6.0) sha256=1da8476dd706ad04b9d32e3c4b90d07d3463b37d6407cb56d41342ea7647d0a1
|
||||
google-cloud-storage (1.59.0) sha256=b8c9a5661d775d65ccb279bb1d6be07fd8152576eb0146c2026bd023c4b186b9
|
||||
googleauth (1.11.2) sha256=7e6bacaeed7aea3dd66dcea985266839816af6633e9f5983c3c2e0e40a44731e
|
||||
highline (2.0.3) sha256=2ddd5c127d4692721486f91737307236fe005352d12a4202e26c48614f719479
|
||||
http-cookie (1.0.8) sha256=b14fe0445cf24bf9ae098633e9b8d42e4c07c3c1f700672b09fbfe32ffd41aa6
|
||||
httpclient (2.9.0) sha256=4b645958e494b2f86c2f8a2f304c959baa273a310e77a2931ddb986d83e498c8
|
||||
jmespath (1.6.2) sha256=238d774a58723d6c090494c8879b5e9918c19485f7e840f2c1c7532cf84ebcb1
|
||||
json (2.19.3) sha256=289b0bb53052a1fa8c34ab33cc750b659ba14a5c45f3fcf4b18762dc67c78646
|
||||
jwt (2.10.2) sha256=31e1ee46f7359883d5e622446969fe9c118c3da87a0b1dca765ce269c3a0c4f4
|
||||
logger (1.7.0) sha256=196edec7cc44b66cfb40f9755ce11b392f21f7967696af15d274dde7edff0203
|
||||
mini_magick (4.13.2) sha256=71d6258e0e8a3d04a9a0a09784d5d857b403a198a51dd4f882510435eb95ddd9
|
||||
mini_mime (1.1.5) sha256=8681b7e2e4215f2a159f9400b5816d85e9d8c6c6b491e96a12797e798f8bccef
|
||||
multi_json (1.20.0) sha256=c64106fae5114bd7f388d42d7b52ebb83d7726426d47a35ad5099e35bb923e41
|
||||
multipart-post (2.4.1) sha256=9872d03a8e552020ca096adadbf5e3cb1cd1cdd6acd3c161136b8a5737cdb4a8
|
||||
mutex_m (0.3.0) sha256=cfcb04ac16b69c4813777022fdceda24e9f798e48092a2b817eb4c0a782b0751
|
||||
nanaimo (0.4.0) sha256=faf069551bab17f15169c1f74a1c73c220657e71b6e900919897a10d991d0723
|
||||
naturally (2.3.0) sha256=459923cf76c2e6613048301742363200c3c7e4904c324097d54a67401e179e01
|
||||
nkf (0.2.0) sha256=fbc151bda025451f627fafdfcb3f4f13d0b22ae11f58c6d3a2939c76c5f5f126
|
||||
optparse (0.8.1) sha256=42bea10d53907ccff4f080a69991441d611fbf8733b60ed1ce9ee365ce03bd1a
|
||||
os (1.1.4) sha256=57816d6a334e7bd6aed048f4b0308226c5fb027433b67d90a9ab435f35108d3f
|
||||
ostruct (0.6.3) sha256=95a2ed4a4bd1d190784e666b47b2d3f078e4a9efda2fccf18f84ddc6538ed912
|
||||
plist (3.7.2) sha256=d37a4527cc1116064393df4b40e1dbbc94c65fa9ca2eec52edf9a13616718a42
|
||||
public_suffix (7.0.5) sha256=1a8bb08f1bbea19228d3bed6e5ed908d1cb4f7c2726d18bd9cadf60bc676f623
|
||||
rake (13.3.1) sha256=8c9e89d09f66a26a01264e7e3480ec0607f0c497a861ef16063604b1b08eb19c
|
||||
representable (3.2.0) sha256=cc29bf7eebc31653586849371a43ffe36c60b54b0a6365b5f7d95ec34d1ebace
|
||||
retriable (3.4.1) sha256=fb3f114b7d492121c158c01f3d5152b5a615c5b70d5877d0bc08c7ec3725c3bc
|
||||
rexml (3.4.4) sha256=19e0a2c3425dfbf2d4fc1189747bdb2f849b6c5e74180401b15734bc97b5d142
|
||||
rouge (3.28.0) sha256=0d6de482c7624000d92697772ab14e48dca35629f8ddf3f4b21c99183fd70e20
|
||||
ruby2_keywords (0.0.5) sha256=ffd13740c573b7301cf7a2e61fc857b2a8e3d3aff32545d6f8300d8bae10e3ef
|
||||
rubyzip (2.4.1) sha256=8577c88edc1fde8935eb91064c5cb1aef9ad5494b940cf19c775ee833e075615
|
||||
security (0.1.5) sha256=3a977a0eca7706e804c96db0dd9619e0a94969fe3aac9680fcfc2bf9b8a833b7
|
||||
signet (0.21.0) sha256=d617e9fbf24928280d39dcfefba9a0372d1c38187ffffd0a9283957a10a8cd5b
|
||||
simctl (1.6.10) sha256=b99077f4d13ad81eace9f86bf5ba4df1b0b893a4d1b368bd3ed59b5b27f9236b
|
||||
sysrandom (1.0.5) sha256=5ac1ac3c2ec64ef76ac91018059f541b7e8f437fbda1ccddb4f2c56a9ccf1e75
|
||||
terminal-notifier (2.0.0) sha256=7a0d2b2212ab9835c07f4b2e22a94cff64149dba1eed203c04835f7991078cea
|
||||
terminal-table (3.0.2) sha256=f951b6af5f3e00203fb290a669e0a85c5dd5b051b3b023392ccfd67ba5abae91
|
||||
trailblazer-option (0.1.2) sha256=20e4f12ea4e1f718c8007e7944ca21a329eee4eed9e0fa5dde6e8ad8ac4344a3
|
||||
tty-cursor (0.7.1) sha256=79534185e6a777888d88628b14b6a1fdf5154a603f285f80b1753e1908e0bf48
|
||||
tty-screen (0.8.2) sha256=c090652115beae764336c28802d633f204fb84da93c6a968aa5d8e319e819b50
|
||||
tty-spinner (0.9.3) sha256=0e036f047b4ffb61f2aa45f5a770ec00b4d04130531558a94bfc5b192b570542
|
||||
uber (0.1.0) sha256=5beeb407ff807b5db994f82fa9ee07cfceaa561dad8af20be880bc67eba935dc
|
||||
unicode-display_width (2.6.0) sha256=12279874bba6d5e4d2728cef814b19197dbb10d7a7837a869bab65da943b7f5a
|
||||
word_wrap (1.0.0) sha256=f556d4224c812e371000f12a6ee8102e0daa724a314c3f246afaad76d82accc7
|
||||
xcodeproj (1.27.0) sha256=8cc7a73b4505c227deab044dce118ede787041c702bc47636856a2e566f854d3
|
||||
xcpretty (0.4.1) sha256=b14c50e721f6589ee3d6f5353e2c2cfcd8541fa1ea16d6c602807dd7327f3892
|
||||
xcpretty-travis-formatter (1.0.1) sha256=aacc332f17cb7b2cba222994e2adc74223db88724fe76341483ad3098e232f93
|
||||
|
||||
BUNDLED WITH
|
||||
4.0.10
|
||||
123
README.md
123
README.md
@ -1,24 +1,29 @@
|
||||
<p align="center">
|
||||
<img src="https://hellbenderwallet.com/assets/AppIcon-og.png" alt="Hellbender" width="128" height="128" style="border-radius: 24px;" />
|
||||
<img src="https://birchwallet.app/assets/AppIcon-og.png" alt="Birch" width="128" height="128" style="border-radius: 24px;" />
|
||||
</p>
|
||||
|
||||
<h1 align="center">Hellbender</h1>
|
||||
<h1 align="center">Birch</h1>
|
||||
|
||||
<p align="center">
|
||||
<em>Travel to your private keys and leave your laptop at home.</em>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<img src="https://hellbenderwallet.com/assets/screenshots/welcome.png" alt="Welcome" width="150" />
|
||||
<img src="https://hellbenderwallet.com/assets/screenshots/transactions.png" alt="Transactions" width="150" />
|
||||
<img src="https://hellbenderwallet.com/assets/screenshots/multisig-config.png" alt="Multisig Config" width="150" />
|
||||
<img src="https://hellbenderwallet.com/assets/screenshots/import-descriptor.png" alt="Import Descriptor" width="150" />
|
||||
<img src="https://hellbenderwallet.com/assets/screenshots/review-wallet.png" alt="Review Wallet" width="150" />
|
||||
<img src="https://birchwallet.app/assets/screenshots/welcome.png" alt="Welcome" width="150" />
|
||||
<img src="https://birchwallet.app/assets/screenshots/wallet-setup.png" alt="Setup Choice" width="150" />
|
||||
<img src="https://birchwallet.app/assets/screenshots/multisig-config.png" alt="New Wallet Multisig Config" width="150" />
|
||||
<img src="https://birchwallet.app/assets/screenshots/cosigner-import.png" alt="Cosigner Import" width="150" />
|
||||
<img src="https://birchwallet.app/assets/screenshots/verify-wallet-top.png" alt="Verify Wallet" width="150" />
|
||||
<img src="https://birchwallet.app/assets/screenshots/verify-wallet-backup.png" alt="Backup PDF/QR" width="150" />
|
||||
<img src="https://birchwallet.app/assets/screenshots/transactions.png" alt="Transactions" width="150" />
|
||||
<img src="https://birchwallet.app/assets/screenshots/send.png" alt="Send" width="150" />
|
||||
<img src="https://birchwallet.app/assets/screenshots/receive.png" alt="Receive" width="150" />
|
||||
<img src="https://birchwallet.app/assets/screenshots/utxos.png" alt="UTXO" width="150" />
|
||||
</p>
|
||||
|
||||
---
|
||||
|
||||
Hellbender is an iOS Bitcoin multisig coordinator written in Swift. It operates as a **watch-only wallet** — private keys never touch your phone. Coordinate signing across air-gapped hardware wallets using animated QR codes, bringing cold storage security with mobile convenience.
|
||||
Birch is an iOS Bitcoin multisig coordinator written in Swift. It operates as a **watch-only wallet** — private keys never touch your phone. Coordinate signing across air-gapped hardware wallets using animated QR codes, bringing cold storage security with mobile convenience.
|
||||
|
||||
## Features
|
||||
|
||||
@ -60,7 +65,7 @@ All dependencies are managed via Swift Package Manager and resolve automatically
|
||||
git clone https://github.com/newtonick/hellbender-wallet.git
|
||||
cd hellbender-wallet
|
||||
```
|
||||
2. Open `hellbender.xcodeproj` in Xcode
|
||||
2. Open `birch.xcodeproj` in Xcode
|
||||
3. SPM dependencies resolve automatically on first open
|
||||
4. Build and run on a simulator or device
|
||||
|
||||
@ -72,7 +77,7 @@ GitHub Actions runs `xcodebuild clean build analyze` on every push and pull requ
|
||||
|
||||
### Reproducible Builds
|
||||
|
||||
Hellbender supports **functionally equivalent** reproducible builds. Given the same source code and Xcode version, two independent builds will produce the same compiled logic after normalization. Certain metadata bytes (Mach-O UUIDs, timestamps, build-machine identifiers) are expected to differ and are zeroed by the normalization step.
|
||||
Birch supports **functionally equivalent** reproducible builds. Given the same source code and Xcode version, two independent builds will produce the same compiled logic after normalization. Certain metadata bytes (Mach-O UUIDs, timestamps, build-machine identifiers) are expected to differ and are zeroed by the normalization step.
|
||||
|
||||
**What IS reproducible** (after normalization): all code-bearing sections, resources, and application logic.
|
||||
|
||||
@ -89,7 +94,7 @@ Hellbender supports **functionally equivalent** reproducible builds. Given the s
|
||||
./scripts/build-release.sh
|
||||
```
|
||||
|
||||
This creates an unsigned archive at `/tmp/hellbender-build/hellbender.xcarchive`.
|
||||
This creates an unsigned archive at `/tmp/birch-build/birch.xcarchive`.
|
||||
|
||||
#### Verifying two builds
|
||||
|
||||
@ -104,9 +109,101 @@ This creates an unsigned archive at `/tmp/hellbender-build/hellbender.xcarchive`
|
||||
|
||||
The comparison exits 0 if the builds are functionally equivalent, 1 if code differences are found.
|
||||
|
||||
## Generating Screenshots
|
||||
|
||||
Birch uses [`fastlane snapshot`](https://docs.fastlane.tools/actions/snapshot/) to generate marketing and App Store screenshots. A single UI test walks the app from Welcome through the main tabs, capturing every major screen on each configured device in both dark and light mode.
|
||||
|
||||
### One-time setup
|
||||
|
||||
1. Install [Bundler](https://bundler.io/) if you don't already have it:
|
||||
```bash
|
||||
gem install bundler
|
||||
```
|
||||
2. Install fastlane via the project `Gemfile`:
|
||||
```bash
|
||||
bundle install
|
||||
```
|
||||
3. Install [ImageMagick](https://imagemagick.org/) — used to composite iPhone 13 mini screenshots onto their bezel (frameit's bundled 13 mini frame has a pixel-misalignment bug that leaves a visible gap, so we bypass frameit for that device):
|
||||
```bash
|
||||
brew install imagemagick
|
||||
```
|
||||
4. Patch the installed fastlane gem with iPhone 16/17 device support (from
|
||||
[fastlane PR #29921](https://github.com/fastlane/fastlane/pull/29921)):
|
||||
```bash
|
||||
bundle exec ruby scripts/patch-frameit.rb
|
||||
```
|
||||
This is idempotent — safe to re-run. If you upgrade fastlane, the script
|
||||
aborts with a clear error so you can review the patch for the new version.
|
||||
|
||||
On a fresh machine, also download the device frame PNGs before running
|
||||
the patch (they live at `~/.fastlane/frameit/latest/` and are not in the repo):
|
||||
```bash
|
||||
bundle exec fastlane frameit download_frames
|
||||
```
|
||||
|
||||
5. Make sure the required simulators are downloaded. The screenshot lane targets:
|
||||
- **iPhone 17 Pro Max** (6.9")
|
||||
- **iPhone 17 Pro** (6.3")
|
||||
- **iPhone 11 Pro Max** (6.5")
|
||||
- **iPhone 13 mini** (5.4")
|
||||
|
||||
You can trigger a download by booting them once in Xcode (**Window → Devices and Simulators → Simulators → +**) or via the command line:
|
||||
```bash
|
||||
xcrun simctl list devices | grep -E "iPhone 17 Pro Max|iPhone 17 Pro|iPhone 11 Pro Max|iPhone 13 mini"
|
||||
```
|
||||
|
||||
### Running
|
||||
|
||||
From the repo root:
|
||||
|
||||
```bash
|
||||
bundle exec fastlane screenshots
|
||||
```
|
||||
|
||||
This runs the `screenshots` lane defined in [`fastlane/Fastfile`](fastlane/Fastfile), which:
|
||||
|
||||
1. Captures all 23 stops in **dark mode** first (the product's default aesthetic)
|
||||
2. Captures the same stops in **light mode**
|
||||
3. Moves iPhone 13 mini bare captures aside (frameit's bundled 13 mini frame has a pixel-misalignment bug)
|
||||
4. Runs `frameit` to composite device bezels onto iPhone 17 Pro Max, iPhone 17 Pro, and iPhone 11 Pro Max screenshots
|
||||
5. Moves every `*_framed.png` into a sibling `framed/` subfolder
|
||||
6. Composites iPhone 13 mini captures onto the bezel directly with ImageMagick (upscaling to 1086×2353 so the screenshot fully covers the frame's screen hole) and writes the result into the same `framed/` directory
|
||||
|
||||
Output lands in:
|
||||
|
||||
```
|
||||
fastlane/screenshots/
|
||||
├── dark/
|
||||
│ ├── en-US/
|
||||
│ │ ├── iPhone 17 Pro Max-01-Welcome.png
|
||||
│ │ ├── iPhone 17 Pro-01-Welcome.png
|
||||
│ │ └── ...
|
||||
│ └── framed/
|
||||
│ ├── iPhone 17 Pro Max-01-Welcome_framed.png
|
||||
│ ├── iPhone 17 Pro-01-Welcome_framed.png
|
||||
│ └── ...
|
||||
└── light/
|
||||
└── ...
|
||||
```
|
||||
|
||||
### How it works
|
||||
|
||||
- [`birchUITests/ScreenshotTests.swift`](birchUITests/ScreenshotTests.swift) is a dedicated XCUITest that walks the app. It reuses the existing `-UITesting` launch argument (defined in `birch/birchApp.swift`), which wipes `UserDefaults`/keychain and uses an in-memory SwiftData store so every run starts from a deterministic Welcome screen.
|
||||
- The test imports a real testnet4 1-of-2 `wsh(sortedmulti(...))` descriptor with live history, waits for Electrum sync, then visits each screen.
|
||||
- Dark/light mode is driven by the simulator's OS appearance (`xcrun simctl ui ... appearance`). The app's `RootView` follows the OS when the theme is set to `.system`, which it is by default after the `-UITesting` wipe, so no app-side toggle is required.
|
||||
- The device matrix, scheme, status bar override, and other `snapshot` options live in [`fastlane/Snapfile`](fastlane/Snapfile). Device destinations (simulator OS version), the frameit pass, and the custom 13 mini ImageMagick composite all live in [`fastlane/Fastfile`](fastlane/Fastfile).
|
||||
|
||||
### Customizing
|
||||
|
||||
- **Add/remove devices:** edit both the `devices([...])` array in `fastlane/Snapfile` and the `DEVICES` hash in `fastlane/Fastfile`.
|
||||
- **Change which screens are captured:** edit `testScreenshotTour` in `birchUITests/ScreenshotTests.swift` and add or remove `snapshot("NN-Name")` calls.
|
||||
- **Skip framing:** remove the `frameit(...)` lines and the ImageMagick composite block (steps 4–7) from `fastlane/Fastfile` if you only need the bare PNGs.
|
||||
|
||||
> **Known workaround** (contained in `fastlane/Fastfile`): `frameit` gem 2.232.2's bundled iPhone 13 Mini frame PNG has a ~3-pixel placement-offset bug that leaves a visible edge gap, so 13 mini is composited directly with ImageMagick instead. iPhone 16/17 device support is patched in via `scripts/patch-frameit.rb` (see setup step 4 above).
|
||||
|
||||
## Links
|
||||
|
||||
- **Website**: [hellbenderwallet.com](https://hellbenderwallet.com)
|
||||
- **Website**: [birchwallet.app](https://birchwallet.app)
|
||||
- **TestFlight Beta**: [Join the beta](https://testflight.apple.com/join/PuHVwJDJ)
|
||||
- **Author**: [newtonick](https://github.com/newtonick/hellbender-wallet/)
|
||||
|
||||
@ -114,5 +211,5 @@ The comparison exits 0 if the builds are functionally equivalent, 1 if code diff
|
||||
|
||||
MIT License — see [LICENSE](LICENSE) for details.
|
||||
|
||||
Hellbender's dependencies use permissive licenses compatible with MIT:
|
||||
Birch's dependencies use permissive licenses compatible with MIT:
|
||||
bdk-swift (MIT/Apache-2.0), URKit (BSD-2-Clause-Patent), URUI (BSD-2-Clause-Patent), Bbqr (Apache-2.0).
|
||||
|
||||
@ -20,54 +20,54 @@
|
||||
containerPortal = 3C9ACE1C2F5DED94009B00D0 /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 3C9ACE232F5DED94009B00D0;
|
||||
remoteInfo = hellbender;
|
||||
remoteInfo = birch;
|
||||
};
|
||||
3C9ACE3F2F5DED95009B00D0 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 3C9ACE1C2F5DED94009B00D0 /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 3C9ACE232F5DED94009B00D0;
|
||||
remoteInfo = hellbender;
|
||||
remoteInfo = birch;
|
||||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
3C9ACE242F5DED94009B00D0 /* hellbender.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = hellbender.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
3C9ACE342F5DED95009B00D0 /* hellbenderTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = hellbenderTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
3C9ACE3E2F5DED95009B00D0 /* hellbenderUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = hellbenderUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
3C9ACE242F5DED94009B00D0 /* birch.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = birch.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
3C9ACE342F5DED95009B00D0 /* birchTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = birchTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
3C9ACE3E2F5DED95009B00D0 /* birchUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = birchUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
CC0000010000000000000001 /* Base.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Base.xcconfig; sourceTree = "<group>"; };
|
||||
CC0000010000000000000002 /* Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = "<group>"; };
|
||||
CC0000010000000000000003 /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = "<group>"; };
|
||||
CC0000010000000000000004 /* Hellbender.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Hellbender.xcconfig; sourceTree = "<group>"; };
|
||||
CC0000010000000000000004 /* Birch.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Birch.xcconfig; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
|
||||
CC0000010000000000000006 /* Exceptions for "hellbender" folder in "hellbender" target */ = {
|
||||
CC0000010000000000000006 /* Exceptions for "birch" folder in "birch" target */ = {
|
||||
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
|
||||
membershipExceptions = (
|
||||
Info.plist,
|
||||
);
|
||||
target = 3C9ACE232F5DED94009B00D0 /* hellbender */;
|
||||
target = 3C9ACE232F5DED94009B00D0 /* birch */;
|
||||
};
|
||||
/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
|
||||
|
||||
/* Begin PBXFileSystemSynchronizedRootGroup section */
|
||||
3C9ACE262F5DED94009B00D0 /* hellbender */ = {
|
||||
3C9ACE262F5DED94009B00D0 /* birch */ = {
|
||||
isa = PBXFileSystemSynchronizedRootGroup;
|
||||
exceptions = (
|
||||
CC0000010000000000000006 /* Exceptions for "hellbender" folder in "hellbender" target */,
|
||||
CC0000010000000000000006 /* Exceptions for "birch" folder in "birch" target */,
|
||||
);
|
||||
path = hellbender;
|
||||
path = birch;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
3C9ACE372F5DED95009B00D0 /* hellbenderTests */ = {
|
||||
3C9ACE372F5DED95009B00D0 /* birchTests */ = {
|
||||
isa = PBXFileSystemSynchronizedRootGroup;
|
||||
path = hellbenderTests;
|
||||
path = birchTests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
3C9ACE412F5DED95009B00D0 /* hellbenderUITests */ = {
|
||||
3C9ACE412F5DED95009B00D0 /* birchUITests */ = {
|
||||
isa = PBXFileSystemSynchronizedRootGroup;
|
||||
path = hellbenderUITests;
|
||||
path = birchUITests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXFileSystemSynchronizedRootGroup section */
|
||||
@ -106,9 +106,9 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
CC0000010000000000000005 /* Config */,
|
||||
3C9ACE262F5DED94009B00D0 /* hellbender */,
|
||||
3C9ACE372F5DED95009B00D0 /* hellbenderTests */,
|
||||
3C9ACE412F5DED95009B00D0 /* hellbenderUITests */,
|
||||
3C9ACE262F5DED94009B00D0 /* birch */,
|
||||
3C9ACE372F5DED95009B00D0 /* birchTests */,
|
||||
3C9ACE412F5DED95009B00D0 /* birchUITests */,
|
||||
3C9ACE252F5DED94009B00D0 /* Products */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
@ -116,9 +116,9 @@
|
||||
3C9ACE252F5DED94009B00D0 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
3C9ACE242F5DED94009B00D0 /* hellbender.app */,
|
||||
3C9ACE342F5DED95009B00D0 /* hellbenderTests.xctest */,
|
||||
3C9ACE3E2F5DED95009B00D0 /* hellbenderUITests.xctest */,
|
||||
3C9ACE242F5DED94009B00D0 /* birch.app */,
|
||||
3C9ACE342F5DED95009B00D0 /* birchTests.xctest */,
|
||||
3C9ACE3E2F5DED95009B00D0 /* birchUITests.xctest */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
@ -128,7 +128,7 @@
|
||||
children = (
|
||||
CC0000010000000000000001 /* Base.xcconfig */,
|
||||
CC0000010000000000000002 /* Debug.xcconfig */,
|
||||
CC0000010000000000000004 /* Hellbender.xcconfig */,
|
||||
CC0000010000000000000004 /* Birch.xcconfig */,
|
||||
CC0000010000000000000003 /* Release.xcconfig */,
|
||||
);
|
||||
path = Config;
|
||||
@ -137,9 +137,9 @@
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
3C9ACE232F5DED94009B00D0 /* hellbender */ = {
|
||||
3C9ACE232F5DED94009B00D0 /* birch */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 3C9ACE482F5DED95009B00D0 /* Build configuration list for PBXNativeTarget "hellbender" */;
|
||||
buildConfigurationList = 3C9ACE482F5DED95009B00D0 /* Build configuration list for PBXNativeTarget "birch" */;
|
||||
buildPhases = (
|
||||
3C9ACE202F5DED94009B00D0 /* Sources */,
|
||||
3C9ACE212F5DED94009B00D0 /* Frameworks */,
|
||||
@ -150,9 +150,9 @@
|
||||
dependencies = (
|
||||
);
|
||||
fileSystemSynchronizedGroups = (
|
||||
3C9ACE262F5DED94009B00D0 /* hellbender */,
|
||||
3C9ACE262F5DED94009B00D0 /* birch */,
|
||||
);
|
||||
name = hellbender;
|
||||
name = birch;
|
||||
packageProductDependencies = (
|
||||
AA00000500000000000000D0 /* URKit */,
|
||||
AA00000800000000000000D0 /* URUI */,
|
||||
@ -160,13 +160,13 @@
|
||||
3C1E1C452F7B0D99002FDAE2 /* BitcoinDevKit */,
|
||||
3C1E1FA42F7B5F63002FDAE2 /* BitcoinDevKit */,
|
||||
);
|
||||
productName = hellbender;
|
||||
productReference = 3C9ACE242F5DED94009B00D0 /* hellbender.app */;
|
||||
productName = birch;
|
||||
productReference = 3C9ACE242F5DED94009B00D0 /* birch.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
3C9ACE332F5DED95009B00D0 /* hellbenderTests */ = {
|
||||
3C9ACE332F5DED95009B00D0 /* birchTests */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 3C9ACE4B2F5DED95009B00D0 /* Build configuration list for PBXNativeTarget "hellbenderTests" */;
|
||||
buildConfigurationList = 3C9ACE4B2F5DED95009B00D0 /* Build configuration list for PBXNativeTarget "birchTests" */;
|
||||
buildPhases = (
|
||||
3C9ACE302F5DED95009B00D0 /* Sources */,
|
||||
3C9ACE312F5DED95009B00D0 /* Frameworks */,
|
||||
@ -178,18 +178,18 @@
|
||||
3C9ACE362F5DED95009B00D0 /* PBXTargetDependency */,
|
||||
);
|
||||
fileSystemSynchronizedGroups = (
|
||||
3C9ACE372F5DED95009B00D0 /* hellbenderTests */,
|
||||
3C9ACE372F5DED95009B00D0 /* birchTests */,
|
||||
);
|
||||
name = hellbenderTests;
|
||||
name = birchTests;
|
||||
packageProductDependencies = (
|
||||
);
|
||||
productName = hellbenderTests;
|
||||
productReference = 3C9ACE342F5DED95009B00D0 /* hellbenderTests.xctest */;
|
||||
productName = birchTests;
|
||||
productReference = 3C9ACE342F5DED95009B00D0 /* birchTests.xctest */;
|
||||
productType = "com.apple.product-type.bundle.unit-test";
|
||||
};
|
||||
3C9ACE3D2F5DED95009B00D0 /* hellbenderUITests */ = {
|
||||
3C9ACE3D2F5DED95009B00D0 /* birchUITests */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 3C9ACE4E2F5DED95009B00D0 /* Build configuration list for PBXNativeTarget "hellbenderUITests" */;
|
||||
buildConfigurationList = 3C9ACE4E2F5DED95009B00D0 /* Build configuration list for PBXNativeTarget "birchUITests" */;
|
||||
buildPhases = (
|
||||
3C9ACE3A2F5DED95009B00D0 /* Sources */,
|
||||
3C9ACE3B2F5DED95009B00D0 /* Frameworks */,
|
||||
@ -201,13 +201,13 @@
|
||||
3C9ACE402F5DED95009B00D0 /* PBXTargetDependency */,
|
||||
);
|
||||
fileSystemSynchronizedGroups = (
|
||||
3C9ACE412F5DED95009B00D0 /* hellbenderUITests */,
|
||||
3C9ACE412F5DED95009B00D0 /* birchUITests */,
|
||||
);
|
||||
name = hellbenderUITests;
|
||||
name = birchUITests;
|
||||
packageProductDependencies = (
|
||||
);
|
||||
productName = hellbenderUITests;
|
||||
productReference = 3C9ACE3E2F5DED95009B00D0 /* hellbenderUITests.xctest */;
|
||||
productName = birchUITests;
|
||||
productReference = 3C9ACE3E2F5DED95009B00D0 /* birchUITests.xctest */;
|
||||
productType = "com.apple.product-type.bundle.ui-testing";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
@ -233,7 +233,7 @@
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 3C9ACE1F2F5DED94009B00D0 /* Build configuration list for PBXProject "hellbender" */;
|
||||
buildConfigurationList = 3C9ACE1F2F5DED94009B00D0 /* Build configuration list for PBXProject "birch" */;
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
@ -253,9 +253,9 @@
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
3C9ACE232F5DED94009B00D0 /* hellbender */,
|
||||
3C9ACE332F5DED95009B00D0 /* hellbenderTests */,
|
||||
3C9ACE3D2F5DED95009B00D0 /* hellbenderUITests */,
|
||||
3C9ACE232F5DED94009B00D0 /* birch */,
|
||||
3C9ACE332F5DED95009B00D0 /* birchTests */,
|
||||
3C9ACE3D2F5DED95009B00D0 /* birchUITests */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
@ -311,12 +311,12 @@
|
||||
/* Begin PBXTargetDependency section */
|
||||
3C9ACE362F5DED95009B00D0 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 3C9ACE232F5DED94009B00D0 /* hellbender */;
|
||||
target = 3C9ACE232F5DED94009B00D0 /* birch */;
|
||||
targetProxy = 3C9ACE352F5DED95009B00D0 /* PBXContainerItemProxy */;
|
||||
};
|
||||
3C9ACE402F5DED95009B00D0 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 3C9ACE232F5DED94009B00D0 /* hellbender */;
|
||||
target = 3C9ACE232F5DED94009B00D0 /* birch */;
|
||||
targetProxy = 3C9ACE3F2F5DED95009B00D0 /* PBXContainerItemProxy */;
|
||||
};
|
||||
/* End PBXTargetDependency section */
|
||||
@ -326,6 +326,7 @@
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = CC0000010000000000000002 /* Debug.xcconfig */;
|
||||
buildSettings = {
|
||||
DEVELOPMENT_TEAM = ZW85AH743B;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
@ -333,22 +334,25 @@
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = CC0000010000000000000003 /* Release.xcconfig */;
|
||||
buildSettings = {
|
||||
DEVELOPMENT_TEAM = ZW85AH743B;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
3C9ACE492F5DED95009B00D0 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = CC0000010000000000000004 /* Hellbender.xcconfig */;
|
||||
baseConfigurationReference = CC0000010000000000000004 /* Birch.xcconfig */;
|
||||
buildSettings = {
|
||||
CURRENT_PROJECT_VERSION = 24;
|
||||
CURRENT_PROJECT_VERSION = 26;
|
||||
MARKETING_VERSION = 0.2.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
3C9ACE4A2F5DED95009B00D0 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = CC0000010000000000000004 /* Hellbender.xcconfig */;
|
||||
baseConfigurationReference = CC0000010000000000000004 /* Birch.xcconfig */;
|
||||
buildSettings = {
|
||||
CURRENT_PROJECT_VERSION = 24;
|
||||
CURRENT_PROJECT_VERSION = 26;
|
||||
MARKETING_VERSION = 0.2.0;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
@ -366,7 +370,7 @@
|
||||
SWIFT_EMIT_LOC_STRINGS = NO;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/hellbender.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/hellbender";
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/birch.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/birch";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
@ -384,7 +388,7 @@
|
||||
SWIFT_EMIT_LOC_STRINGS = NO;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/hellbender.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/hellbender";
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/birch.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/birch";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
@ -400,7 +404,7 @@
|
||||
SWIFT_EMIT_LOC_STRINGS = NO;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
TEST_TARGET_NAME = hellbender;
|
||||
TEST_TARGET_NAME = birch;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
@ -416,14 +420,14 @@
|
||||
SWIFT_EMIT_LOC_STRINGS = NO;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
TEST_TARGET_NAME = hellbender;
|
||||
TEST_TARGET_NAME = birch;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
3C9ACE1F2F5DED94009B00D0 /* Build configuration list for PBXProject "hellbender" */ = {
|
||||
3C9ACE1F2F5DED94009B00D0 /* Build configuration list for PBXProject "birch" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
3C9ACE462F5DED95009B00D0 /* Debug */,
|
||||
@ -432,7 +436,7 @@
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
3C9ACE482F5DED95009B00D0 /* Build configuration list for PBXNativeTarget "hellbender" */ = {
|
||||
3C9ACE482F5DED95009B00D0 /* Build configuration list for PBXNativeTarget "birch" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
3C9ACE492F5DED95009B00D0 /* Debug */,
|
||||
@ -441,7 +445,7 @@
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
3C9ACE4B2F5DED95009B00D0 /* Build configuration list for PBXNativeTarget "hellbenderTests" */ = {
|
||||
3C9ACE4B2F5DED95009B00D0 /* Build configuration list for PBXNativeTarget "birchTests" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
3C9ACE4C2F5DED95009B00D0 /* Debug */,
|
||||
@ -450,7 +454,7 @@
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
3C9ACE4E2F5DED95009B00D0 /* Build configuration list for PBXNativeTarget "hellbenderUITests" */ = {
|
||||
3C9ACE4E2F5DED95009B00D0 /* Build configuration list for PBXNativeTarget "birchUITests" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
3C9ACE4F2F5DED95009B00D0 /* Debug */,
|
||||
@ -16,9 +16,9 @@
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "3C9ACE232F5DED94009B00D0"
|
||||
BuildableName = "hellbender.app"
|
||||
BlueprintName = "hellbender"
|
||||
ReferencedContainer = "container:hellbender.xcodeproj">
|
||||
BuildableName = "birch.app"
|
||||
BlueprintName = "birch"
|
||||
ReferencedContainer = "container:birch.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
@ -36,9 +36,9 @@
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "3C9ACE332F5DED95009B00D0"
|
||||
BuildableName = "hellbenderTests.xctest"
|
||||
BlueprintName = "hellbenderTests"
|
||||
ReferencedContainer = "container:hellbender.xcodeproj">
|
||||
BuildableName = "birchTests.xctest"
|
||||
BlueprintName = "birchTests"
|
||||
ReferencedContainer = "container:birch.xcodeproj">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
<TestableReference
|
||||
@ -47,9 +47,9 @@
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "3C9ACE3D2F5DED95009B00D0"
|
||||
BuildableName = "hellbenderUITests.xctest"
|
||||
BlueprintName = "hellbenderUITests"
|
||||
ReferencedContainer = "container:hellbender.xcodeproj">
|
||||
BuildableName = "birchUITests.xctest"
|
||||
BlueprintName = "birchUITests"
|
||||
ReferencedContainer = "container:birch.xcodeproj">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
@ -69,9 +69,9 @@
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "3C9ACE232F5DED94009B00D0"
|
||||
BuildableName = "hellbender.app"
|
||||
BlueprintName = "hellbender"
|
||||
ReferencedContainer = "container:hellbender.xcodeproj">
|
||||
BuildableName = "birch.app"
|
||||
BlueprintName = "birch"
|
||||
ReferencedContainer = "container:birch.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</LaunchAction>
|
||||
@ -86,9 +86,9 @@
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "3C9ACE232F5DED94009B00D0"
|
||||
BuildableName = "hellbender.app"
|
||||
BlueprintName = "hellbender"
|
||||
ReferencedContainer = "container:hellbender.xcodeproj">
|
||||
BuildableName = "birch.app"
|
||||
BlueprintName = "birch"
|
||||
ReferencedContainer = "container:birch.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
BIN
birch/Assets.xcassets/AppIcon-Dark.appiconset/AppIcon.png
Normal file
BIN
birch/Assets.xcassets/AppIcon-Dark.appiconset/AppIcon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.5 MiB |
14
birch/Assets.xcassets/AppIcon-Dark.appiconset/Contents.json
Normal file
14
birch/Assets.xcassets/AppIcon-Dark.appiconset/Contents.json
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "AppIcon.png",
|
||||
"idiom" : "universal",
|
||||
"platform" : "ios",
|
||||
"size" : "1024x1024"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
birch/Assets.xcassets/AppIcon.appiconset/AppIcon.png
Normal file
BIN
birch/Assets.xcassets/AppIcon.appiconset/AppIcon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.5 MiB |
14
birch/Assets.xcassets/AppIcon.appiconset/Contents.json
Normal file
14
birch/Assets.xcassets/AppIcon.appiconset/Contents.json
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "AppIcon.png",
|
||||
"idiom" : "universal",
|
||||
"platform" : "ios",
|
||||
"size" : "1024x1024"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
birch/Assets.xcassets/AppIconPreviewDark.imageset/AppIcon.png
vendored
Normal file
BIN
birch/Assets.xcassets/AppIconPreviewDark.imageset/AppIcon.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.5 MiB |
@ -6,12 +6,10 @@
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "AppIcon.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "AppIcon.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
@ -20,4 +18,4 @@
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
birch/Assets.xcassets/AppIconPreviewLight.imageset/AppIcon.png
vendored
Normal file
BIN
birch/Assets.xcassets/AppIconPreviewLight.imageset/AppIcon.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.5 MiB |
21
birch/Assets.xcassets/AppIconPreviewLight.imageset/Contents.json
vendored
Normal file
21
birch/Assets.xcassets/AppIconPreviewLight.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "AppIcon.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@ -3,7 +3,7 @@ import OSLog
|
||||
import SwiftData
|
||||
import SwiftUI
|
||||
|
||||
private let logger = Logger(subsystem: Bundle.main.bundleIdentifier ?? "hellbender", category: "AppLifecycle")
|
||||
private let logger = Logger(subsystem: Bundle.main.bundleIdentifier ?? "birch", category: "AppLifecycle")
|
||||
|
||||
struct ContentView: View {
|
||||
@Query private var wallets: [WalletProfile]
|
||||
@ -100,21 +100,26 @@ private struct PrivacyOverlayView: View {
|
||||
Color.hbBackground
|
||||
.ignoresSafeArea()
|
||||
|
||||
Image("WelcomeIcon")
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(width: 120, height: 120)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 28, style: .continuous))
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 28, style: .continuous)
|
||||
.stroke(Color.hbBackground, lineWidth: 24)
|
||||
.blur(radius: 12)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 28, style: .continuous))
|
||||
)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 28, style: .continuous)
|
||||
.strokeBorder(Color.hbBorder.opacity(0.5), lineWidth: 1)
|
||||
)
|
||||
VStack(spacing: 32) {
|
||||
Spacer()
|
||||
|
||||
ThemedAppIcon()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(width: 120, height: 120)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 28, style: .continuous))
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 28, style: .continuous)
|
||||
.stroke(Color.hbBackground, lineWidth: 24)
|
||||
.blur(radius: 12)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 28, style: .continuous))
|
||||
)
|
||||
|
||||
Text("Birch Wallet")
|
||||
.font(.hbDisplay(34))
|
||||
.foregroundStyle(Color.hbTextPrimary)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -5,9 +5,49 @@
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>Hellbender</string>
|
||||
<string>Birch Wallet</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIcons</key>
|
||||
<dict>
|
||||
<key>CFBundlePrimaryIcon</key>
|
||||
<dict>
|
||||
<key>CFBundleIconName</key>
|
||||
<string>AppIcon</string>
|
||||
</dict>
|
||||
<key>CFBundleAlternateIcons</key>
|
||||
<dict>
|
||||
<key>AppIcon-Dark</key>
|
||||
<dict>
|
||||
<key>CFBundleIconFiles</key>
|
||||
<array>
|
||||
<string>AppIcon-Dark</string>
|
||||
</array>
|
||||
<key>UIPrerenderedIcon</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>CFBundleIcons~ipad</key>
|
||||
<dict>
|
||||
<key>CFBundlePrimaryIcon</key>
|
||||
<dict>
|
||||
<key>CFBundleIconName</key>
|
||||
<string>AppIcon</string>
|
||||
</dict>
|
||||
<key>CFBundleAlternateIcons</key>
|
||||
<dict>
|
||||
<key>AppIcon-Dark</key>
|
||||
<dict>
|
||||
<key>CFBundleIconFiles</key>
|
||||
<array>
|
||||
<string>AppIcon-Dark</string>
|
||||
</array>
|
||||
<key>UIPrerenderedIcon</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
@ -23,9 +63,9 @@
|
||||
<key>LSApplicationCategoryType</key>
|
||||
<string>public.app-category.finance</string>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>Hellbender needs camera access to scan QR codes for importing cosigner keys and signed PSBTs from hardware wallets.</string>
|
||||
<string>Birch needs camera access to scan QR codes for importing cosigner keys and signed PSBTs from hardware wallets.</string>
|
||||
<key>NSFaceIDUsageDescription</key>
|
||||
<string>Hellbender uses Face ID to securely unlock your wallet.</string>
|
||||
<string>Birch uses Face ID to securely unlock your wallet.</string>
|
||||
<key>UIApplicationSceneManifest</key>
|
||||
<dict>
|
||||
<key>UIApplicationSupportsMultipleScenes</key>
|
||||
@ -21,7 +21,7 @@ enum FeeSource: String, CaseIterable {
|
||||
}
|
||||
}
|
||||
|
||||
private let logger = Logger(subsystem: Bundle.main.bundleIdentifier ?? "hellbender", category: "BitcoinService")
|
||||
private let logger = Logger(subsystem: Bundle.main.bundleIdentifier ?? "birch", category: "BitcoinService")
|
||||
|
||||
@Observable
|
||||
@MainActor
|
||||
@ -1333,12 +1333,18 @@ final class BitcoinService {
|
||||
) -> String {
|
||||
let chain = isChange ? "1" : "0"
|
||||
let coinType = network.coinType
|
||||
let isTestnet = network != .mainnet
|
||||
|
||||
let sorted = cosigners.sorted { $0.xpub < $1.xpub }
|
||||
let normalized = cosigners.map { cosigner -> (xpub: String, fingerprint: String, derivationPath: String) in
|
||||
let raw = cosigner.xpub.trimmingCharacters(in: CharacterSet(charactersIn: "/"))
|
||||
let xpub = URService.normalizeXpub(raw, isTestnet: isTestnet) ?? raw
|
||||
return (xpub: xpub, fingerprint: cosigner.fingerprint, derivationPath: cosigner.derivationPath)
|
||||
}
|
||||
|
||||
let sorted = normalized.sorted { $0.xpub < $1.xpub }
|
||||
|
||||
let keys = sorted.map { cosigner in
|
||||
let xpub = cosigner.xpub.trimmingCharacters(in: CharacterSet(charactersIn: "/"))
|
||||
return "[\(cosigner.fingerprint)/48'/\(coinType)'/0'/2']\(xpub)/\(chain)/*"
|
||||
"[\(cosigner.fingerprint)/48'/\(coinType)'/0'/2']\(cosigner.xpub)/\(chain)/*"
|
||||
}.joined(separator: ",")
|
||||
|
||||
return "wsh(sortedmulti(\(requiredSignatures),\(keys)))"
|
||||
@ -1351,11 +1357,20 @@ final class BitcoinService {
|
||||
network: BitcoinNetwork
|
||||
) -> String {
|
||||
let coinType = network.coinType
|
||||
let sorted = cosigners.sorted { $0.xpub < $1.xpub }
|
||||
let isTestnet = network != .mainnet
|
||||
|
||||
// Normalize each cosigner xpub to standard xpub/tpub format (BDK descriptor
|
||||
// parser does not accept SLIP132-tagged Vpub/Zpub/Ypub/Upub keys).
|
||||
let normalized = cosigners.map { cosigner -> (xpub: String, fingerprint: String, derivationPath: String) in
|
||||
let raw = cosigner.xpub.trimmingCharacters(in: CharacterSet(charactersIn: "/"))
|
||||
let xpub = URService.normalizeXpub(raw, isTestnet: isTestnet) ?? raw
|
||||
return (xpub: xpub, fingerprint: cosigner.fingerprint, derivationPath: cosigner.derivationPath)
|
||||
}
|
||||
|
||||
let sorted = normalized.sorted { $0.xpub < $1.xpub }
|
||||
|
||||
let keys = sorted.map { cosigner in
|
||||
let xpub = cosigner.xpub.trimmingCharacters(in: CharacterSet(charactersIn: "/"))
|
||||
return "[\(cosigner.fingerprint)/48'/\(coinType)'/0'/2']\(xpub)/<0;1>/*"
|
||||
"[\(cosigner.fingerprint)/48'/\(coinType)'/0'/2']\(cosigner.xpub)/<0;1>/*"
|
||||
}.joined(separator: ",")
|
||||
|
||||
let raw = "wsh(sortedmulti(\(requiredSignatures),\(keys)))"
|
||||
@ -2,7 +2,7 @@ import Foundation
|
||||
import Observation
|
||||
import OSLog
|
||||
|
||||
private let logger = Logger(subsystem: Bundle.main.bundleIdentifier ?? "hellbender", category: "FiatPriceService")
|
||||
private let logger = Logger(subsystem: Bundle.main.bundleIdentifier ?? "birch", category: "FiatPriceService")
|
||||
|
||||
enum FiatSource: String, CaseIterable {
|
||||
case zeus
|
||||
@ -2,7 +2,7 @@ import Foundation
|
||||
import OSLog
|
||||
import SwiftData
|
||||
|
||||
private let logger = Logger(subsystem: Bundle.main.bundleIdentifier ?? "hellbender", category: "LabelService")
|
||||
private let logger = Logger(subsystem: Bundle.main.bundleIdentifier ?? "birch", category: "LabelService")
|
||||
|
||||
/// Handles label propagation between transactions, UTXOs, and addresses.
|
||||
enum LabelService {
|
||||
@ -3,7 +3,7 @@ import Foundation
|
||||
enum Constants {
|
||||
// MARK: - App
|
||||
|
||||
static let appName = "Hellbender"
|
||||
static let appName = "Birch"
|
||||
static let defaultNetwork: BitcoinNetwork = .testnet4
|
||||
|
||||
// MARK: - BIP48 P2WSH
|
||||
@ -1,7 +1,15 @@
|
||||
import Foundation
|
||||
import Security
|
||||
|
||||
enum KeychainHelper {
|
||||
protocol KeychainStoring {
|
||||
@discardableResult
|
||||
static func save(_ data: Data, forKey key: String) -> Bool
|
||||
static func load(forKey key: String) -> Data?
|
||||
static func delete(forKey key: String)
|
||||
static func deleteAll()
|
||||
}
|
||||
|
||||
enum KeychainHelper: KeychainStoring {
|
||||
private static let service = Bundle.main.bundleIdentifier ?? "com.hellbender"
|
||||
|
||||
@discardableResult
|
||||
@ -8,7 +8,7 @@ enum LogExporter {
|
||||
static func collectLogs(hours: Double = 1) throws -> String {
|
||||
let store = try OSLogStore(scope: .currentProcessIdentifier)
|
||||
let cutoff = store.position(date: Date().addingTimeInterval(-hours * 3600))
|
||||
let subsystem = Bundle.main.bundleIdentifier ?? "hellbender"
|
||||
let subsystem = Bundle.main.bundleIdentifier ?? "birch"
|
||||
|
||||
let entries = try store.getEntries(at: cutoff, matching: NSPredicate(format: "subsystem == %@", subsystem))
|
||||
|
||||
@ -27,7 +27,7 @@ enum LogExporter {
|
||||
return "No log entries found in the last \(Int(hours)) hour(s)."
|
||||
}
|
||||
|
||||
let header = "Hellbender Logs — Exported \(formatter.string(from: Date()))\n"
|
||||
let header = "Birch Logs — Exported \(formatter.string(from: Date()))\n"
|
||||
+ "Entries: \(lines.count) (last \(Int(hours))h)\n"
|
||||
+ String(repeating: "─", count: 60) + "\n"
|
||||
|
||||
@ -4,7 +4,7 @@ import LocalAuthentication
|
||||
import OSLog
|
||||
import SwiftData
|
||||
|
||||
private let logger = Logger(subsystem: Bundle.main.bundleIdentifier ?? "hellbender", category: "AppLock")
|
||||
private let logger = Logger(subsystem: Bundle.main.bundleIdentifier ?? "birch", category: "AppLock")
|
||||
|
||||
@Observable
|
||||
@MainActor
|
||||
@ -18,6 +18,7 @@ final class AppLockViewModel {
|
||||
private(set) var failedAttempts: Int = 0
|
||||
private(set) var lockoutExpiry: Date?
|
||||
private var backgroundTime: Date?
|
||||
private let keychain: KeychainStoring.Type
|
||||
|
||||
// MARK: - Computed
|
||||
|
||||
@ -47,9 +48,10 @@ final class AppLockViewModel {
|
||||
|
||||
// MARK: - Init
|
||||
|
||||
init() {
|
||||
hasPIN = KeychainHelper.load(forKey: Constants.keychainPINHashKey) != nil
|
||||
if let data = KeychainHelper.load(forKey: Constants.keychainPINLengthKey),
|
||||
init(keychain: KeychainStoring.Type = KeychainHelper.self) {
|
||||
self.keychain = keychain
|
||||
hasPIN = keychain.load(forKey: Constants.keychainPINHashKey) != nil
|
||||
if let data = keychain.load(forKey: Constants.keychainPINLengthKey),
|
||||
let str = String(data: data, encoding: .utf8),
|
||||
let len = Int(str)
|
||||
{
|
||||
@ -102,7 +104,7 @@ final class AppLockViewModel {
|
||||
return false
|
||||
}
|
||||
|
||||
guard let storedHash = KeychainHelper.load(forKey: Constants.keychainPINHashKey) else {
|
||||
guard let storedHash = keychain.load(forKey: Constants.keychainPINHashKey) else {
|
||||
return false
|
||||
}
|
||||
|
||||
@ -138,8 +140,8 @@ final class AppLockViewModel {
|
||||
func setPIN(_ pin: String) {
|
||||
logger.info("PIN set (\(pin.count) digits)")
|
||||
let hash = hashPIN(pin)
|
||||
KeychainHelper.save(hash, forKey: Constants.keychainPINHashKey)
|
||||
KeychainHelper.save(Data("\(pin.count)".utf8), forKey: Constants.keychainPINLengthKey)
|
||||
keychain.save(hash, forKey: Constants.keychainPINHashKey)
|
||||
keychain.save(Data("\(pin.count)".utf8), forKey: Constants.keychainPINLengthKey)
|
||||
failedAttempts = 0
|
||||
persistFailedAttempts()
|
||||
lockoutExpiry = nil
|
||||
@ -150,10 +152,10 @@ final class AppLockViewModel {
|
||||
|
||||
func removePIN() {
|
||||
logger.info("PIN removed")
|
||||
KeychainHelper.delete(forKey: Constants.keychainPINHashKey)
|
||||
KeychainHelper.delete(forKey: Constants.keychainPINLengthKey)
|
||||
KeychainHelper.delete(forKey: Constants.keychainFailedAttemptsKey)
|
||||
KeychainHelper.delete(forKey: Constants.keychainLockoutExpiryKey)
|
||||
keychain.delete(forKey: Constants.keychainPINHashKey)
|
||||
keychain.delete(forKey: Constants.keychainPINLengthKey)
|
||||
keychain.delete(forKey: Constants.keychainFailedAttemptsKey)
|
||||
keychain.delete(forKey: Constants.keychainLockoutExpiryKey)
|
||||
failedAttempts = 0
|
||||
lockoutExpiry = nil
|
||||
hasPIN = false
|
||||
@ -170,6 +172,15 @@ final class AppLockViewModel {
|
||||
}
|
||||
|
||||
func handleForeground(timeout: Int) {
|
||||
hasPIN = keychain.load(forKey: Constants.keychainPINHashKey) != nil
|
||||
if let data = keychain.load(forKey: Constants.keychainPINLengthKey),
|
||||
let str = String(data: data, encoding: .utf8),
|
||||
let len = Int(str)
|
||||
{
|
||||
storedPINLength = len
|
||||
} else {
|
||||
storedPINLength = 6
|
||||
}
|
||||
if let bgTime = backgroundTime {
|
||||
let elapsed = Int(Date().timeIntervalSince(bgTime))
|
||||
if elapsed >= timeout {
|
||||
@ -208,7 +219,7 @@ final class AppLockViewModel {
|
||||
}
|
||||
|
||||
// Clear Keychain
|
||||
KeychainHelper.deleteAll()
|
||||
keychain.deleteAll()
|
||||
|
||||
// Reset BitcoinService
|
||||
BitcoinService.shared.unloadWallet()
|
||||
@ -245,13 +256,13 @@ final class AppLockViewModel {
|
||||
}
|
||||
|
||||
private func loadPersistedState() {
|
||||
if let data = KeychainHelper.load(forKey: Constants.keychainFailedAttemptsKey),
|
||||
if let data = keychain.load(forKey: Constants.keychainFailedAttemptsKey),
|
||||
let str = String(data: data, encoding: .utf8),
|
||||
let count = Int(str)
|
||||
{
|
||||
failedAttempts = count
|
||||
}
|
||||
if let data = KeychainHelper.load(forKey: Constants.keychainLockoutExpiryKey),
|
||||
if let data = keychain.load(forKey: Constants.keychainLockoutExpiryKey),
|
||||
let str = String(data: data, encoding: .utf8),
|
||||
let interval = Double(str)
|
||||
{
|
||||
@ -261,14 +272,14 @@ final class AppLockViewModel {
|
||||
}
|
||||
|
||||
private func persistFailedAttempts() {
|
||||
KeychainHelper.save(Data("\(failedAttempts)".utf8), forKey: Constants.keychainFailedAttemptsKey)
|
||||
keychain.save(Data("\(failedAttempts)".utf8), forKey: Constants.keychainFailedAttemptsKey)
|
||||
}
|
||||
|
||||
private func persistLockoutExpiry() {
|
||||
if let expiry = lockoutExpiry {
|
||||
KeychainHelper.save(Data("\(expiry.timeIntervalSince1970)".utf8), forKey: Constants.keychainLockoutExpiryKey)
|
||||
keychain.save(Data("\(expiry.timeIntervalSince1970)".utf8), forKey: Constants.keychainLockoutExpiryKey)
|
||||
} else {
|
||||
KeychainHelper.delete(forKey: Constants.keychainLockoutExpiryKey)
|
||||
keychain.delete(forKey: Constants.keychainLockoutExpiryKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -3,7 +3,7 @@ import Observation
|
||||
import OSLog
|
||||
import SwiftData
|
||||
|
||||
private let logger = Logger(subsystem: Bundle.main.bundleIdentifier ?? "hellbender", category: "BumpFeeViewModel")
|
||||
private let logger = Logger(subsystem: Bundle.main.bundleIdentifier ?? "birch", category: "BumpFeeViewModel")
|
||||
|
||||
@Observable
|
||||
@MainActor
|
||||
@ -3,7 +3,7 @@ import Observation
|
||||
import OSLog
|
||||
import SwiftData
|
||||
|
||||
private let logger = Logger(subsystem: Bundle.main.bundleIdentifier ?? "hellbender", category: "SendViewModel")
|
||||
private let logger = Logger(subsystem: Bundle.main.bundleIdentifier ?? "birch", category: "SendViewModel")
|
||||
|
||||
@Observable
|
||||
@MainActor
|
||||
@ -193,13 +193,17 @@ final class SetupWizardViewModel {
|
||||
func buildDescriptors() {
|
||||
guard allCosignersComplete else { return }
|
||||
|
||||
// Build key origin strings and sort by xpub (BIP67 lexicographic sort)
|
||||
// Build key origin strings — normalize to standard xpub/tpub format before
|
||||
// sorting so BIP67 ordering matches what's emitted in the descriptor.
|
||||
let isTestnet = network != .mainnet
|
||||
var keyEntries: [(origin: String, xpub: String, fingerprint: String, path: String, label: String, index: Int)] = []
|
||||
|
||||
for i in 0 ..< totalCosigners {
|
||||
let raw = cosignerXpubs[i].trimmingCharacters(in: CharacterSet(charactersIn: "/"))
|
||||
let normalized = URService.normalizeXpub(raw, isTestnet: isTestnet) ?? raw
|
||||
keyEntries.append((
|
||||
origin: "[\(cosignerFingerprints[i])/48'/\(network.coinType)'/0'/2']",
|
||||
xpub: cosignerXpubs[i],
|
||||
xpub: normalized,
|
||||
fingerprint: cosignerFingerprints[i],
|
||||
path: cosignerDerivationPaths[i],
|
||||
label: cosignerLabels[i],
|
||||
@ -211,12 +215,10 @@ final class SetupWizardViewModel {
|
||||
keyEntries.sort { $0.xpub < $1.xpub }
|
||||
|
||||
let externalKeys = keyEntries.map {
|
||||
let xpub = $0.xpub.trimmingCharacters(in: CharacterSet(charactersIn: "/"))
|
||||
return "\($0.origin)\(xpub)/0/*"
|
||||
"\($0.origin)\($0.xpub)/0/*"
|
||||
}.joined(separator: ",")
|
||||
let internalKeys = keyEntries.map {
|
||||
let xpub = $0.xpub.trimmingCharacters(in: CharacterSet(charactersIn: "/"))
|
||||
return "\($0.origin)\(xpub)/1/*"
|
||||
"\($0.origin)\($0.xpub)/1/*"
|
||||
}.joined(separator: ",")
|
||||
|
||||
externalDescriptor = "wsh(sortedmulti(\(requiredSignatures),\(externalKeys)))"
|
||||
@ -3,7 +3,7 @@ import Observation
|
||||
import OSLog
|
||||
import SwiftData
|
||||
|
||||
private let logger = Logger(subsystem: Bundle.main.bundleIdentifier ?? "hellbender", category: "WalletManager")
|
||||
private let logger = Logger(subsystem: Bundle.main.bundleIdentifier ?? "birch", category: "WalletManager")
|
||||
|
||||
@Observable
|
||||
@MainActor
|
||||
@ -3,7 +3,7 @@ import CoreImage.CIFilterBuiltins
|
||||
import OSLog
|
||||
import SwiftUI
|
||||
|
||||
private let logger = Logger(subsystem: Bundle.main.bundleIdentifier ?? "hellbender", category: "BBQRDisplayView")
|
||||
private let logger = Logger(subsystem: Bundle.main.bundleIdentifier ?? "birch", category: "BBQRDisplayView")
|
||||
|
||||
struct BBQRDisplayView: View {
|
||||
let data: Data
|
||||
@ -78,17 +78,17 @@ struct HBTheme {
|
||||
)
|
||||
|
||||
static let birchLight = HBTheme(
|
||||
background: Color(red: 0.929, green: 0.910, blue: 0.875),
|
||||
surface: Color(red: 0.851, green: 0.824, blue: 0.773),
|
||||
surfaceElevated: Color(red: 0.929, green: 0.910, blue: 0.875),
|
||||
border: Color(red: 0.769, green: 0.741, blue: 0.690),
|
||||
textPrimary: Color(red: 0.165, green: 0.145, blue: 0.125),
|
||||
textSecondary: Color(red: 0.420, green: 0.380, blue: 0.345),
|
||||
accent: Color(red: 0.769, green: 0.584, blue: 0.165),
|
||||
heroBackground: Color(red: 0.851, green: 0.824, blue: 0.773),
|
||||
success: Color(red: 0.353, green: 0.400, blue: 0.259),
|
||||
error: Color(red: 0.549, green: 0.188, blue: 0.125),
|
||||
secondaryAccent: Color(red: 0.353, green: 0.400, blue: 0.259),
|
||||
background: Color(red: 0.949, green: 0.933, blue: 0.902),
|
||||
surface: Color(red: 0.890, green: 0.867, blue: 0.827),
|
||||
surfaceElevated: Color(red: 0.949, green: 0.933, blue: 0.902),
|
||||
border: Color(red: 0.690, green: 0.663, blue: 0.616),
|
||||
textPrimary: Color(red: 0.137, green: 0.122, blue: 0.106),
|
||||
textSecondary: Color(red: 0.310, green: 0.282, blue: 0.251),
|
||||
accent: Color(red: 0.698, green: 0.525, blue: 0.133),
|
||||
heroBackground: Color(red: 0.890, green: 0.867, blue: 0.827),
|
||||
success: Color(red: 0.278, green: 0.373, blue: 0.224),
|
||||
error: Color(red: 0.600, green: 0.180, blue: 0.120),
|
||||
secondaryAccent: Color(red: 0.278, green: 0.373, blue: 0.224),
|
||||
colorScheme: .light
|
||||
)
|
||||
}
|
||||
@ -105,8 +105,8 @@ enum AppTheme: String, CaseIterable {
|
||||
var displayName: String {
|
||||
switch self {
|
||||
case .system: "System"
|
||||
case .dark: "Dark"
|
||||
case .light: "Light"
|
||||
case .dark: "Hellbender Dark"
|
||||
case .light: "Hellbender Light"
|
||||
case .birchDark: "Birch Dark"
|
||||
case .birchLight: "Birch Light"
|
||||
}
|
||||
@ -128,7 +128,7 @@ enum AppTheme: String, CaseIterable {
|
||||
@Observable
|
||||
final class ThemeManager {
|
||||
static let shared = ThemeManager()
|
||||
private(set) var theme: HBTheme = .dark
|
||||
private(set) var theme: HBTheme = .birchDark
|
||||
|
||||
private init() {
|
||||
let saved = UserDefaults.standard.string(forKey: Constants.themeKey) ?? AppTheme.system.rawValue
|
||||
@ -143,7 +143,7 @@ final class ThemeManager {
|
||||
/// Sets the displayed theme to the appropriate custom palette for the given OS color scheme.
|
||||
/// Only used when the System theme is selected — does not save to UserDefaults.
|
||||
func applySystemColorScheme(_ colorScheme: ColorScheme) {
|
||||
theme = colorScheme == .dark ? .dark : .light
|
||||
theme = colorScheme == .dark ? .birchDark : .birchLight
|
||||
}
|
||||
}
|
||||
|
||||
@ -288,6 +288,21 @@ extension View {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Themed App Icon
|
||||
|
||||
/// Renders the app-icon artwork that matches the current theme's light/dark appearance.
|
||||
/// Uses `AppIconPreviewLight` on light color schemes, `AppIconPreviewDark` on dark.
|
||||
/// The theme is applied via `.preferredColorScheme` at the root, so this works for
|
||||
/// all AppTheme cases (system, birch light/dark).
|
||||
struct ThemedAppIcon: View {
|
||||
@Environment(\.colorScheme) private var colorScheme
|
||||
|
||||
var body: some View {
|
||||
Image(colorScheme == .dark ? "AppIconPreviewDark" : "AppIconPreviewLight")
|
||||
.resizable()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Network Badge
|
||||
|
||||
struct NetworkBadge: View {
|
||||
@ -6,7 +6,7 @@ import SwiftUI
|
||||
import URKit
|
||||
import URUI
|
||||
|
||||
private let logger = Logger(subsystem: Bundle.main.bundleIdentifier ?? "hellbender", category: "URScannerSheet")
|
||||
private let logger = Logger(subsystem: Bundle.main.bundleIdentifier ?? "birch", category: "URScannerSheet")
|
||||
|
||||
struct URScannerSheet: View {
|
||||
let onResult: (AppURResult) -> Void
|
||||
@ -265,7 +265,7 @@ struct ConnectionStatusView: View {
|
||||
}
|
||||
|
||||
private func copyDebugInfo() {
|
||||
var lines = ["=== Hellbender Debug Info ==="]
|
||||
var lines = ["=== Birch Debug Info ==="]
|
||||
lines.append("Timestamp: \(ISO8601DateFormatter().string(from: Date()))")
|
||||
|
||||
// SwiftData wallet info
|
||||
@ -2,7 +2,7 @@ import OSLog
|
||||
import SwiftData
|
||||
import SwiftUI
|
||||
|
||||
private let logger = Logger(subsystem: Bundle.main.bundleIdentifier ?? "hellbender", category: "Navigation")
|
||||
private let logger = Logger(subsystem: Bundle.main.bundleIdentifier ?? "birch", category: "Navigation")
|
||||
|
||||
struct MainTabView: View {
|
||||
@State private var selectedTab = 0
|
||||
@ -243,7 +243,7 @@ struct BroadcastResultView: View {
|
||||
walletID: walletID
|
||||
)
|
||||
} catch {
|
||||
Logger(subsystem: Bundle.main.bundleIdentifier ?? "hellbender", category: "LabelService")
|
||||
Logger(subsystem: Bundle.main.bundleIdentifier ?? "birch", category: "LabelService")
|
||||
.error("Failed to propagate change label: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
@ -3,7 +3,7 @@ import OSLog
|
||||
import SwiftData
|
||||
import SwiftUI
|
||||
|
||||
private let logger = Logger(subsystem: Bundle.main.bundleIdentifier ?? "hellbender", category: "Settings")
|
||||
private let logger = Logger(subsystem: Bundle.main.bundleIdentifier ?? "birch", category: "Settings")
|
||||
|
||||
struct SettingsView: View {
|
||||
@Environment(\.modelContext) private var modelContext
|
||||
@ -24,11 +24,6 @@ struct SettingsView: View {
|
||||
// Security
|
||||
AppLockSettingsSection()
|
||||
|
||||
// Appearance
|
||||
Section("Appearance") {
|
||||
AppearanceSettingsRow()
|
||||
}
|
||||
|
||||
// Fee Estimation
|
||||
Section("Fee Estimation") {
|
||||
FeeSettingsRow()
|
||||
@ -39,6 +34,16 @@ struct SettingsView: View {
|
||||
FiatSettingsRow()
|
||||
}
|
||||
|
||||
// Appearance
|
||||
Section("Appearance") {
|
||||
AppearanceSettingsRow()
|
||||
}
|
||||
|
||||
// App Icon
|
||||
Section("App Icon") {
|
||||
AppIconSettingsRow()
|
||||
}
|
||||
|
||||
// About
|
||||
Section("About") {
|
||||
HStack {
|
||||
@ -92,6 +97,108 @@ private struct AppearanceSettingsRow: View {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - App Icon Settings
|
||||
|
||||
private enum AppIconOption: String, CaseIterable, Identifiable {
|
||||
case light
|
||||
case dark
|
||||
|
||||
var id: String {
|
||||
rawValue
|
||||
}
|
||||
|
||||
/// Name passed to `UIApplication.setAlternateIconName`; `nil` selects the primary icon.
|
||||
var alternateIconName: String? {
|
||||
switch self {
|
||||
case .light: nil
|
||||
case .dark: "AppIcon-Dark"
|
||||
}
|
||||
}
|
||||
|
||||
var displayName: String {
|
||||
switch self {
|
||||
case .light: "Light"
|
||||
case .dark: "Dark"
|
||||
}
|
||||
}
|
||||
|
||||
var previewAssetName: String {
|
||||
switch self {
|
||||
case .light: "AppIconPreviewLight"
|
||||
case .dark: "AppIconPreviewDark"
|
||||
}
|
||||
}
|
||||
|
||||
static var current: AppIconOption {
|
||||
UIApplication.shared.alternateIconName == "AppIcon-Dark" ? .dark : .light
|
||||
}
|
||||
}
|
||||
|
||||
private struct AppIconSettingsRow: View {
|
||||
@State private var selected: AppIconOption = .current
|
||||
|
||||
var body: some View {
|
||||
HStack(spacing: 12) {
|
||||
ForEach(AppIconOption.allCases) { option in
|
||||
AppIconTile(option: option, isSelected: selected == option) {
|
||||
select(option)
|
||||
}
|
||||
}
|
||||
}
|
||||
.listRowBackground(Color.hbSurface)
|
||||
}
|
||||
|
||||
private func select(_ option: AppIconOption) {
|
||||
guard selected != option else { return }
|
||||
UIApplication.shared.setAlternateIconName(option.alternateIconName) { error in
|
||||
Task { @MainActor in
|
||||
if let error {
|
||||
logger.error("Failed to set app icon: \(error.localizedDescription, privacy: .public)")
|
||||
} else {
|
||||
logger.info("App icon changed to \(option.displayName, privacy: .public)")
|
||||
selected = option
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct AppIconTile: View {
|
||||
let option: AppIconOption
|
||||
let isSelected: Bool
|
||||
let onTap: () -> Void
|
||||
|
||||
var body: some View {
|
||||
Button(action: onTap) {
|
||||
VStack(spacing: 8) {
|
||||
Image(option.previewAssetName)
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(width: 72, height: 72)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 16, style: .continuous))
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 16, style: .continuous)
|
||||
.stroke(isSelected ? Color.hbBitcoinOrange : Color.hbBorder, lineWidth: isSelected ? 3 : 1)
|
||||
)
|
||||
|
||||
HStack(spacing: 4) {
|
||||
if isSelected {
|
||||
Image(systemName: "checkmark.circle.fill")
|
||||
.font(.system(size: 12))
|
||||
.foregroundStyle(Color.hbBitcoinOrange)
|
||||
}
|
||||
Text(option.displayName)
|
||||
.font(.hbBody(13))
|
||||
.foregroundStyle(Color.hbTextPrimary)
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.vertical, 8)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Denomination Settings
|
||||
|
||||
private struct DenominationSettingsRow: View {
|
||||
@ -159,6 +266,7 @@ private struct FiatSettingsRow: View {
|
||||
}
|
||||
}
|
||||
.tint(Color.hbBitcoinOrange)
|
||||
.accessibilityIdentifier("showFiatPriceToggle")
|
||||
|
||||
if fiatEnabled {
|
||||
Picker("Price Source", selection: $fiatSourceRaw) {
|
||||
@ -2,7 +2,7 @@ import OSLog
|
||||
import SwiftData
|
||||
import SwiftUI
|
||||
|
||||
private let logger = Logger(subsystem: Bundle.main.bundleIdentifier ?? "hellbender", category: "UTXODetail")
|
||||
private let logger = Logger(subsystem: Bundle.main.bundleIdentifier ?? "birch", category: "UTXODetail")
|
||||
|
||||
struct UTXODetailView: View {
|
||||
let utxo: UTXOItem
|
||||
@ -3,7 +3,7 @@ import SwiftData
|
||||
import SwiftUI
|
||||
import URKit
|
||||
|
||||
private let logger = Logger(subsystem: Bundle.main.bundleIdentifier ?? "hellbender", category: "WalletInfo")
|
||||
private let logger = Logger(subsystem: Bundle.main.bundleIdentifier ?? "birch", category: "WalletInfo")
|
||||
|
||||
struct WalletInfoView: View {
|
||||
@Environment(\.modelContext) private var modelContext
|
||||
@ -687,10 +687,9 @@ private struct EditCosignersView: View {
|
||||
Text("Derivation Path")
|
||||
.font(.hbLabel())
|
||||
.foregroundStyle(Color.hbTextSecondary)
|
||||
TextField("m/48'/1'/0'/2'", text: $editableCosigners[index].derivationPath)
|
||||
Text(editableCosigners[index].derivationPath.isEmpty ? "–" : editableCosigners[index].derivationPath)
|
||||
.font(.hbMono())
|
||||
.textInputAutocapitalization(.never)
|
||||
.autocorrectionDisabled()
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.padding(12)
|
||||
.background(Color.hbSurfaceElevated)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 8))
|
||||
@ -2,7 +2,7 @@ import OSLog
|
||||
import SwiftData
|
||||
import SwiftUI
|
||||
|
||||
private let logger = Logger(subsystem: Bundle.main.bundleIdentifier ?? "hellbender", category: "TransactionDetailView")
|
||||
private let logger = Logger(subsystem: Bundle.main.bundleIdentifier ?? "birch", category: "TransactionDetailView")
|
||||
|
||||
struct TransactionDetailView: View {
|
||||
let transaction: TransactionItem
|
||||
@ -3,7 +3,7 @@ import SwiftData
|
||||
import SwiftUI
|
||||
import UniformTypeIdentifiers
|
||||
|
||||
private let logger = Logger(subsystem: Bundle.main.bundleIdentifier ?? "hellbender", category: "TransactionListView")
|
||||
private let logger = Logger(subsystem: Bundle.main.bundleIdentifier ?? "birch", category: "TransactionListView")
|
||||
|
||||
struct TransactionListView: View {
|
||||
@Query private var wallets: [WalletProfile]
|
||||
@ -231,6 +231,7 @@ struct TransactionListView: View {
|
||||
.frame(width: 44, height: 44)
|
||||
.contentShape(Rectangle())
|
||||
}
|
||||
.accessibilityIdentifier("walletMenu")
|
||||
|
||||
Spacer()
|
||||
|
||||
@ -255,6 +256,7 @@ struct TransactionListView: View {
|
||||
.strokeBorder(Color.hbBorder, lineWidth: 1)
|
||||
)
|
||||
}
|
||||
.accessibilityIdentifier("walletPicker")
|
||||
|
||||
Spacer()
|
||||
|
||||
@ -66,10 +66,9 @@ struct CosignerImportView: View {
|
||||
.font(.hbLabel())
|
||||
.foregroundStyle(Color.hbTextSecondary)
|
||||
|
||||
TextField("m/48'/1'/0'/2'", text: $viewModel.cosignerDerivationPaths[viewModel.currentCosignerIndex])
|
||||
Text(viewModel.cosignerDerivationPaths[viewModel.currentCosignerIndex].isEmpty ? "–" : viewModel.cosignerDerivationPaths[viewModel.currentCosignerIndex])
|
||||
.font(.hbMono())
|
||||
.textInputAutocapitalization(.never)
|
||||
.autocorrectionDisabled()
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.padding(12)
|
||||
.background(Color.hbSurfaceElevated)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 8))
|
||||
@ -149,7 +148,12 @@ struct CosignerImportView: View {
|
||||
.padding(.bottom, 32)
|
||||
}
|
||||
.padding(.top, 16)
|
||||
.contentShape(Rectangle())
|
||||
.onTapGesture {
|
||||
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
|
||||
}
|
||||
}
|
||||
.scrollDismissesKeyboard(.interactively)
|
||||
.sheet(isPresented: $showScanner) {
|
||||
URScannerSheet(expectedTypes: [.hdKey], onCancel: { showScanner = false }) { result in
|
||||
handleScanResult(result)
|
||||
@ -129,6 +129,10 @@ struct DescriptorImportView: View {
|
||||
.padding(.horizontal, 24)
|
||||
.padding(.bottom, 32)
|
||||
}
|
||||
.contentShape(Rectangle())
|
||||
.onTapGesture {
|
||||
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
|
||||
}
|
||||
}
|
||||
.scrollDismissesKeyboard(.interactively)
|
||||
.sheet(isPresented: $showScanner) {
|
||||
@ -7,7 +7,7 @@ struct WalletCreationChoiceView: View {
|
||||
VStack(spacing: 24) {
|
||||
Spacer()
|
||||
|
||||
Text("Set Up Wallet")
|
||||
Text("Wallet Setup")
|
||||
.font(.hbDisplay(28))
|
||||
.foregroundStyle(Color.hbTextPrimary)
|
||||
|
||||
@ -30,7 +30,7 @@ struct WalletCreationChoiceView: View {
|
||||
ChoiceCard(
|
||||
icon: "plus.circle.fill",
|
||||
title: "Create New Wallet",
|
||||
subtitle: "Set up M-of-N multisig by importing cosigner xpubs from one or more air-gapped signing devices",
|
||||
subtitle: "Setup M-of-N multisig by importing cosigner xpubs from one or more air-gapped signing devices",
|
||||
isSelected: viewModel.creationMode == .createNew
|
||||
) {
|
||||
viewModel.creationMode = .createNew
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user