Launch Activity Logic

Nucleic-Session: EBB24271-3AB2-4CBD-A47A-59FC73500371
Co-authored-by: Nucleic <[email protected]>
This commit is contained in:
2026-07-06 14:04:06 -07:00
co-authored by Nucleic
parent c362480c8a
commit 3806229442
@@ -395,6 +395,50 @@ final class RemoteStore: ObservableObject {
#endif #endif
} }
/// Handle the host's silent "adopt" wake (UX_IOS §5.3): a session started while the app was
/// closed, the host push-*started* the Live Activity, and now the phone must come online long
/// enough to harvest that activity's per-activity update token and register it — so live updates
/// (and a clean end) resume without the user ever opening the app. Called from `PushAppDelegate`
/// on a `content-available` background push, which may relaunch the app with no scene, so this
/// does the setup `onAppear` normally would. Holds a background-task assertion until the token is
/// registered with a host or the window closes, then calls `completion`. Best-effort: iOS may
/// deny background runtime (budget, force-quit), in which case the glance keeps its start-time
/// state until the app next runs.
func handleLiveActivityAdoptWake(completion: @escaping () -> Void) {
guard !demoMode, isPaired else { completion(); return }
setupLiveActivityBridge() // starts the push-to-start + adoption observers if not already
startNetworkingIfNeeded()
#if canImport(UIKit)
var bgTask: UIBackgroundTaskIdentifier = .invalid
var finished = false
let finish = {
guard !finished else { return }
finished = true
if bgTask != .invalid {
UIApplication.shared.endBackgroundTask(bgTask)
bgTask = .invalid
}
completion()
}
bgTask = UIApplication.shared.beginBackgroundTask(withName: "nucleic.liveactivity.adopt") {
finish() // the OS reclaimed the grant before we finished — report what we have
}
#else
let finish = completion
#endif
pathMonitor.refresh()
reconnect()
// Poll until the harvested update token has reached a host (`liveActivitySentTo` fills in via
// the adopt → onPushToken → syncLiveActivityRegistration chain), or we run out of runtime.
Task { @MainActor [weak self] in
for _ in 0..<50 { // ~25s at a 0.5s cadence, inside iOS's ~30s background grant
guard let self, self.liveActivitySentTo.isEmpty else { break }
try? await Task.sleep(for: .milliseconds(500))
}
finish()
}
}
private func seedDemo() { private func seedDemo() {
connectivity = .connected(.lan) connectivity = .connected(.lan)
hostName = "Andrew's Mac" hostName = "Andrew's Mac"