Skip to content

Commit 035cfcd

Browse files
committed
fix: gate paykit ui
1 parent cc1e08c commit 035cfcd

20 files changed

Lines changed: 425 additions & 65 deletions

Bitkit/AppScene.swift

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ struct AppScene: View {
5353

5454
// Run app data migrations before any feature code loads migrated state
5555
AppDataMigrations.run()
56+
PaykitFeatureFlags.enforceBuildAvailability()
5657

5758
_app = StateObject(wrappedValue: AppViewModel(sheetViewModel: sheetViewModel, navigationViewModel: navigationViewModel))
5859
_sheets = StateObject(wrappedValue: sheetViewModel)
@@ -146,13 +147,21 @@ struct AppScene: View {
146147
.environment(keyboardManager)
147148
.onChange(of: pubkyProfile.authState, initial: true) { _, authState in
148149
if authState == .authenticated, let pk = pubkyProfile.publicKey {
149-
Task { try? await contactsManager.loadContacts(for: pk) }
150+
Task {
151+
try? await contactsManager.loadContacts(for: pk)
152+
if !PaykitFeatureFlags.isUIEnabled, wallet.walletExists == true {
153+
await retryPendingPaykitEndpointRemoval()
154+
}
155+
}
150156
} else if authState == .idle {
151157
contactsManager.reset()
152158
}
153159
}
154160
.onReceive(contactsManager.$contacts) { contacts in
155-
guard wallet.walletExists == true, pubkyProfile.authState == .authenticated else { return }
161+
guard PaykitFeatureFlags.isUIEnabled,
162+
wallet.walletExists == true,
163+
pubkyProfile.authState == .authenticated
164+
else { return }
156165
let publicKeys = contacts.map(\.publicKey)
157166
Task {
158167
await PrivatePaykitService.shared.prepareSavedContacts(publicKeys, wallet: wallet)
@@ -383,6 +392,9 @@ struct AppScene: View {
383392
do {
384393
try await wallet.start()
385394
try await activity.syncLdkNodePayments()
395+
if !PaykitFeatureFlags.isUIEnabled {
396+
await retryPendingPaykitEndpointRemoval()
397+
}
386398

387399
// Start watching pending orders after wallet is ready
388400
await blocktank.startWatchingPendingOrders(transferViewModel: transfer)
@@ -544,6 +556,10 @@ struct AppScene: View {
544556
app.markAppStatusInit()
545557
BackupService.shared.startObservingBackups()
546558
Task {
559+
if !PaykitFeatureFlags.isUIEnabled {
560+
await retryPendingPaykitEndpointRemoval()
561+
}
562+
guard PaykitFeatureFlags.isUIEnabled else { return }
547563
await PrivatePaykitAddressReservationStore.shared.reconcileReservedIndexesWithLdk()
548564
await PrivatePaykitService.shared.prepareSavedContacts(
549565
contactsManager.contacts.map(\.publicKey),
@@ -576,20 +592,26 @@ struct AppScene: View {
576592
await clearDeliveredNotifications()
577593
await LightningService.shared.reconnectPeers()
578594
try? await wallet.sync()
579-
await PrivatePaykitService.shared.retryPendingEndpointRemoval(
580-
wallet: wallet,
581-
savedPublicKeys: contactsManager.contacts.map(\.publicKey)
582-
)
595+
await retryPendingPaykitEndpointRemoval()
583596
await wallet.refreshPublicPaykitEndpointsOnForeground()
584-
await PrivatePaykitService.shared.refreshSavedContactEndpoints(
585-
for: contactsManager.contacts.map(\.publicKey),
586-
wallet: wallet
587-
)
597+
if PaykitFeatureFlags.isUIEnabled {
598+
await PrivatePaykitService.shared.refreshSavedContactEndpoints(
599+
for: contactsManager.contacts.map(\.publicKey),
600+
wallet: wallet
601+
)
602+
}
588603
}
589604
}
590605
}
591606
}
592607

608+
private func retryPendingPaykitEndpointRemoval() async {
609+
await PrivatePaykitService.shared.retryPendingEndpointRemoval(
610+
wallet: wallet,
611+
savedPublicKeys: contactsManager.contacts.map(\.publicKey)
612+
)
613+
}
614+
593615
/// Removes all delivered notifications from Notification Center so the app can handle them when opened.
594616
private func clearDeliveredNotifications() async {
595617
let center = UNUserNotificationCenter.current()

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: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import SwiftUI
22

33
struct Header: View {
4+
@AppStorage(PaykitFeatureFlags.uiEnabledKey) private var isPaykitUIEnabled = false
5+
46
@EnvironmentObject var app: AppViewModel
57
@EnvironmentObject var navigation: NavigationViewModel
68
@EnvironmentObject var pubkyProfile: PubkyProfileManager
@@ -10,14 +12,20 @@ struct Header: View {
1012
/// Binding to widgets edit state; used when showWidgetEditButton is true.
1113
@Binding var isEditingWidgets: Bool
1214

15+
private var isPaykitUIActive: Bool {
16+
PaykitFeatureFlags.isUIAvailable && isPaykitUIEnabled
17+
}
18+
1319
init(showWidgetEditButton: Bool = false, isEditingWidgets: Binding<Bool> = .constant(false)) {
1420
self.showWidgetEditButton = showWidgetEditButton
1521
_isEditingWidgets = isEditingWidgets
1622
}
1723

1824
var body: some View {
1925
HStack(alignment: .center, spacing: 0) {
20-
profileButton
26+
if isPaykitUIActive {
27+
profileButton
28+
}
2129

2230
Spacer()
2331

@@ -65,7 +73,6 @@ struct Header: View {
6573
.padding(.trailing, 10)
6674
}
6775

68-
@ViewBuilder
6976
private var profileButton: some View {
7077
Button {
7178
if pubkyProfile.isAuthenticated || pubkyProfile.cachedName != nil {

Bitkit/Components/Widgets/Suggestions.swift

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,8 +172,13 @@ struct Suggestions: View {
172172
@EnvironmentObject var wallet: WalletViewModel
173173
@EnvironmentObject var pubkyProfile: PubkyProfileManager
174174

175+
@AppStorage(PaykitFeatureFlags.uiEnabledKey) private var isPaykitUIEnabled = false
175176
@State private var showShareSheet = false
176177

178+
private var isPaykitUIActive: Bool {
179+
PaykitFeatureFlags.isUIAvailable && isPaykitUIEnabled
180+
}
181+
177182
/// Which suggestion cards to show.
178183
/// Up to 4 for current wallet state, in priority order; completed and dismissed are skipped.
179184
/// In widget preview: 2 fixed cards.
@@ -183,6 +188,7 @@ struct Suggestions: View {
183188
settings: SettingsViewModel,
184189
suggestionsManager: SuggestionsManager,
185190
pubkyProfile: PubkyProfileManager? = nil,
191+
isPaykitUIEnabled: Bool = PaykitFeatureFlags.isUIEnabled,
186192
isPreview: Bool = false
187193
) -> [SuggestionCardData] {
188194
if isPreview {
@@ -199,6 +205,7 @@ struct Suggestions: View {
199205
var result: [SuggestionCardData] = []
200206
for id in orderedIds {
201207
guard let card = cardsById[id] else { continue }
208+
if !isPaykitUIEnabled, card.isPaykitCard { continue }
202209
if isCardCompleted(card, app: app, settings: settings, pubkyProfile: pubkyProfile) { continue }
203210
if suggestionsManager.isDismissed(card.id) { continue }
204211
result.append(card)
@@ -229,6 +236,7 @@ struct Suggestions: View {
229236
settings: settings,
230237
suggestionsManager: suggestionsManager,
231238
pubkyProfile: pubkyProfile,
239+
isPaykitUIEnabled: isPaykitUIActive,
232240
isPreview: isPreview
233241
)
234242
}
@@ -273,6 +281,7 @@ struct Suggestions: View {
273281
}
274282

275283
private func onItemTap(_ card: SuggestionCardData) {
284+
if card.isPaykitCard, !PaykitFeatureFlags.isUIEnabled { return }
276285
var route: Route?
277286

278287
switch card.action {
@@ -322,6 +331,12 @@ struct Suggestions: View {
322331
}
323332
}
324333

334+
private extension SuggestionCardData {
335+
var isPaykitCard: Bool {
336+
action == .profile
337+
}
338+
}
339+
325340
#Preview {
326341
VStack {
327342
Suggestions()

Bitkit/MainNavView.swift

Lines changed: 58 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import SwiftUI
22

33
struct MainNavView: View {
4+
@AppStorage(PaykitFeatureFlags.uiEnabledKey) private var isPaykitUIEnabled = false
5+
46
@EnvironmentObject private var app: AppViewModel
57
@Environment(CameraManager.self) private var cameraManager
68
@EnvironmentObject private var contactsManager: ContactsManager
@@ -16,6 +18,10 @@ struct MainNavView: View {
1618
@State private var showClipboardAlert = false
1719
@State private var clipboardUri: String?
1820

21+
private var isPaykitUIActive: Bool {
22+
PaykitFeatureFlags.isUIAvailable && isPaykitUIEnabled
23+
}
24+
1925
// Delay constants for clipboard processing
2026
private static let nodeReadyDelayNanoseconds: UInt64 = 500_000_000 // 0.5 seconds
2127
private static let statePropagationDelayNanoseconds: UInt64 = 500_000_000 // 0.5 seconds
@@ -247,6 +253,15 @@ struct MainNavView: View {
247253
Logger.info("Received deeplink: \(url.absoluteString)")
248254

249255
if let callback = PubkyRingAuthCallback.parse(url: url) {
256+
guard isPaykitUIActive else {
257+
app.toast(
258+
type: .error,
259+
title: t("profile__auth_error_title"),
260+
description: t("other__qr_error_text")
261+
)
262+
return
263+
}
264+
250265
let handlingResult = await pubkyProfile.handleAuthCallback(callback)
251266

252267
switch handlingResult {
@@ -368,7 +383,9 @@ struct MainNavView: View {
368383

369384
// Profile & Contacts
370385
case .contacts:
371-
if let initializationErrorMessage = pubkyProfile.initializationErrorMessage {
386+
if !isPaykitUIActive {
387+
ComingSoonScreen()
388+
} else if let initializationErrorMessage = pubkyProfile.initializationErrorMessage {
372389
pubkyInitializationErrorView(message: initializationErrorMessage)
373390
} else if app.hasSeenContactsIntro || !contactsManager.contacts.isEmpty {
374391
if !pubkyProfile.isInitialized {
@@ -383,12 +400,18 @@ struct MainNavView: View {
383400
} else {
384401
ContactsIntroView()
385402
}
386-
case .contactsIntro: ContactsIntroView()
387-
case let .contactDetail(publicKey): ContactDetailView(publicKey: publicKey)
388-
case let .contactActivity(publicKey): ContactActivityView(publicKey: publicKey)
389-
case let .assignActivityContact(activityId): AssignActivityContactView(activityId: activityId)
403+
case .contactsIntro:
404+
if isPaykitUIActive { ContactsIntroView() } else { ComingSoonScreen() }
405+
case let .contactDetail(publicKey):
406+
if isPaykitUIActive { ContactDetailView(publicKey: publicKey) } else { paykitDisabledRedirectView }
407+
case let .contactActivity(publicKey):
408+
if isPaykitUIActive { ContactActivityView(publicKey: publicKey) } else { paykitDisabledRedirectView }
409+
case let .assignActivityContact(activityId):
410+
if isPaykitUIActive { AssignActivityContactView(activityId: activityId) } else { paykitDisabledRedirectView }
390411
case .contactImportOverview:
391-
if let fallbackRoute = fallbackRouteForMissingPendingImport(hasPendingImport: contactsManager.hasPendingImport) {
412+
if !isPaykitUIActive {
413+
paykitDisabledRedirectView
414+
} else if let fallbackRoute = fallbackRouteForMissingPendingImport(hasPendingImport: contactsManager.hasPendingImport) {
392415
missingPendingImportView(fallbackRoute: fallbackRoute)
393416
} else if let profile = contactsManager.pendingImportProfile {
394417
ContactImportOverviewView(
@@ -399,15 +422,21 @@ struct MainNavView: View {
399422
missingPendingImportView(fallbackRoute: .payContacts)
400423
}
401424
case .contactImportSelect:
402-
if let fallbackRoute = fallbackRouteForMissingPendingImport(hasPendingImport: contactsManager.hasPendingImport) {
425+
if !isPaykitUIActive {
426+
paykitDisabledRedirectView
427+
} else if let fallbackRoute = fallbackRouteForMissingPendingImport(hasPendingImport: contactsManager.hasPendingImport) {
403428
missingPendingImportView(fallbackRoute: fallbackRoute)
404429
} else {
405430
ContactImportSelectView(contacts: contactsManager.pendingImportContacts)
406431
}
407-
case let .addContact(publicKey): AddContactView(publicKey: publicKey)
408-
case let .editContact(publicKey): EditContactView(publicKey: publicKey)
432+
case let .addContact(publicKey):
433+
if isPaykitUIActive { AddContactView(publicKey: publicKey) } else { paykitDisabledRedirectView }
434+
case let .editContact(publicKey):
435+
if isPaykitUIActive { EditContactView(publicKey: publicKey) } else { paykitDisabledRedirectView }
409436
case .profile:
410-
if let initializationErrorMessage = pubkyProfile.initializationErrorMessage {
437+
if !isPaykitUIActive {
438+
ComingSoonScreen()
439+
} else if let initializationErrorMessage = pubkyProfile.initializationErrorMessage {
411440
pubkyInitializationErrorView(message: initializationErrorMessage)
412441
} else if !pubkyProfile.isInitialized {
413442
pubkyLoadingView
@@ -418,11 +447,16 @@ struct MainNavView: View {
418447
} else {
419448
ProfileIntroView()
420449
}
421-
case .profileIntro: ProfileIntroView()
422-
case .pubkyChoice: PubkyChoiceView()
423-
case .createProfile: CreateProfileView()
424-
case .editProfile: EditProfileView()
425-
case .payContacts: PayContactsView()
450+
case .profileIntro:
451+
if isPaykitUIActive { ProfileIntroView() } else { ComingSoonScreen() }
452+
case .pubkyChoice:
453+
if isPaykitUIActive { PubkyChoiceView() } else { paykitDisabledRedirectView }
454+
case .createProfile:
455+
if isPaykitUIActive { CreateProfileView() } else { paykitDisabledRedirectView }
456+
case .editProfile:
457+
if isPaykitUIActive { EditProfileView() } else { paykitDisabledRedirectView }
458+
case .payContacts:
459+
if isPaykitUIActive { PayContactsView() } else { paykitDisabledRedirectView }
426460

427461
// Shop
428462
case .shopIntro: ShopIntro()
@@ -451,7 +485,8 @@ struct MainNavView: View {
451485
case .widgetsSettings: WidgetsSettingsScreen()
452486
case .notifications: NotificationsSettings()
453487
case .notificationsIntro: NotificationsIntro()
454-
case .paymentPreference: PaymentPreferenceView()
488+
case .paymentPreference:
489+
if isPaykitUIActive { PaymentPreferenceView() } else { paykitDisabledRedirectView }
455490

456491
// Security settings
457492
case .changePin: ChangePinScreen()
@@ -498,6 +533,13 @@ struct MainNavView: View {
498533
}
499534
}
500535

536+
private var paykitDisabledRedirectView: some View {
537+
Color.customBlack
538+
.task {
539+
navigation.reset()
540+
}
541+
}
542+
501543
private func handleClipboard() {
502544
Task { @MainActor in
503545
guard let uri = UIPasteboard.general.string else {

Bitkit/Services/PrivatePaykitService+Invoices.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,8 @@ extension PrivatePaykitService {
9696

9797
@MainActor
9898
func canPublishPrivateEndpoints(wallet: WalletViewModel) async -> Bool {
99-
guard UserDefaults.standard.bool(forKey: Self.publishingEnabledKey),
99+
guard PaykitFeatureFlags.isUIEnabled,
100+
UserDefaults.standard.bool(forKey: Self.publishingEnabledKey),
100101
UIApplication.shared.applicationState == .active,
101102
wallet.walletExists == true,
102103
wallet.nodeLifecycleState == .running,

0 commit comments

Comments
 (0)