Rebrand Wallet to Birch Wallet (#28)

* step one

* progress

* minor theme enhancements

* update screenshot and icon links in README.md

* update site link

* swiftformat fixes
This commit is contained in:
Nick Klockenga 2026-04-30 21:00:59 -04:00 committed by GitHub
parent b75f93d950
commit 209750c4e5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
129 changed files with 441 additions and 264 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -1,29 +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/wallet-setup.png" alt="Setup Choice" width="150" />
<img src="https://hellbenderwallet.com/assets/screenshots/multisig-config.png" alt="New Wallet Multisig Config" width="150" />
<img src="https://hellbenderwallet.com/assets/screenshots/cosigner-import.png" alt="Cosigner Import" width="150" />
<img src="https://hellbenderwallet.com/assets/screenshots/verify-wallet-top.png" alt="Verify Wallet" width="150" />
<img src="https://hellbenderwallet.com/assets/screenshots/verify-wallet-backup.png" alt="Backup PDF/QR" width="150" />
<img src="https://hellbenderwallet.com/assets/screenshots/transactions.png" alt="Transactions" width="150" />
<img src="https://hellbenderwallet.com/assets/screenshots/send.png" alt="Send" width="150" />
<img src="https://hellbenderwallet.com/assets/screenshots/receive.png" alt="Receive" width="150" />
<img src="https://hellbenderwallet.com/assets/screenshots/utxos.png" alt="UTXO" 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
@ -65,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
@ -77,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.
@ -94,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
@ -111,7 +111,7 @@ The comparison exits 0 if the builds are functionally equivalent, 1 if code diff
## Generating Screenshots
Hellbender 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.
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
@ -188,7 +188,7 @@ fastlane/screenshots/
### How it works
- [`hellbenderUITests/ScreenshotTests.swift`](hellbenderUITests/ScreenshotTests.swift) is a dedicated XCUITest that walks the app. It reuses the existing `-UITesting` launch argument (defined in `hellbender/hellbenderApp.swift`), which wipes `UserDefaults`/keychain and uses an in-memory SwiftData store so every run starts from a deterministic Welcome screen.
- [`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).
@ -196,14 +196,14 @@ fastlane/screenshots/
### 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 `hellbenderUITests/ScreenshotTests.swift` and add or remove `snapshot("NN-Name")` calls.
- **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 47) 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/)
@ -211,5 +211,5 @@ fastlane/screenshots/
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).

View File

@ -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 = 25;
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 = 25;
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 */,

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

View File

@ -0,0 +1,14 @@
{
"images" : [
{
"filename" : "AppIcon.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

View File

@ -0,0 +1,14 @@
{
"images" : [
{
"filename" : "AppIcon.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

View 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
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -75,7 +75,7 @@ struct WalletVerifyView: View {
.foregroundStyle(Color.hbTextPrimary)
}
Text("The output descriptor is your **only** recovery path. If you lose Hellbender (phone dies, app deleted, data corrupted), the descriptor is the only thing needed to rebuild the wallet in any compatible coordinator (Sparrow, Nunchuk, etc.). Without it, you'd need to re-gather all cosigner xpubs and reconstruct the exact same configuration — which may not be possible.")
Text("The output descriptor is your **only** recovery path. If you lose Birch (phone dies, app deleted, data corrupted), the descriptor is the only thing needed to rebuild the wallet in any compatible coordinator (Sparrow, Nunchuk, etc.). Without it, you'd need to re-gather all cosigner xpubs and reconstruct the exact same configuration — which may not be possible.")
.font(.hbBody(13))
.foregroundStyle(Color.hbTextSecondary)
@ -140,7 +140,7 @@ struct WalletVerifyView: View {
.font(.hbHeadline)
.foregroundStyle(Color.hbTextPrimary)
Text("Verifying your first receive address confirms that Hellbender built the correct output descriptor and will generate the same addresses as your cosigner devices. If the addresses don't match, funds sent to this wallet could be unspendable.")
Text("Verifying your first receive address confirms that Birch built the correct output descriptor and will generate the same addresses as your cosigner devices. If the addresses don't match, funds sent to this wallet could be unspendable.")
.font(.hbBody(13))
.foregroundStyle(Color.hbTextSecondary)

View File

@ -8,8 +8,7 @@ struct WelcomeStepView: View {
Spacer()
// Icon
Image("WelcomeIcon")
.resizable()
ThemedAppIcon()
.aspectRatio(contentMode: .fit)
.frame(width: 120, height: 120)
.clipShape(RoundedRectangle(cornerRadius: 28, style: .continuous))
@ -19,13 +18,9 @@ struct WelcomeStepView: View {
.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: 12) {
Text("Hellbender Wallet")
Text("Birch Wallet")
.font(.hbDisplay(34))
.foregroundStyle(Color.hbTextPrimary)

Some files were not shown because too many files have changed in this diff Show More