import ActivityKit import Foundation import Observation /// Startet, aktualisiert und beendet die Live Activity des Neigungsmessers. /// /// Es läuft höchstens eine Aktivität gleichzeitig – mehr als einen /// Neigungsmesser gibt es im aktiven Profil ohnehin nicht. Seit iOS 17 zeigt /// CarPlay eine laufende Live Activity automatisch im Dashboard an; ein /// eigenes CarPlay-App-Target braucht es dafür nicht. @Observable final class LevelActivityManager { private(set) var trackedDeviceID: UUID? @ObservationIgnored private var activity: Activity? func isActive(for deviceID: UUID) -> Bool { trackedDeviceID == deviceID && activity != nil } func start(deviceID: UUID, deviceName: String, state: LevelState) { guard ActivityAuthorizationInfo().areActivitiesEnabled else { return } end() let attributes = LevelActivityAttributes(deviceName: deviceName) let content = ActivityContent(state: Self.contentState(from: state), staleDate: nil) do { activity = try Activity.request(attributes: attributes, content: content) trackedDeviceID = deviceID } catch { // Kann z.B. an fehlender Nutzerfreigabe liegen – dann bleibt es // einfach bei der Anzeige in der App, es gibt sonst nichts zu tun. } } /// Wird bei jeder neuen Messung aufgerufen, unabhängig davon, ob gerade /// eine Aktivität läuft – kein Aufwand ohne aktive Anzeige. func update(deviceID: UUID, state: LevelState) { guard trackedDeviceID == deviceID, let activity else { return } let content = ActivityContent(state: Self.contentState(from: state), staleDate: nil) Task { await activity.update(content) } } func end() { guard let activity else { return } trackedDeviceID = nil self.activity = nil Task { await activity.end(nil, dismissalPolicy: .immediate) } } private static func contentState(from state: LevelState) -> LevelActivityAttributes.ContentState { LevelActivityAttributes.ContentState( pitch: state.pitch, roll: state.roll, isLevel: state.isLevel, instruction: state.instruction, isCalibrated: state.isCalibrated, updatedAt: Date() ) } }