Skip to content

Commit 514bbe9

Browse files
committed
Merge remote-tracking branch 'origin/ovi/trezor-bridge-transport' into ovi/trezor-ai-device-tests
2 parents ecfef5f + 0d757ef commit 514bbe9

150 files changed

Lines changed: 8017 additions & 2058 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.agents/commands/pr.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,9 @@ gh pr create --base $base --title "..." --body "..." [--draft]
197197
### 8b. Changelog Fragments
198198

199199
If the PR is user-facing, verify the branch adds exactly one changelog fragment under `changelog.d/next/` or `changelog.d/hotfix/`.
200-
Do not edit `CHANGELOG.md` in normal PRs and do not backfill PR numbers into fragments.
200+
Do not edit `CHANGELOG.md` in normal PRs.
201+
If the PR was created (not dry run), rename any new changelog fragment whose filename does not start with the actual PR number to `<PR_NUMBER>.<category>.md`, preserving the category (`added`, `changed`, `deprecated`, `removed`, `fixed`, or `security`).
202+
If any fragment was renamed, create a follow-up commit with message `chore: rename changelog fragment` and push it.
201203

202204
### 9. Output Summary
203205

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,4 @@ buildServer.json
2525
.ai/
2626
.codex/
2727
.claude/*.local*
28+
.claude/scheduled_tasks.lock

Bitkit.xcodeproj/project.pbxproj

Lines changed: 244 additions & 2 deletions
Large diffs are not rendered by default.

Bitkit/AppScene.swift

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,12 @@ struct AppScene: View {
2727
@StateObject private var transferTracking: TransferTrackingManager
2828
@StateObject private var channelDetails = ChannelDetailsViewModel.shared
2929
@StateObject private var migrations = MigrationsService.shared
30+
@StateObject private var languageManager = LanguageManager.shared
3031
@StateObject private var pubkyProfile = PubkyProfileManager()
3132
@StateObject private var contactsManager = ContactsManager()
3233
@State private var keyboardManager = KeyboardManager()
3334
@State private var trezorViewModel = TrezorViewModel()
35+
@State private var calculatorInputManager = CalculatorInputManager()
3436

3537
@State private var hideSplash = false
3638
@State private var removeSplash = false
@@ -54,6 +56,7 @@ struct AppScene: View {
5456

5557
// Run app data migrations before any feature code loads migrated state
5658
AppDataMigrations.run()
59+
PaykitFeatureFlags.enforceBuildAvailability()
5760

5861
_app = StateObject(wrappedValue: AppViewModel(sheetViewModel: sheetViewModel, navigationViewModel: navigationViewModel))
5962
_sheets = StateObject(wrappedValue: sheetViewModel)
@@ -146,15 +149,24 @@ struct AppScene: View {
146149
.environmentObject(contactsManager)
147150
.environment(keyboardManager)
148151
.environment(trezorViewModel)
152+
.environment(calculatorInputManager)
149153
.onChange(of: pubkyProfile.authState, initial: true) { _, authState in
150154
if authState == .authenticated, let pk = pubkyProfile.publicKey {
151-
Task { try? await contactsManager.loadContacts(for: pk) }
155+
Task {
156+
try? await contactsManager.loadContacts(for: pk)
157+
if !PaykitFeatureFlags.isUIEnabled, wallet.walletExists == true {
158+
await retryPendingPaykitEndpointRemoval()
159+
}
160+
}
152161
} else if authState == .idle {
153162
contactsManager.reset()
154163
}
155164
}
156165
.onReceive(contactsManager.$contacts) { contacts in
157-
guard wallet.walletExists == true, pubkyProfile.authState == .authenticated else { return }
166+
guard PaykitFeatureFlags.isUIEnabled,
167+
wallet.walletExists == true,
168+
pubkyProfile.authState == .authenticated
169+
else { return }
158170
let publicKeys = contacts.map(\.publicKey)
159171
Task {
160172
await PrivatePaykitService.shared.prepareSavedContacts(publicKeys, wallet: wallet)
@@ -394,6 +406,9 @@ struct AppScene: View {
394406
do {
395407
try await wallet.start()
396408
try await activity.syncLdkNodePayments()
409+
if !PaykitFeatureFlags.isUIEnabled {
410+
await retryPendingPaykitEndpointRemoval()
411+
}
397412

398413
// Start watching pending orders after wallet is ready
399414
await blocktank.startWatchingPendingOrders(transferViewModel: transfer)
@@ -555,6 +570,10 @@ struct AppScene: View {
555570
app.markAppStatusInit()
556571
BackupService.shared.startObservingBackups()
557572
Task {
573+
if !PaykitFeatureFlags.isUIEnabled {
574+
await retryPendingPaykitEndpointRemoval()
575+
}
576+
guard PaykitFeatureFlags.isUIEnabled else { return }
558577
await PrivatePaykitAddressReservationStore.shared.reconcileReservedIndexesWithLdk()
559578
await PrivatePaykitService.shared.prepareSavedContacts(
560579
contactsManager.contacts.map(\.publicKey),
@@ -587,20 +606,26 @@ struct AppScene: View {
587606
await clearDeliveredNotifications()
588607
await LightningService.shared.reconnectPeers()
589608
try? await wallet.sync()
590-
await PrivatePaykitService.shared.retryPendingEndpointRemoval(
591-
wallet: wallet,
592-
savedPublicKeys: contactsManager.contacts.map(\.publicKey)
593-
)
609+
await retryPendingPaykitEndpointRemoval()
594610
await wallet.refreshPublicPaykitEndpointsOnForeground()
595-
await PrivatePaykitService.shared.refreshSavedContactEndpoints(
596-
for: contactsManager.contacts.map(\.publicKey),
597-
wallet: wallet
598-
)
611+
if PaykitFeatureFlags.isUIEnabled {
612+
await PrivatePaykitService.shared.refreshSavedContactEndpoints(
613+
for: contactsManager.contacts.map(\.publicKey),
614+
wallet: wallet
615+
)
616+
}
599617
}
600618
}
601619
}
602620
}
603621

622+
private func retryPendingPaykitEndpointRemoval() async {
623+
await PrivatePaykitService.shared.retryPendingEndpointRemoval(
624+
wallet: wallet,
625+
savedPublicKeys: contactsManager.contacts.map(\.publicKey)
626+
)
627+
}
628+
604629
/// Removes all delivered notifications from Notification Center so the app can handle them when opened.
605630
private func clearDeliveredNotifications() async {
606631
let center = UNUserNotificationCenter.current()
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"images" : [
3+
{
4+
"filename" : "bitcoin-symbol.pdf",
5+
"idiom" : "universal"
6+
}
7+
],
8+
"info" : {
9+
"author" : "xcode",
10+
"version" : 1
11+
},
12+
"properties" : {
13+
"template-rendering-intent" : "template"
14+
}
15+
}
Binary file not shown.

Bitkit/Components/Activity/ActivityList.swift

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,18 @@ import BitkitCore
22
import SwiftUI
33

44
struct ActivityList: View {
5+
@AppStorage(PaykitFeatureFlags.uiEnabledKey) private var isPaykitUIEnabled = false
6+
57
@EnvironmentObject var activity: ActivityListViewModel
68
@EnvironmentObject var contactsManager: ContactsManager
79
@EnvironmentObject var feeEstimatesManager: FeeEstimatesManager
810

911
let viewType: ActivityViewType
1012

13+
private var isPaykitUIActive: Bool {
14+
PaykitFeatureFlags.isUIAvailable && isPaykitUIEnabled
15+
}
16+
1117
enum ActivityViewType {
1218
case all
1319
case lightning
@@ -31,7 +37,7 @@ struct ActivityList: View {
3137
ActivityRow(
3238
item: item,
3339
feeEstimates: feeEstimatesManager.estimates,
34-
contact: item.contact(in: contactsManager.contacts)
40+
contact: isPaykitUIActive ? item.contact(in: contactsManager.contacts) : nil
3541
)
3642
}
3743
.accessibilityIdentifier("Activity-\(index)")

Bitkit/Components/Header.swift

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import SwiftUI
22

33
struct Header: View {
4+
@Environment(CalculatorInputManager.self) private var calculatorInput
5+
6+
@AppStorage(PaykitFeatureFlags.uiEnabledKey) private var isPaykitUIEnabled = false
7+
48
@EnvironmentObject var app: AppViewModel
59
@EnvironmentObject var navigation: NavigationViewModel
610
@EnvironmentObject var pubkyProfile: PubkyProfileManager
@@ -10,27 +14,35 @@ struct Header: View {
1014
/// Binding to widgets edit state; used when showWidgetEditButton is true.
1115
@Binding var isEditingWidgets: Bool
1216

17+
private var isPaykitUIActive: Bool {
18+
PaykitFeatureFlags.isUIAvailable && isPaykitUIEnabled
19+
}
20+
1321
init(showWidgetEditButton: Bool = false, isEditingWidgets: Binding<Bool> = .constant(false)) {
1422
self.showWidgetEditButton = showWidgetEditButton
1523
_isEditingWidgets = isEditingWidgets
1624
}
1725

1826
var body: some View {
1927
HStack(alignment: .center, spacing: 0) {
20-
profileButton
28+
if isPaykitUIActive {
29+
profileButton
30+
}
2131

2232
Spacer()
2333

2434
HStack(alignment: .center, spacing: 8) {
2535
AppStatus(
2636
testID: "HeaderAppStatus",
2737
onPress: {
38+
if dismissCalculatorIfNeeded() { return }
2839
navigation.navigate(.appStatus)
2940
}
3041
)
3142

3243
if showWidgetEditButton {
3344
Button(action: {
45+
if dismissCalculatorIfNeeded() { return }
3446
isEditingWidgets.toggle()
3547
}) {
3648
Image(isEditingWidgets ? "check-mark" : "pencil")
@@ -45,6 +57,8 @@ struct Header: View {
4557
}
4658

4759
Button {
60+
if dismissCalculatorIfNeeded() { return }
61+
4862
withAnimation {
4963
app.showDrawer = true
5064
}
@@ -65,9 +79,10 @@ struct Header: View {
6579
.padding(.trailing, 10)
6680
}
6781

68-
@ViewBuilder
6982
private var profileButton: some View {
7083
Button {
84+
if dismissCalculatorIfNeeded() { return }
85+
7186
if pubkyProfile.isAuthenticated || pubkyProfile.cachedName != nil {
7287
navigation.navigate(.profile)
7388
} else if pubkyProfile.initializationErrorMessage != nil {
@@ -96,6 +111,12 @@ struct Header: View {
96111
.accessibilityIdentifier("ProfileButton")
97112
}
98113

114+
private func dismissCalculatorIfNeeded() -> Bool {
115+
guard calculatorInput.isPresented else { return false }
116+
calculatorInput.dismiss()
117+
return true
118+
}
119+
99120
@ViewBuilder
100121
private var profileAvatar: some View {
101122
if let imageUri = pubkyProfile.displayImageUri {

Bitkit/Components/NumberPad.swift

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,38 @@ enum NumberPadType {
88

99
struct NumberPad: View {
1010
let type: NumberPadType
11+
let decimalSeparator: String
1112
let errorKey: String?
13+
let onDeleteLongPress: (() -> Void)?
1214
let onPress: (String) -> Void
1315

14-
init(type: NumberPadType = .simple, errorKey: String? = nil, onPress: @escaping (String) -> Void) {
16+
static var contentHeight: CGFloat {
17+
buttonHeight * 4
18+
}
19+
20+
private static var buttonHeight: CGFloat {
21+
UIScreen.main.isSmall ? 65 : 44 + 34
22+
}
23+
24+
init(
25+
type: NumberPadType = .simple,
26+
decimalSeparator: String = ".",
27+
errorKey: String? = nil,
28+
onDeleteLongPress: (() -> Void)? = nil,
29+
onPress: @escaping (String) -> Void
30+
) {
1531
self.type = type
32+
self.decimalSeparator = decimalSeparator
1633
self.errorKey = errorKey
34+
self.onDeleteLongPress = onDeleteLongPress
1735
self.onPress = onPress
1836
}
1937

20-
private let buttonHeight: CGFloat = UIScreen.main.isSmall ? 65 : 44 + 34
2138
private let gridItems = Array(repeating: GridItem(.flexible(), spacing: 0), count: 3)
2239
private let numbers = ["1", "2", "3", "4", "5", "6", "7", "8", "9"]
40+
private var buttonHeight: CGFloat {
41+
Self.buttonHeight
42+
}
2343

2444
var body: some View {
2545
VStack(spacing: 0) {
@@ -59,7 +79,7 @@ struct NumberPad: View {
5979
}
6080
case .decimal:
6181
NumberPadButton(
62-
text: ".",
82+
text: decimalSeparator,
6383
height: buttonHeight,
6484
hasError: errorKey == ".",
6585
testID: "NDecimal"
@@ -98,6 +118,13 @@ struct NumberPad: View {
98118
.buttonStyle(NumberPadButtonStyle())
99119
.accessibilityIdentifier("NRemove")
100120
.frame(maxWidth: .infinity)
121+
.simultaneousGesture(
122+
LongPressGesture(minimumDuration: 0.45).onEnded { _ in
123+
guard let onDeleteLongPress else { return }
124+
Haptics.play(.buttonTap)
125+
onDeleteLongPress()
126+
}
127+
)
101128
}
102129
}
103130
}

Bitkit/Components/TabBar/TabBar.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
import SwiftUI
22

33
struct TabBar: View {
4+
@Environment(CalculatorInputManager.self) private var calculatorInput
45
@EnvironmentObject var navigation: NavigationViewModel
56
@EnvironmentObject var sheets: SheetViewModel
67
@EnvironmentObject var wallet: WalletViewModel
78

89
var shouldShow: Bool {
10+
if calculatorInput.isPresented { return false }
11+
912
let routesWithTabBar = Set<Route>([.activityList, .savingsWallet, .spendingWallet])
1013
if navigation.path.isEmpty { return true }
1114
return navigation.currentRoute.map { routesWithTabBar.contains($0) } ?? false
@@ -34,7 +37,7 @@ struct TabBar: View {
3437
.transition(.move(edge: .bottom))
3538
}
3639
}
37-
.animation(.easeInOut, value: shouldShow)
40+
.animation(.easeOut(duration: 0.14), value: shouldShow)
3841
.bottomSafeAreaPadding()
3942
}
4043

@@ -66,6 +69,7 @@ struct TabBar: View {
6669
.frame(maxWidth: .infinity, maxHeight: .infinity)
6770
.overlay {
6871
TabBar()
72+
.environment(CalculatorInputManager())
6973
.environmentObject(NavigationViewModel())
7074
.environmentObject(SheetViewModel())
7175
}
@@ -79,6 +83,7 @@ struct TabBar: View {
7983
.frame(maxWidth: .infinity, maxHeight: .infinity)
8084
.overlay {
8185
TabBar()
86+
.environment(CalculatorInputManager())
8287
.environmentObject(NavigationViewModel())
8388
.environmentObject(SheetViewModel())
8489
}

0 commit comments

Comments
 (0)