Skip to content

Commit 989c0e4

Browse files
authored
feat(recovery): add recovery quick action (#146)
1 parent 72b9f89 commit 989c0e4

File tree

12 files changed

+540
-18
lines changed

12 files changed

+540
-18
lines changed

Bitkit/AppScene.swift

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ struct AppScene: View {
2323
@State private var walletIsInitializing: Bool? = nil
2424
@State private var walletInitShouldFinish = false
2525
@State private var isPinVerified: Bool = false
26+
@State private var showRecoveryScreen = false
2627

2728
init() {
2829
let sheetViewModel = SheetViewModel()
@@ -79,13 +80,27 @@ struct AppScene: View {
7980
description: "Bitkit was unable to register for push notifications."
8081
)
8182
}
83+
84+
// Listen for quick action notifications
85+
NotificationCenter.default.addObserver(
86+
forName: .quickActionSelected,
87+
object: nil,
88+
queue: .main
89+
) { notification in
90+
handleQuickAction(notification)
91+
}
8292
}
8393
}
8494

8595
@ViewBuilder
8696
private var mainContent: some View {
8797
ZStack {
88-
walletContent
98+
if showRecoveryScreen {
99+
RecoveryRouter()
100+
.accentColor(.white)
101+
} else {
102+
walletContent
103+
}
89104

90105
if !removeSplash && !session.skipSplashOnce {
91106
SplashView()
@@ -99,7 +114,7 @@ struct AppScene: View {
99114
if wallet.walletExists == true {
100115
existingWalletContent
101116
} else if wallet.walletExists == false {
102-
newWalletContent
117+
onboardingContent
103118
}
104119
}
105120

@@ -138,7 +153,7 @@ struct AppScene: View {
138153
}
139154

140155
@ViewBuilder
141-
private var newWalletContent: some View {
156+
private var onboardingContent: some View {
142157
NavigationStack {
143158
TermsView()
144159
}
@@ -174,6 +189,9 @@ struct AppScene: View {
174189

175190
guard wallet.walletExists == true else { return }
176191

192+
// Don't start wallet if we're in recovery mode
193+
guard !showRecoveryScreen else { return }
194+
177195
wallet.addOnEvent(id: "toasts-and-sheets") { [weak app] lightningEvent in
178196
app?.handleLdkNodeEvent(lightningEvent)
179197
}
@@ -235,4 +253,19 @@ struct AppScene: View {
235253
isPinVerified = false
236254
}
237255
}
256+
257+
private func handleQuickAction(_ notification: Notification) {
258+
guard let userInfo = notification.userInfo,
259+
let shortcutType = userInfo["shortcutType"] as? String
260+
else {
261+
return
262+
}
263+
264+
switch shortcutType {
265+
case "Recovery":
266+
showRecoveryScreen = true
267+
default:
268+
break
269+
}
270+
}
238271
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"images" : [
3+
{
4+
"filename" : "recovery.png",
5+
"idiom" : "universal",
6+
"scale" : "1x"
7+
},
8+
{
9+
"filename" : "recovery 1.png",
10+
"idiom" : "universal",
11+
"scale" : "2x"
12+
},
13+
{
14+
"filename" : "recovery 2.png",
15+
"idiom" : "universal",
16+
"scale" : "3x"
17+
}
18+
],
19+
"info" : {
20+
"author" : "xcode",
21+
"version" : 1
22+
}
23+
}
847 Bytes
Loading
847 Bytes
Loading
847 Bytes
Loading

Bitkit/BitkitApp.swift

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,44 @@
11
import SwiftUI
22

3+
// MARK: - Quick Action Notification
4+
5+
// Communication bridge between delegates and SwiftUI views
6+
extension Notification.Name {
7+
static let quickActionSelected = Notification.Name("quickActionSelected")
8+
}
9+
310
class AppDelegate: NSObject, UIApplicationDelegate {
4-
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool
11+
// MARK: - App Launch
12+
13+
func application(_ application: UIApplication,
14+
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool
515
{
616
UNUserNotificationCenter.current().delegate = self
717
return true
818
}
919

20+
// MARK: - Scene Configuration
21+
22+
// Required for SwiftUI apps to handle quick actions
23+
func application(
24+
_ application: UIApplication,
25+
configurationForConnecting connectingSceneSession: UISceneSession,
26+
options: UIScene.ConnectionOptions
27+
) -> UISceneConfiguration {
28+
let config = UISceneConfiguration(name: nil, sessionRole: connectingSceneSession.role)
29+
config.delegateClass = SceneDelegate.self
30+
return config
31+
}
32+
33+
// MARK: - App Termination
34+
1035
func applicationWillTerminate(_ application: UIApplication) {
11-
// Unlock so LDK can run in the background
1236
try? StateLocker.unlock(.lightning)
1337
}
1438
}
1539

40+
// MARK: - Push Notifications
41+
1642
extension AppDelegate: UNUserNotificationCenterDelegate {
1743
func userNotificationCenter(
1844
_ center: UNUserNotificationCenter,
@@ -23,23 +49,18 @@ extension AppDelegate: UNUserNotificationCenterDelegate {
2349

2450
Logger.debug(userInfo, context: "push notification received while app is in the foreground")
2551

26-
// If we want to display the native notification to the user while the app is open we need to call this with options
27-
// Unlikely we will need to as the background operation would have been aborted and we would have nothint to show
2852
completionHandler([])
29-
// completionHandler([[.banner, .badge, .sound]])
53+
// completionHandler([[.banner, .badge, .sound]])
3054
}
3155

3256
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
33-
// Convert token to hex string and register immediately with server
3457
let tokenString = deviceToken.map { String(format: "%02hhx", $0) }.joined()
3558

36-
// Register with server immediately when we get a new token
3759
Task {
3860
do {
3961
try await NotificationService.shared.registerDeviceForNotifications(deviceToken: tokenString)
4062
} catch {
4163
Logger.error("Failed to register device token with server: \(error)")
42-
// Notify via callback that registration failed
4364
await MainActor.run {
4465
NotificationService.shared.onRegistrationStatusChanged?(false)
4566
NotificationService.shared.onRegistrationFailed?(error)
@@ -63,15 +84,14 @@ extension AppDelegate: UNUserNotificationCenterDelegate {
6384
}
6485
}
6586

87+
// MARK: - SwiftUI App
88+
6689
@main
6790
struct BitkitApp: App {
6891
@UIApplicationDelegateAdaptor(AppDelegate.self) var delegate
6992

7093
init() {
71-
// Set dark mode as default
7294
UIWindow.appearance().overrideUserInterfaceStyle = .dark
73-
74-
// Initialize toast window manager early
7595
_ = ToastWindowManager.shared
7696
}
7797

Bitkit/Components/NavigationBar.swift

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,23 +9,37 @@ struct NavigationBar: View {
99
let showMenuButton: Bool
1010
let action: AnyView?
1111
let icon: String?
12+
let onBack: (() -> Void)?
1213

13-
init(title: String, showBackButton: Bool = true, showMenuButton: Bool = true, action: AnyView? = nil, icon: String? = nil) {
14+
init(
15+
title: String,
16+
showBackButton: Bool = true,
17+
showMenuButton: Bool = true,
18+
action: AnyView? = nil,
19+
icon: String? = nil,
20+
onBack: (() -> Void)? = nil
21+
) {
1422
self.title = title
1523
self.showBackButton = showBackButton
1624
self.showMenuButton = showMenuButton
1725
self.action = action
1826
self.icon = icon
27+
self.onBack = onBack
1928
}
2029

2130
var body: some View {
2231
HStack(alignment: .center, spacing: 0) {
2332
if showBackButton {
2433
Button(action: {
25-
if navigation.canGoBack {
26-
navigation.navigateBack()
34+
if let onBack {
35+
onBack()
2736
} else {
28-
navigation.reset()
37+
// Default behavior for main navigation stack
38+
if navigation.canGoBack {
39+
navigation.navigateBack()
40+
} else {
41+
navigation.reset()
42+
}
2943
}
3044
}) {
3145
Image("arrow-left")

Bitkit/Info.plist

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,5 +33,16 @@
3333
</array>
3434
<key>UILaunchStoryboardName</key>
3535
<string>LaunchScreen</string>
36+
<key>UIApplicationShortcutItems</key>
37+
<array>
38+
<dict>
39+
<key>UIApplicationShortcutItemType</key>
40+
<string>Recovery</string>
41+
<key>UIApplicationShortcutItemTitle</key>
42+
<string>Recovery</string>
43+
<key>UIApplicationShortcutItemIconFile</key>
44+
<string>recovery</string>
45+
</dict>
46+
</array>
3647
</dict>
3748
</plist>

Bitkit/SceneDelegate.swift

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import SwiftUI
2+
import UIKit
3+
4+
// MARK: - Scene Delegate for Quick Actions
5+
6+
// Handles scene lifecycle and quick actions for SwiftUI apps
7+
class SceneDelegate: NSObject, UIWindowSceneDelegate {
8+
// MARK: - Quick Action State
9+
10+
var savedShortCutItem: UIApplicationShortcutItem?
11+
12+
// MARK: - Scene Connection
13+
14+
// Save quick action when scene is created
15+
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
16+
if let shortcutItem = connectionOptions.shortcutItem {
17+
savedShortCutItem = shortcutItem
18+
}
19+
}
20+
21+
// MARK: - Scene Activation
22+
23+
// Handle saved quick action when scene becomes active
24+
func sceneDidBecomeActive(_ scene: UIScene) {
25+
if let shortcutItem = savedShortCutItem {
26+
handleQuickAction(shortcutItem)
27+
savedShortCutItem = nil
28+
}
29+
}
30+
31+
// MARK: - Quick Action Handling (App Running)
32+
33+
// Handle quick action when app is already running
34+
func windowScene(
35+
_ windowScene: UIWindowScene,
36+
performActionFor shortcutItem: UIApplicationShortcutItem,
37+
completionHandler: @escaping (Bool) -> Void
38+
) {
39+
handleQuickAction(shortcutItem)
40+
completionHandler(true)
41+
}
42+
43+
// MARK: - Quick Action Processing
44+
45+
// Process quick action and notify SwiftUI views
46+
private func handleQuickAction(_ shortcutItem: UIApplicationShortcutItem) {
47+
let userInfo = ["shortcutType": shortcutItem.type]
48+
NotificationCenter.default.post(name: .quickActionSelected, object: nil, userInfo: userInfo)
49+
}
50+
}

0 commit comments

Comments
 (0)