// // WorkoutSessionManager.swift // Workouts Watch App // // Copyright 2025 Rouslan Zenetl. All Rights Reserved. // import Foundation import HealthKit import Observation /// Runs an `HKWorkoutSession` for the duration of a watch workout. /// /// When the phone launches us via `startWatchApp(toHandle:)`, watchOS hands the app /// delegate a workout configuration; starting a session with it is what grants the /// freshly-launched app foreground runtime (and keeps it alive while the wrist is /// down). We don't record samples — the session exists purely to host the UI. @Observable @MainActor final class WorkoutSessionManager: NSObject { private let healthStore = HKHealthStore() private var session: HKWorkoutSession? private(set) var isRunning = false /// Prompt for workout-sharing authorization. Called once at launch so a later /// phone-initiated launch can start a session without first hitting a permission /// wall. func requestAuthorization() { guard HKHealthStore.isHealthDataAvailable() else { return } healthStore.requestAuthorization(toShare: [.workoutType()], read: []) { _, _ in } } /// Start the session for a phone-launched workout. Idempotent — a second launch /// while one is already running is ignored. func start(with configuration: HKWorkoutConfiguration) { guard HKHealthStore.isHealthDataAvailable(), session == nil else { return } do { let session = try HKWorkoutSession(healthStore: healthStore, configuration: configuration) session.delegate = self self.session = session session.startActivity(with: Date()) isRunning = true } catch { session = nil isRunning = false } } /// End the session — called when the workout finishes (no active workout remains). func end() { session?.end() clear() } private func clear() { session = nil isRunning = false } } // MARK: - HKWorkoutSessionDelegate extension WorkoutSessionManager: HKWorkoutSessionDelegate { nonisolated func workoutSession( _ workoutSession: HKWorkoutSession, didChangeTo toState: HKWorkoutSessionState, from fromState: HKWorkoutSessionState, date: Date ) { guard toState == .ended || toState == .stopped else { return } Task { @MainActor in self.clear() } } nonisolated func workoutSession(_ workoutSession: HKWorkoutSession, didFailWithError error: Error) { Task { @MainActor in self.clear() } } }