Skip to content

Commit c005aeb

Browse files
bgoncalclaude
andcommitted
Open Assist from the watch complication via SwiftUI App lifecycle
The Assist complication's `homeassistant://assist` widget URL was never received: the watch app ran on the legacy WatchKit lifecycle (@main on ExtensionDelegate + WKHostingController), where SwiftUI's .onOpenURL / .onContinueUserActivity are inert (runtime warned "Cannot use Scene methods for URL … without SwiftUI Lifecycle"). - Adopt the SwiftUI App lifecycle: @main HomeAssistantWatchApp with WKApplicationDelegateAdaptor(ExtensionDelegate.self) and WindowGroup { WatchHomeView() } - WatchHomeView now handles the assist deep link via .onOpenURL / .onContinueUserActivity, plus a cold-launch pending flag consumed on appear - Replace the Assist fullScreenCover fatalError with a "configure Assist" message Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 4719649 commit c005aeb

4 files changed

Lines changed: 51 additions & 5 deletions

File tree

Sources/WatchApp/Complication/Assist/AssistDefaultComplication.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,9 @@ enum AssistDefaultComplication {
44
static let title = "Assist"
55
static let launchNotification: Notification.Name = .init("assist-detault-complication-launch")
66
static let defaultComplicationId = "default-assist"
7+
8+
/// Set when the app is launched from the Assist complication before the UI is ready to present it.
9+
/// `WatchHomeView` consumes this on appear so a cold launch still opens Assist (the launch
10+
/// notification would otherwise fire before the view subscribes to it).
11+
static var pendingLaunch = false
712
}

Sources/WatchApp/ExtensionDelegate.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import WatchKit
66
import WidgetKit
77
import XCGLogger
88

9-
@main
109
class ExtensionDelegate: NSObject, WKApplicationDelegate {
1110
// MARK: Fileprivate
1211

@@ -124,6 +123,9 @@ class ExtensionDelegate: NSObject, WKApplicationDelegate {
124123
}
125124

126125
private func launchAssist() {
126+
// Record the intent so a cold launch still opens Assist once the UI appears, and post the
127+
// notification for the case where the view is already on screen (warm launch).
128+
AssistDefaultComplication.pendingLaunch = true
127129
NotificationCenter.default.post(name: AssistDefaultComplication.launchNotification, object: nil)
128130
}
129131

Sources/WatchApp/Home/WatchHomeView.swift

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,14 @@ struct WatchHomeView: View {
5757
.onReceive(NotificationCenter.default.publisher(for: AssistDefaultComplication.launchNotification)) { _ in
5858
showAssist = true
5959
}
60+
// The Assist complication opens the app via a `homeassistant://assist` widget URL. Depending on
61+
// launch state watchOS delivers this either as an opened URL or as a browsing-web user activity.
62+
.onOpenURL { url in
63+
if isAssistDeepLink(url) { showAssist = true }
64+
}
65+
.onContinueUserActivity(NSUserActivityTypeBrowsingWeb) { activity in
66+
if isAssistDeepLink(activity.webpageURL) { showAssist = true }
67+
}
6068
.onReceive(NotificationCenter.default.publisher(for: .watchConfigDidChange)) { _ in
6169
viewModel.loadCache()
6270
}
@@ -68,7 +76,13 @@ struct WatchHomeView: View {
6876
pipelineId: pipelineId
6977
)
7078
} else {
71-
fatalError("Assist launched without serverId or pipelineId")
79+
// Assist isn't configured yet (e.g. cold launch before the config synced). Surface a
80+
// message instead of crashing; the user can retry once configuration is available.
81+
Text(verbatim: L10n.Watch.Assist.LackConfig.Error.title)
82+
.font(.footnote)
83+
.foregroundStyle(.secondary)
84+
.multilineTextAlignment(.center)
85+
.padding()
7286
}
7387
})
7488
.sheet(isPresented: $showSettings) {
@@ -96,6 +110,11 @@ struct WatchHomeView: View {
96110
Text(verbatim: L10n.Watch.Config.Conflict.message)
97111
}
98112
.onAppear {
113+
// Consume a launch requested from the complication before this view existed (cold launch).
114+
if AssistDefaultComplication.pendingLaunch {
115+
AssistDefaultComplication.pendingLaunch = false
116+
showAssist = true
117+
}
99118
guard autoLoad else { return }
100119
viewModel.startNetworkMonitoring()
101120
Task {
@@ -289,6 +308,11 @@ struct WatchHomeView: View {
289308
}
290309
}
291310

311+
private func isAssistDeepLink(_ url: URL?) -> Bool {
312+
guard let url else { return false }
313+
return ["homeassistant", "homeassistant-dev"].contains(url.scheme) && url.host == "assist"
314+
}
315+
292316
private func enterEditMode() {
293317
withAnimation { isEditing = true }
294318
}

Sources/WatchApp/HostingController.swift

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,27 @@ import Foundation
22
import Shared
33
import SwiftUI
44

5-
final class HostingController: WKHostingController<WatchHomeView> {
6-
override init() {
7-
super.init()
5+
// The watch app uses the SwiftUI App lifecycle so SwiftUI can deliver external events (URLs /
6+
// NSUserActivity) — notably the `homeassistant://assist` widget URL from the Assist complication.
7+
// The legacy WatchKit lifecycle (WKApplicationDelegate + WKHostingController) cannot receive those,
8+
// hence the "Cannot use Scene methods for URL … without using SwiftUI Lifecycle" runtime warning.
9+
@main
10+
struct HomeAssistantWatchApp: App {
11+
@WKApplicationDelegateAdaptor(ExtensionDelegate.self) private var delegate
12+
13+
init() {
814
MaterialDesignIcons.register()
915
}
1016

17+
var body: some Scene {
18+
WindowGroup {
19+
WatchHomeView()
20+
}
21+
}
22+
}
23+
24+
// Retained so the legacy `Interface.storyboard` reference resolves; unused under the SwiftUI lifecycle.
25+
final class HostingController: WKHostingController<WatchHomeView> {
1126
override var body: WatchHomeView {
1227
WatchHomeView()
1328
}

0 commit comments

Comments
 (0)