150 lines
5.5 KiB
Swift
150 lines
5.5 KiB
Swift
import ActivityKit
|
||
import Foundation
|
||
|
||
@MainActor
|
||
final class LiveActivityManager {
|
||
static let shared = LiveActivityManager()
|
||
private init() {}
|
||
|
||
private var currentActivity: Activity<SessionActivityAttributes>?
|
||
|
||
/// 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<SessionActivityAttributes>.request(
|
||
attributes: attributes,
|
||
content: .init(state: initialContentState, staleDate: nil),
|
||
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
|
||
)
|
||
|
||
await currentActivity.update(.init(state: updatedContentState, staleDate: nil))
|
||
print("✅ Live Activity updated successfully")
|
||
}
|
||
|
||
/// Call this when a ClimbSession ends to end the Live Activity
|
||
func endLiveActivity() async {
|
||
// First end the tracked activity if it exists
|
||
if let currentActivity {
|
||
print("🔴 Ending tracked Live Activity: \(currentActivity.id)")
|
||
await currentActivity.end(nil, dismissalPolicy: .immediate)
|
||
self.currentActivity = nil
|
||
print("✅ Tracked Live Activity ended successfully")
|
||
}
|
||
|
||
// Force end ALL active activities of our type to ensure cleanup
|
||
print("🔍 Checking for any remaining active activities...")
|
||
let activities = Activity<SessionActivityAttributes>.activities
|
||
|
||
if activities.isEmpty {
|
||
print("ℹ️ No additional activities found")
|
||
} else {
|
||
print("🔴 Found \(activities.count) additional active activities, ending them...")
|
||
for activity in activities {
|
||
print("🔴 Force ending activity: \(activity.id)")
|
||
await activity.end(nil, dismissalPolicy: .immediate)
|
||
}
|
||
print("✅ All Live Activities ended successfully")
|
||
}
|
||
}
|
||
|
||
/// 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)
|
||
}
|
||
}
|
||
}
|
||
}
|