// // LiveActivityDebugView.swift import SwiftUI struct LiveActivityDebugView: View { @EnvironmentObject var dataManager: ClimbingDataManager @State private var debugOutput: String = "" @State private var isTestRunning = false var body: some View { NavigationStack { VStack(alignment: .leading, spacing: 20) { // Header VStack(alignment: .leading, spacing: 8) { Text("Live Activity Debug") .font(.title) .fontWeight(.bold) Text("Test and debug Live Activities for climbing sessions") .font(.subheadline) .foregroundColor(.secondary) } // Status Section GroupBox("Current Status") { VStack(alignment: .leading, spacing: 12) { HStack { Image(systemName: "circle.fill") .foregroundColor(dataManager.activeSession != nil ? .green : .red) Text( "Active Session: \(dataManager.activeSession != nil ? "Yes" : "No")" ) } HStack { Image(systemName: "building.2") Text("Total Gyms: \(dataManager.gyms.count)") } if let activeSession = dataManager.activeSession, let gym = dataManager.gym(withId: activeSession.gymId) { HStack { Image(systemName: "location") Text("Current Gym: \(gym.name)") } } } } // Test Buttons GroupBox("Live Activity Tests") { VStack(spacing: 16) { Button(action: checkStatus) { HStack { Image(systemName: "checkmark.circle") Text("Check Live Activity Status") } .frame(maxWidth: .infinity) } .buttonStyle(.bordered) .disabled(isTestRunning) Button(action: testLiveActivity) { HStack { Image(systemName: isTestRunning ? "hourglass" : "play.circle") Text( isTestRunning ? "Running Test..." : "Run Full Live Activity Test") } .frame(maxWidth: .infinity) } .buttonStyle(.borderedProminent) .disabled(isTestRunning || dataManager.gyms.isEmpty) Button(action: forceLiveActivityUpdate) { HStack { Image(systemName: "arrow.clockwise") Text("Force Live Activity Update") } .frame(maxWidth: .infinity) } .buttonStyle(.bordered) .disabled(dataManager.activeSession == nil) if dataManager.gyms.isEmpty { Text("WARNING: Add at least one gym to test Live Activities") .font(.caption) .foregroundColor(.orange) } if dataManager.activeSession != nil { Button(action: endCurrentSession) { HStack { Image(systemName: "stop.circle") Text("End Current Session") } .frame(maxWidth: .infinity) } .buttonStyle(.bordered) .disabled(isTestRunning) } } } // Debug Output GroupBox("Debug Output") { ScrollView { ScrollViewReader { proxy in VStack(alignment: .leading, spacing: 4) { if debugOutput.isEmpty { Text("No debug output yet. Run a test to see details.") .foregroundColor(.secondary) .italic() } else { Text(debugOutput) .font(.system(.caption, design: .monospaced)) .textSelection(.enabled) } } .frame(maxWidth: .infinity, alignment: .leading) .padding(8) .id("bottom") .onChange(of: debugOutput) { withAnimation { proxy.scrollTo("bottom", anchor: .bottom) } } } } .frame(maxHeight: 200) .background(Color(UIColor.systemGray6)) .cornerRadius(8) } // Clear button HStack { Spacer() Button("Clear Output") { debugOutput = "" } .buttonStyle(.bordered) } Spacer() } .padding() } .navigationTitle("Live Activity Debug") .navigationBarTitleDisplayMode(.inline) } private func appendDebugOutput(_ message: String) { let timestamp = DateFormatter.timeFormatter.string(from: Date()) let newLine = "[\(timestamp)] \(message)" DispatchQueue.main.async { if debugOutput.isEmpty { debugOutput = newLine } else { debugOutput += "\n" + newLine } } } private func checkStatus() { appendDebugOutput("Checking Live Activity status...") let status = LiveActivityManager.shared.checkLiveActivityAvailability() appendDebugOutput("Status: \(status)") // Check iOS version if #available(iOS 16.1, *) { appendDebugOutput("iOS version supports Live Activities") } else { appendDebugOutput( "ERROR: iOS version does not support Live Activities (requires 16.1+)") } // Check if we're on simulator #if targetEnvironment(simulator) appendDebugOutput( "WARNING: Running on Simulator - Live Activities have limited functionality") #else appendDebugOutput("Running on device - Live Activities should work fully") #endif } private func testLiveActivity() { guard !dataManager.gyms.isEmpty else { appendDebugOutput("ERROR: No gyms available for testing") return } isTestRunning = true appendDebugOutput("🧪 Starting Live Activity test...") Task { defer { DispatchQueue.main.async { isTestRunning = false } } // Test with first gym let testGym = dataManager.gyms[0] appendDebugOutput("Using gym: \(testGym.name)") // Create test session let testSession = ClimbSession( gymId: testGym.id, notes: "Test session for Live Activity") appendDebugOutput("Created test session") // Start Live Activity await LiveActivityManager.shared.startLiveActivity( for: testSession, gymName: testGym.name) appendDebugOutput("Live Activity start request sent") // Wait and update try? await Task.sleep(nanoseconds: 3_000_000_000) // 3 seconds appendDebugOutput("Updating Live Activity with test data...") await LiveActivityManager.shared.updateLiveActivity( elapsed: 180, totalAttempts: 8, completedProblems: 2 ) // Another update try? await Task.sleep(nanoseconds: 3_000_000_000) // 3 seconds appendDebugOutput("Second update...") await LiveActivityManager.shared.updateLiveActivity( elapsed: 360, totalAttempts: 15, completedProblems: 4 ) // End after delay try? await Task.sleep(nanoseconds: 5_000_000_000) // 5 seconds appendDebugOutput("Ending Live Activity...") await LiveActivityManager.shared.endLiveActivity() appendDebugOutput("Live Activity test completed!") } } private func endCurrentSession() { guard let activeSession = dataManager.activeSession else { appendDebugOutput("ERROR: No active session to end") return } appendDebugOutput("Ending current session: \(activeSession.id)") dataManager.endSession(activeSession.id) appendDebugOutput("Session ended") } private func forceLiveActivityUpdate() { appendDebugOutput("Forcing Live Activity update...") dataManager.forceLiveActivityUpdate() appendDebugOutput("Live Activity update sent") } } extension DateFormatter { static let timeFormatter: DateFormatter = { let formatter = DateFormatter() formatter.dateFormat = "HH:mm:ss" return formatter }() } #Preview { LiveActivityDebugView() .environmentObject(ClimbingDataManager.preview) }