df6a5325d8
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>
57 lines
2.0 KiB
Swift
57 lines
2.0 KiB
Swift
// The saved-host as an App Intents entity — the parameter type for the Connect/Wake intents and
|
|
// the configurable single-host widget. Lives in the shared module (not the app) because widget
|
|
// *configuration* intents execute in the EXTENSION process, so the entity can't be app-only.
|
|
//
|
|
// AppIntents is genuinely available on macOS (13+), so this is gated on `canImport(AppIntents)`
|
|
// (unlike ActivityKit, whose macOS types are unavailable) — it compiles on every platform and the
|
|
// entity query reads the same shared App-Group store the widget does.
|
|
|
|
#if canImport(AppIntents)
|
|
import AppIntents
|
|
import Foundation
|
|
|
|
public struct HostEntity: AppEntity, Identifiable {
|
|
public static let typeDisplayRepresentation = TypeDisplayRepresentation(name: "Host")
|
|
public static let defaultQuery = HostEntityQuery()
|
|
|
|
public let id: UUID
|
|
public let name: String
|
|
|
|
public init(id: UUID, name: String) {
|
|
self.id = id
|
|
self.name = name
|
|
}
|
|
|
|
public init(_ host: StoredHost) {
|
|
self.id = host.id
|
|
self.name = host.displayName
|
|
}
|
|
|
|
public var displayRepresentation: DisplayRepresentation {
|
|
DisplayRepresentation(title: "\(name)")
|
|
}
|
|
}
|
|
|
|
public struct HostEntityQuery: EntityQuery {
|
|
public init() {}
|
|
|
|
public func entities(for identifiers: [UUID]) async throws -> [HostEntity] {
|
|
Self.loadHosts().filter { identifiers.contains($0.id) }.map(HostEntity.init)
|
|
}
|
|
|
|
/// Sorted most-recent first — Siri/Shortcuts and the widget config picker suggest recent hosts.
|
|
public func suggestedEntities() async throws -> [HostEntity] {
|
|
Self.loadHosts().map(HostEntity.init)
|
|
}
|
|
|
|
static func loadHosts() -> [StoredHost] {
|
|
guard let data = AppGroup.defaults.data(forKey: DefaultsKey.hosts),
|
|
let hosts = try? JSONDecoder().decode([StoredHost].self, from: data)
|
|
else { return [] }
|
|
return hosts.sorted {
|
|
($0.lastConnected ?? .distantPast) > ($1.lastConnected ?? .distantPast)
|
|
}
|
|
}
|
|
}
|
|
#endif
|