Read App Intents Documentation
Nucleic-Session: 56C5F781-4444-466B-91CE-A7734A8E117F Co-authored-by: Nucleic <[email protected]>
This commit is contained in:
@@ -1225,6 +1225,59 @@ final class RemoteStore: ObservableObject {
|
|||||||
func setSessionAutoShip(_ id: SessionID, _ autoShip: Bool) { send(.setSessionAutoShip(id, autoShip)) }
|
func setSessionAutoShip(_ id: SessionID, _ autoShip: Bool) { send(.setSessionAutoShip(id, autoShip)) }
|
||||||
func setSessionShipBranch(_ id: SessionID, _ branch: String?) { send(.setSessionShipBranch(id, branch)) }
|
func setSessionShipBranch(_ id: SessionID, _ branch: String?) { send(.setSessionShipBranch(id, branch)) }
|
||||||
|
|
||||||
|
// MARK: - App Intents support
|
||||||
|
|
||||||
|
/// Bring networking online and dial the paired host(s) for an App Intent that runs in a *cold*
|
||||||
|
/// background process (Siri / Shortcuts / a widget or Live Activity button), where the SwiftUI
|
||||||
|
/// scene never mounts and `onAppear` never fires. Mirrors the push handler's silent-launch
|
||||||
|
/// bootstrap (`startNetworkingIfNeeded` + `reconnect`). Idempotent; a no-op in demo mode.
|
||||||
|
func bootstrapForIntent() {
|
||||||
|
guard !demoMode else { return }
|
||||||
|
startNetworkingIfNeeded()
|
||||||
|
if isPaired { reconnect() }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether any paired Mac currently has a live channel.
|
||||||
|
var hasLiveConnection: Bool { connections.values.contains { $0.connectivity.isLive } }
|
||||||
|
|
||||||
|
/// Await a live connection to any paired Mac, up to `timeout` seconds — an intent must act over a
|
||||||
|
/// *live* channel or fail clean (UX_IOS §6, §3.1). Brings networking up first if it's cold, then
|
||||||
|
/// polls until a link comes up or the deadline passes. Returns whether a link is live.
|
||||||
|
func awaitLiveConnection(timeout: TimeInterval = 6) async -> Bool {
|
||||||
|
if demoMode || hasLiveConnection { return true }
|
||||||
|
bootstrapForIntent()
|
||||||
|
let deadline = Date().addingTimeInterval(timeout)
|
||||||
|
while Date() < deadline {
|
||||||
|
try? await Task.sleep(for: .milliseconds(200))
|
||||||
|
if hasLiveConnection { return true }
|
||||||
|
}
|
||||||
|
return hasLiveConnection
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resolve an approval by id from an App Intent with a full `Decision`. Like
|
||||||
|
/// `respondFromNotification`, it prefers the owning Mac, else broadcasts to every live Mac (the
|
||||||
|
/// owner resolves; the rest see an unknown / `alreadyResolved` id and no-op), and queues briefly
|
||||||
|
/// on a dropped link so a decision made just as the socket blips still lands. `sessionID` (when
|
||||||
|
/// the surface knows it) routes directly. Returns whether it went out over a live channel now.
|
||||||
|
@discardableResult
|
||||||
|
func respondToApproval(id: ApprovalID, sessionID: SessionID?, decision: Decision) -> Bool {
|
||||||
|
if demoMode { demoHandle(.approvalRespond(id, decision)); return true }
|
||||||
|
if let sessionID, let conn = connection(owningSession: sessionID), conn.connectivity.isLive {
|
||||||
|
conn.send(.approvalRespond(id, decision))
|
||||||
|
openApprovals.removeAll { $0.id == id }
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
let live = connections.values.filter { $0.connectivity.isLive }
|
||||||
|
if !live.isEmpty {
|
||||||
|
for conn in live { conn.send(.approvalRespond(id, decision)) }
|
||||||
|
openApprovals.removeAll { $0.id == id }
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
pendingNotificationDecision = (id, decision, Date())
|
||||||
|
reconnect()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Plumbing
|
// MARK: - Plumbing
|
||||||
|
|
||||||
/// Route an intent to the Mac that owns its target (mesh P3). Sessions/projects/to-dos are shown
|
/// Route an intent to the Mac that owns its target (mesh P3). Sessions/projects/to-dos are shown
|
||||||
|
|||||||
Reference in New Issue
Block a user