import Combine import Foundation import HealthKit @MainActor class HealthKitService: ObservableObject { static let shared = HealthKitService() private let healthStore = HKHealthStore() private var currentWorkoutStartDate: Date? private var currentWorkoutSessionId: UUID? @Published var isAuthorized = false @Published var isEnabled = false private let userDefaults = UserDefaults.standard private let isEnabledKey = "healthKitEnabled" private init() { loadSettings() } func loadSettings() { isEnabled = userDefaults.bool(forKey: isEnabledKey) if HKHealthStore.isHealthDataAvailable() { checkAuthorization() } } func setEnabled(_ enabled: Bool) { isEnabled = enabled userDefaults.set(enabled, forKey: isEnabledKey) } func requestAuthorization() async throws { guard HKHealthStore.isHealthDataAvailable() else { throw HealthKitError.notAvailable } let workoutType = HKObjectType.workoutType() let energyBurnedType = HKObjectType.quantityType(forIdentifier: .activeEnergyBurned)! let typesToShare: Set = [ workoutType, energyBurnedType, ] let typesToRead: Set = [ workoutType ] try await healthStore.requestAuthorization(toShare: typesToShare, read: typesToRead) self.isAuthorized = true } private func checkAuthorization() { let workoutType = HKObjectType.workoutType() let status = healthStore.authorizationStatus(for: workoutType) isAuthorized = (status == .sharingAuthorized) } func startWorkout(startDate: Date, sessionId: UUID) async throws { guard isEnabled && isAuthorized else { return } guard HKHealthStore.isHealthDataAvailable() else { throw HealthKitError.notAvailable } currentWorkoutStartDate = startDate currentWorkoutSessionId = sessionId } func endWorkout(endDate: Date) async throws { guard isEnabled && isAuthorized else { return } guard let startDate = currentWorkoutStartDate else { return } guard HKHealthStore.isHealthDataAvailable() else { throw HealthKitError.notAvailable } let duration = endDate.timeIntervalSince(startDate) let calories = estimateCalories(durationInMinutes: duration / 60.0) let energyBurned = HKQuantity(unit: .kilocalorie(), doubleValue: calories) let workout = HKWorkout( activityType: .climbing, start: startDate, end: endDate, duration: duration, totalEnergyBurned: energyBurned, totalDistance: nil, metadata: [ HKMetadataKeyIndoorWorkout: true ] ) do { try await healthStore.save(workout) currentWorkoutStartDate = nil currentWorkoutSessionId = nil } catch { currentWorkoutStartDate = nil currentWorkoutSessionId = nil throw HealthKitError.workoutSaveFailed } } func cancelWorkout() { currentWorkoutStartDate = nil currentWorkoutSessionId = nil } func hasActiveWorkout() -> Bool { return currentWorkoutStartDate != nil } private func estimateCalories(durationInMinutes: Double) -> Double { let caloriesPerMinute = 8.0 return durationInMinutes * caloriesPerMinute } } enum HealthKitError: LocalizedError { case notAvailable case notAuthorized case workoutStartFailed case workoutSaveFailed var errorDescription: String? { switch self { case .notAvailable: return "HealthKit is not available on this device" case .notAuthorized: return "HealthKit authorization not granted" case .workoutStartFailed: return "Failed to start HealthKit workout" case .workoutSaveFailed: return "Failed to save workout to HealthKit" } } }