// // LiveActivityManager.swift // Keging // // 다이나믹 아일랜드 Live Activity — 세트 일정을 실어 **시작할 때 한 번만 등록**하고 // 이후 갱신하지 않는다(종료·중단 시 dismiss만). 위젯이 전부 시스템 렌더 요소로 그리는 // 구조라(§3.4) 갱신이 필요 없고, 백그라운드 갱신 예산제(실기기 실측: 4번째부터 버려짐)의 // 영향도 받지 않는다. // import ActivityKit import Foundation @MainActor final class LiveActivityManager { static let shared = LiveActivityManager() private var activity: Activity? private init() {} /// 앱 시작 시 호출 — 강제 종료 등으로 남은 이전 Live Activity를 정리한다 /// (안 하면 운동 중 강제 종료 시 다이나믹 아일랜드가 몇 시간 동안 유령으로 남음) func cleanupStaleActivities() { for stale in Activity.activities { Task { await stale.end(nil, dismissalPolicy: .immediate) } } activity = nil } /// 운동 중인데 Live Activity가 없으면 등록 — 시작 직후 1회가 정상 경로, /// 하트비트에서도 불러 등록 실패(권한 등)를 재시도한다. 이미 있으면 아무것도 안 함 func ensureStarted() { guard activity == nil else { return } let engine = KegelEngine.shared guard case .running = engine.state else { return } guard ActivityAuthorizationInfo().areActivitiesEnabled else { return } let config = engine.config let state = KegingActivityAttributes.ContentState( sessionStart: engine.sessionStart, prepSeconds: config.prepSeconds, contractSeconds: config.contractSeconds, relaxSeconds: config.relaxSeconds, totalReps: config.reps ) activity = try? Activity.request( attributes: KegingActivityAttributes(startedAt: engine.sessionStart), content: ActivityContent(state: state, staleDate: nil) ) } func end() { guard let activity else { return } self.activity = nil Task { await activity.end(nil, dismissalPolicy: .immediate) } } }