Skip to content

Commit a28e2f9

Browse files
authored
feat: add Handoff support for cross-device continuity (#677)
1 parent 8b58ab8 commit a28e2f9

3 files changed

Lines changed: 76 additions & 0 deletions

File tree

TablePro/AppDelegate+FileOpen.swift

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,58 @@ import SwiftUI
1212
private let fileOpenLogger = Logger(subsystem: "com.TablePro", category: "FileOpen")
1313

1414
extension AppDelegate {
15+
// MARK: - Handoff
16+
17+
func application(_ application: NSApplication, continue userActivity: NSUserActivity,
18+
restorationHandler: @escaping ([any NSUserActivityRestoring]) -> Void) -> Bool {
19+
handleHandoffActivity(userActivity)
20+
return true
21+
}
22+
23+
private func handleHandoffActivity(_ activity: NSUserActivity) {
24+
guard let connectionIdString = activity.userInfo?["connectionId"] as? String,
25+
let connectionId = UUID(uuidString: connectionIdString) else { return }
26+
27+
let connections = ConnectionStorage.shared.loadConnections()
28+
guard let connection = connections.first(where: { $0.id == connectionId }) else {
29+
fileOpenLogger.error("Handoff: no connection with ID '\(connectionIdString, privacy: .public)'")
30+
return
31+
}
32+
33+
let tableName = activity.userInfo?["tableName"] as? String
34+
35+
if DatabaseManager.shared.activeSessions[connectionId]?.driver != nil {
36+
if let tableName {
37+
let payload = EditorTabPayload(connectionId: connectionId, tabType: .table, tableName: tableName)
38+
WindowOpener.shared.openNativeTab(payload)
39+
} else {
40+
for window in NSApp.windows where isMainWindow(window) {
41+
window.makeKeyAndOrderFront(nil)
42+
return
43+
}
44+
}
45+
return
46+
}
47+
48+
let initialPayload = EditorTabPayload(connectionId: connectionId)
49+
WindowOpener.shared.openNativeTab(initialPayload)
50+
51+
Task { @MainActor in
52+
do {
53+
try await DatabaseManager.shared.connectToSession(connection)
54+
for window in NSApp.windows where self.isWelcomeWindow(window) {
55+
window.close()
56+
}
57+
if let tableName {
58+
let payload = EditorTabPayload(connectionId: connectionId, tabType: .table, tableName: tableName)
59+
WindowOpener.shared.openNativeTab(payload)
60+
}
61+
} catch {
62+
fileOpenLogger.error("Handoff connect failed: \(error.localizedDescription)")
63+
}
64+
}
65+
}
66+
1567
// MARK: - URL Classification
1668

1769
private func isDatabaseURL(_ url: URL) -> Bool {

TablePro/Info.plist

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,11 @@
204204
</dict>
205205
</dict>
206206
</array>
207+
<key>NSUserActivityTypes</key>
208+
<array>
209+
<string>com.TablePro.viewConnection</string>
210+
<string>com.TablePro.viewTable</string>
211+
</array>
207212
<key>CFBundleURLTypes</key>
208213
<array>
209214
<dict>

TablePro/Views/Main/MainContentView.swift

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,25 @@ struct MainContentView: View {
292292
.onChange(of: pendingChangeTrigger) {
293293
updateToolbarPendingState()
294294
}
295+
.userActivity("com.TablePro.viewConnection") { activity in
296+
activity.title = connection.name.isEmpty
297+
? connection.host
298+
: connection.name
299+
activity.isEligibleForHandoff = true
300+
activity.userInfo = ["connectionId": connection.id.uuidString]
301+
}
302+
.userActivity("com.TablePro.viewTable") { activity in
303+
guard let tableName = tabManager.selectedTab?.tableName else {
304+
activity.invalidate()
305+
return
306+
}
307+
activity.title = tableName
308+
activity.isEligibleForHandoff = true
309+
activity.userInfo = [
310+
"connectionId": connection.id.uuidString,
311+
"tableName": tableName
312+
]
313+
}
295314
}
296315

297316
private var bodyContentCore: some View {

0 commit comments

Comments
 (0)