import Foundation import HealthKit import CoreLocation /// Saves a completed walk as an HKWorkout(.walking) with HKWorkoutRoute so it /// shows up in Apple Health and counts toward the user's Activity rings. @MainActor final class WalkHealthSync { static let shared = WalkHealthSync() private let store = HKHealthStore() var isAvailable: Bool { HKHealthStore.isHealthDataAvailable() } /// Requests share authorization for workouts + workout routes. @discardableResult func requestAuthorization() async -> Bool { guard isAvailable else { return false } let types: Set = [ HKObjectType.workoutType(), HKSeriesType.workoutRoute() ] do { try await store.requestAuthorization(toShare: types, read: []) return true } catch { print("WalkHealthSync auth failed: \(error)") return false } } func saveWalk( points: [GPSPoint], startedAt: Date, endedAt: Date, distanceMeters: Double ) async { guard isAvailable, points.count >= 2 else { return } let configuration = HKWorkoutConfiguration() configuration.activityType = .walking configuration.locationType = .outdoor let builder = HKWorkoutBuilder( healthStore: store, configuration: configuration, device: .local() ) do { try await builder.beginCollection(at: startedAt) let distanceQuantity = HKQuantity(unit: .meter(), doubleValue: distanceMeters) let distanceSample = HKQuantitySample( type: HKQuantityType(.distanceWalkingRunning), quantity: distanceQuantity, start: startedAt, end: endedAt ) try await builder.addSamples([distanceSample]) try await builder.endCollection(at: endedAt) guard let workout = try await builder.finishWorkout() else { return } // Distribute timestamps evenly across the recorded period — our // GPSPoint doesn't carry per-point timing. let totalSeconds = endedAt.timeIntervalSince(startedAt) let interval = totalSeconds / Double(max(1, points.count - 1)) let locations = points.enumerated().map { i, p in CLLocation( coordinate: CLLocationCoordinate2D(latitude: p.lat, longitude: p.lon), altitude: p.alt ?? 0, horizontalAccuracy: 5, verticalAccuracy: 5, timestamp: startedAt.addingTimeInterval(Double(i) * interval) ) } let routeBuilder = HKWorkoutRouteBuilder(healthStore: store, device: .local()) try await routeBuilder.insertRouteData(locations) _ = try await routeBuilder.finishRoute(with: workout, metadata: nil) } catch { print("WalkHealthSync save failed: \(error)") } } }