feat: connect hardware flow#614
Conversation
|
Passphrase flow deferred to another PR, It touches too many files |
Greptile SummaryThis PR replaces the disabled
Confidence Score: 4/5The core pairing flow is well-structured and safe to merge; the noted issues are all quality-of-life items with no data-loss risk in the happy path. The wizard's phase transitions, BLE gating, inline pairing-code step, and teardown on dismiss are all correctly wired. Three areas deserve a follow-up: the BLE scan loop swallows Task cancellation during its 2-second sleep,
|
| Filename | Overview |
|---|---|
| Bitkit/ViewModels/Trezor/HwConnectViewModel.swift | New ViewModel backing the connect wizard — well-structured with protocol seam for testing, but the BLE scan loop swallows CancellationError during sleep (up to 2s delay on cancel) and onFinish() persists empty labels without validation. |
| Bitkit/Views/Sheets/HardwareConnectSheet.swift | Main sheet orchestrator for the wizard phases; Bluetooth gating, alert, and inline pairing code handling look correct. onFinished is wired inside .task{} which introduces a small async window before the closure is set. |
| BitkitTests/HwConnectViewModelTests.swift | 9 tests covering all major phase transitions with a FakeHwConnectService seam; waitUntil helper silently passes on timeout, making failures harder to diagnose. |
| Bitkit/Views/Sheets/HardwareConnect/HwPairCodeView.swift | Inline 6-digit pairing code entry with collapse-to-spinner animation; digit handling and submit guard look correct. |
| Bitkit/Views/Sheets/HardwareConnect/HwPairedView.swift | Paired step with editable label TextField and coin illustration; balance display uses Int(clamping:) which is safe at any realistic BTC value. |
| Bitkit/Views/Sheets/HardwareConnect/HwFoundView.swift | Found step showing discovered device with Connect/Cancel buttons; uses existing trezor-device asset and correctly surfaces connect errors inline. |
| Bitkit/Views/Sheets/HardwareConnect/HwSearchingView.swift | Searching step composing the ring animation with inline error display; straightforward and correct. |
| Bitkit/Views/Sheets/HardwareConnect/HwSearchingAnimation.swift | Counter-rotating ring/arrow animation; onAppear trigger and .id(viewModel.phase) reset are correct. |
| Bitkit/MainNavView.swift | Sheet registration updated from hardwareIntro to hardwareConnect; the guard preventing app-wide pairing sheet from replacing the wizard is well-placed. |
| Bitkit/ViewModels/SheetViewModel.swift | SheetID enum and computed property renamed from hardwareIntro to hardwareConnect; mechanical rename, no logic changes. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[Intro] -->|Continue + BT OK| B[Searching]
A -->|Continue + BT off/unauth| BTA[Bluetooth Alert]
BTA -->|Open Settings| SET[iOS Settings]
BTA -->|Cancel| A
B -->|Device found| C[Found]
B -->|Scan error| B
B -->|Cancel| DISMISS[Sheet dismissed]
C -->|Connect| D{connect task}
D -->|success| E[Paired]
D -->|device requests code| F[PairCode]
F -->|6 digits entered| G[submitPairingCode]
G -->|connect completes| E
D -->|error| C
C -->|Cancel| DISMISS
E -->|Finish| H[setDeviceLabel]
H --> I[hideSheet + nav.reset + Home]
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
A[Intro] -->|Continue + BT OK| B[Searching]
A -->|Continue + BT off/unauth| BTA[Bluetooth Alert]
BTA -->|Open Settings| SET[iOS Settings]
BTA -->|Cancel| A
B -->|Device found| C[Found]
B -->|Scan error| B
B -->|Cancel| DISMISS[Sheet dismissed]
C -->|Connect| D{connect task}
D -->|success| E[Paired]
D -->|device requests code| F[PairCode]
F -->|6 digits entered| G[submitPairingCode]
G -->|connect completes| E
D -->|error| C
C -->|Cancel| DISMISS
E -->|Finish| H[setDeviceLabel]
H --> I[hideSheet + nav.reset + Home]
Comments Outside Diff (3)
-
BitkitTests/HwConnectViewModelTests.swift, line 1188-1193 (link)waitUntilsilently passes on timeoutwaitUntilexits when the deadline passes without asserting, so if a phase transition never arrives, the nextXCTAssertEqualfires with a confusing inequality message rather than a clear timeout failure. Replace the helper with anXCTestExpectation-based approach, or add an explicitXCTFailat the end ofwaitUntilso the cause is immediately obvious. -
Bitkit/ViewModels/Trezor/HwConnectViewModel.swift, line 293-298 (link)Empty label is persisted without validation
onFinish()callsservice.setDeviceLabel(id:label:)unconditionally with whatever is inlabelInput. If the user clears the text field completely and taps Finish, the device gets renamed to an empty string, leaving it nameless in the UI. Consider adding a guard so that an emptylabelInputfalls back todeviceNamebefore persisting. -
Bitkit/Views/Sheets/HardwareConnectSheet.swift, line 823-829 (link)onFinishedclosure is set inside.task {}— could race with early navigation.task { viewModel.onFinished = { … } }executes asynchronously on the first runloop after the view appears. IfonIntroContinue()is somehow triggered before.taskhas run (e.g., a very fast Bluetooth state change),onFinishedwould beniland the flow would complete without dismissing the sheet or resetting navigation. SettingviewModel.onFinishedininitoronAppear(synchronously) avoids the window entirely.
Reviews (1): Last reviewed commit: "refactor: comments cleanup" | Re-trigger Greptile
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d366be64eb
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
…e sheet stops scanning immediately instead of idling up to 2s
…ngering previous connectedDevice can't be reported as success
Refs #589
This PR adds the guided Connect Hardware flow for pairing a Trezor as a watch-only wallet over Bluetooth, reachable from the Home hardware suggestion card and Hardware Wallets settings. It completes the "later PR" the intro sheet was deferring to and is an iOS port of bitkit-android's connect hardware flow (synonymdev/bitkit-android#1033).
Description
This builds on the hardware-wallet backend already on the branch (device scan/connect, known-device storage, funds label, name resolution, watch-only balances). Because iOS is Bluetooth-only, the Android USB attach handling, USB device identifiers, scan-before-connect, and runtime permission dialog are intentionally omitted; Bluetooth availability is handled via CoreBluetooth state and a Settings deep-link.
QA Notes
OBS: couldn't test some flows on emulator
Manual Tests
regression:back or Cancel from Intro / Searching / Found / Paired: dismisses the sheet instead of stepping back through completed steps.regression:reconnect of a known device that requests a pairing code while the wizard is closed: the app-wide Pair Device sheet still appears.Automated Checks
BitkitTests/HwConnectViewModelTests.swift(9 cases) cover Intro→Searching discovery, search failure surfacing, Connect→Paired, connect-failure return to Found, inline pairing-code navigation (and its guard when not connecting), connected-wallet balance update, label 50-char cap, and Finish persisting the label.xcodebuild buildsucceeded andHwConnectViewModelTestspassed 9/9 on the iPhone 16 simulator withONLY_ACTIVE_ARCH=YES; SwiftFormat reports no changes.Linked Issues/Tasks
Screenshot / Video
home.mov
hw-settings.mov