import ActivityKit import Foundation @MainActor final class LiveActivityManager { static let shared = LiveActivityManager() private init() {} private var currentActivity: Activity? /// Check if there's an active session and restart Live Activity if needed func restartLiveActivityIfNeeded(activeSession: ClimbSession?, gymName: String?) async { // If we have an active session but no Live Activity, restart it guard let activeSession = activeSession, let gymName = gymName, activeSession.status == .active else { return } // Check if we already have a running Live Activity if currentActivity != nil { print("â„šī¸ Live Activity already running") return } print("🔄 Restarting Live Activity for existing session") await startLiveActivity(for: activeSession, gymName: gymName) } /// Call this when a ClimbSession starts to begin a Live Activity func startLiveActivity(for session: ClimbSession, gymName: String) async { print("🔴 Starting Live Activity for gym: \(gymName)") await endLiveActivity() let attributes = SessionActivityAttributes( gymName: gymName, startTime: session.startTime ?? session.date) let initialContentState = SessionActivityAttributes.ContentState( elapsed: 0, totalAttempts: 0, completedProblems: 0 ) do { let activity = try Activity.request( attributes: attributes, contentState: initialContentState, pushType: nil ) self.currentActivity = activity print("✅ Live Activity started successfully: \(activity.id)") } catch { print("❌ Failed to start live activity: \(error)") print("Error details: \(error.localizedDescription)") // Check specific error types if error.localizedDescription.contains("authorization") { print("Authorization error - check Live Activity permissions in Settings") } else if error.localizedDescription.contains("content") { print("Content error - check ActivityAttributes structure") } } } /// Call this to update the Live Activity with new session progress func updateLiveActivity(elapsed: TimeInterval, totalAttempts: Int, completedProblems: Int) async { guard let currentActivity else { print("âš ī¸ No current activity to update") return } print( "🔄 Updating Live Activity - Attempts: \(totalAttempts), Completed: \(completedProblems)" ) let updatedContentState = SessionActivityAttributes.ContentState( elapsed: elapsed, totalAttempts: totalAttempts, completedProblems: completedProblems ) do { await currentActivity.update(using: updatedContentState, alertConfiguration: nil) print("✅ Live Activity updated successfully") } catch { print("❌ Failed to update live activity: \(error)") } } /// Call this when a ClimbSession ends to end the Live Activity func endLiveActivity() async { guard let currentActivity else { print("â„šī¸ No current activity to end") return } print("🔴 Ending Live Activity: \(currentActivity.id)") do { await currentActivity.end(using: nil, dismissalPolicy: .immediate) self.currentActivity = nil print("✅ Live Activity ended successfully") } catch { print("❌ Failed to end live activity: \(error)") self.currentActivity = nil } } /// Check if Live Activities are available and authorized func checkLiveActivityAvailability() -> String { let authorizationInfo = ActivityAuthorizationInfo() let status = authorizationInfo.areActivitiesEnabled let message = """ Live Activity Status: â€ĸ Enabled: \(status) â€ĸ Authorization: \(authorizationInfo.areActivitiesEnabled ? "Granted" : "Denied/Unknown") â€ĸ Current Activity: \(currentActivity?.id.description ?? "None") """ print(message) return message } /// Start periodic updates for Live Activity func startPeriodicUpdates(for session: ClimbSession, totalAttempts: Int, completedProblems: Int) { guard currentActivity != nil else { return } Task { while currentActivity != nil { let elapsed = Date().timeIntervalSince(session.startTime ?? session.date) await updateLiveActivity( elapsed: elapsed, totalAttempts: totalAttempts, completedProblems: completedProblems ) // Wait 30 seconds before next update try? await Task.sleep(nanoseconds: 30_000_000_000) } } } }