// // LiveActivityManager.swift // Haru_Danim // // 추적 상태를 다이나믹 아일랜드/잠금화면 Live Activity에 반영 (CLAUDE.md §4.1, §4.2) // import Foundation import ActivityKit import SwiftData enum LiveActivityManager { /// 현재 진행 중인 세션들을 읽어 Live Activity를 시작/갱신/종료한다. /// 세션 시작·종료·수정 후마다 호출. static func sync(context: ModelContext) { guard ActivityAuthorizationInfo().areActivitiesEnabled else { return } let descriptor = FetchDescriptor( predicate: #Predicate { $0.endAt == nil }, sortBy: [SortDescriptor(\.startAt, order: .forward)] ) let running = (try? context.fetch(descriptor)) ?? [] let mode = LiveActivityMode( rawValue: UserDefaults.standard.string(forKey: SettingsKeys.liveActivityMode) ?? "" ) ?? .latest let representative = mode == .earliest ? running.first : running.last guard let representative, let action = representative.action else { endAll() return } let state = TrackingActivityAttributes.ContentState( actionName: action.name, symbolName: action.symbolName, colorHex: action.sortedTags.first?.colorHex ?? "#2F6B4F", startedAt: representative.startAt, extraCount: max(running.count - 1, 0) ) let content = ActivityContent(state: state, staleDate: nil) Task { let activities = Activity.activities if let activity = activities.first { await activity.update(content) // 혹시 중복 생성된 것이 있으면 정리 for extra in activities.dropFirst() { await extra.end(nil, dismissalPolicy: .immediate) } } else { _ = try? Activity.request( attributes: TrackingActivityAttributes(), content: content ) } } } static func endAll() { Task { for activity in Activity.activities { await activity.end(nil, dismissalPolicy: .immediate) } } } }