Files
punktfunk/clients/apple/Sources/PunktfunkShared/SessionIntents.swift
T
enricobuehler df6a5325d8 feat(apple/M1+M3+M4): widgets, Live Activity, and Siri/Shortcuts intents
The extension-side + App Intents surface for design/apple-live-activities-and-
widgets.md. The iOS-framework code (WidgetKit/ActivityKit/AppIntents) can't be
compiled by the macOS `swift build` CI target and needs the Xcode widget-
extension target that only exists once created in the GUI — see the checklist in
the memory note. What macOS DID verify: HostEntity (AppIntents is available on
macOS), the shared attribute/notification plumbing, and that nothing regressed
(142 tests green).

Shared (PunktfunkShared):
- PunktfunkSessionAttributes (ActivityAttributes) — the one type app + extension
  share; gated os(iOS) (ActivityKit imports on macOS but its types are
  unavailable, so canImport would wrongly admit it).
- EndStreamIntent (LiveActivityIntent) — posts .punktfunkEndActiveSession.
- HostEntity + HostEntityQuery (AppEntity over the shared store) — the intent /
  widget-config parameter type; canImport(AppIntents), so macOS type-checks it.
- New notifications: end-active-session, open-deep-link.

M1 widget extension sources (Sources/PunktfunkWidgets/, NOT a SwiftPM target —
`swift build` ignores the dir):
- PunktfunkWidgetBundle (@main): HostsWidget + PunktfunkSessionLiveActivity.
- HostsWidget (kind "PunktfunkHosts"): reads the shared-suite store, sorts by
  recency, deep-links each host; small/medium/accessory families; empty state.
- SessionLiveActivity: Lock-Screen banner + Dynamic Island (elapsed timer,
  mode line, background countdown, End button).

M3 controller (app, iOS): SessionActivityController owns the Activity lifecycle
(request/update/end + launch orphan-sweep + staleDate); ContentView drives it
from the model's phase/isBackgrounded/backgroundDeadline (which SessionModel now
publishes), keeping ActivityKit out of the cross-platform model.

M4 (app, iOS): ConnectToHost/WakeHost intents + AppShortcutsProvider; Connect
routes via .punktfunkOpenDeepLink into the same onOpenURL router (one set of
guards); Wake reuses the WoL path; End surfaced to Shortcuts too.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 18:51:41 +02:00

31 lines
1.4 KiB
Swift

// App Intents that must compile into BOTH the app and the widget extension live here in the shared
// module. Today that's `EndStreamIntent` — the Live Activity's "End stream" button (a
// LiveActivityIntent runs in the APP's process) which M4 also surfaces to Siri/Shortcuts.
//
// Gated on os(iOS): LiveActivityIntent is part of ActivityKit's world (iPhone/iPad only). The M4
// Connect/Wake intents that need the app's router live in the app target, not here.
#if os(iOS)
import AppIntents
import Foundation
/// Ends the active streaming session. Backs the Live Activity's End button and the Shortcuts /
/// Siri "End the Punktfunk stream" phrase. `perform()` runs in the app's process (LiveActivityIntent)
/// — it posts `.punktfunkEndActiveSession`, which the app's SessionModel owner observes and turns
/// into `disconnect(deliberate: true)` (the user explicitly ended it → quit-close the host).
@available(iOS 17.0, *)
public struct EndStreamIntent: LiveActivityIntent {
public static let title: LocalizedStringResource = "End Punktfunk Stream"
public static let description = IntentDescription("Ends the active Punktfunk streaming session.")
public init() {}
public func perform() async throws -> some IntentResult {
await MainActor.run {
NotificationCenter.default.post(name: .punktfunkEndActiveSession, object: nil)
}
return .result()
}
}
#endif