// // WorkoutHealthDeleter.swift // Workouts // // Copyright 2025 Rouslan Zenetl. All Rights Reserved. // import Foundation import HealthKit import os /// Removes a workout's Apple Health record when the user deletes that workout in the app. /// /// Apple Health recording is **watch-only**: the watch's `HKWorkoutSession` /// (`WorkoutSessionManager`) is the sole recorder, and the phone writes nothing to /// Health. This helper exists purely so that *legacy* phone-estimate workouts — the /// MET-based `HKWorkout`s that app versions up to 2.x wrote from the phone — can still /// be pruned from Health when the user deletes them here. /// /// HealthKit only permits deleting samples this app authored, so those phone estimates /// delete cleanly while watch-recorded workouts (authored by the watch app's separate /// HealthKit source) are refused — the delete flow directs the user to the Health app. @MainActor final class WorkoutHealthDeleter { private let healthStore = HKHealthStore() private let log = Logger(subsystem: "dev.rzen.indie.Workouts", category: "health") private var isScreenshotRun: Bool { #if DEBUG return ScreenshotSeed.isActive #else return false #endif } /// Request the single scope a delete needs: share (write) access to the workout /// type. HealthKit requires write authorization for a sample's type in order to /// delete a sample this app authored. No read scopes — the phone reads nothing /// from Health (heart rate and calories come from the watch over WatchConnectivity). func authorizeIfNeeded() { guard HKHealthStore.isHealthDataAvailable(), !isScreenshotRun else { return } healthStore.requestAuthorization( toShare: WorkoutHealthAuthorization.phoneShare, read: [] ) { _, _ in } } /// Best-effort removal of a workout's Health record, on the user's request when /// deleting the workout in the app. Only samples this app authored (legacy phone /// estimates) can be deleted; watch-recorded workouts are refused by HealthKit and /// remain in Health until removed there. func deleteFromHealth(uuidString: String) { guard HKHealthStore.isHealthDataAvailable(), let uuid = UUID(uuidString: uuidString) else { return } guard healthStore.authorizationStatus(for: .workoutType()) == .sharingAuthorized else { log.notice("Skipping Health delete: workout sharing not authorized") return } let healthStore = self.healthStore let log = self.log let query = HKSampleQuery( sampleType: .workoutType(), predicate: HKQuery.predicateForObject(with: uuid), limit: 1, sortDescriptors: nil ) { _, samples, error in if let error { log.error("Health workout lookup failed: \(error.localizedDescription, privacy: .public)") return } guard let workout = samples?.first else { return } healthStore.delete([workout]) { _, error in if let error { log.error("Health workout delete failed: \(error.localizedDescription, privacy: .public)") } } } healthStore.execute(query) } }