diff --git a/StrandiOS/Health/HealthKitBridge.swift b/StrandiOS/Health/HealthKitBridge.swift index bcb3b8bfab..4353e24a82 100644 --- a/StrandiOS/Health/HealthKitBridge.swift +++ b/StrandiOS/Health/HealthKitBridge.swift @@ -111,6 +111,7 @@ final class HealthKitBridge: ObservableObject { } if let sleep = HKObjectType.categoryType(forIdentifier: .sleepAnalysis) { s.insert(sleep) } s.insert(HKObjectType.workoutType()) + s.insert(HKSeriesType.workoutRoute()) return s } @@ -1242,7 +1243,35 @@ final class HealthKitBridge: ObservableObject { } if !extras.isEmpty { try await builder.addSamples(extras) } try await builder.endCollection(at: end) - _ = try await builder.finishWorkout() + let workout = try await builder.finishWorkout() + + // #1314: workout route (GPX) write-back. Load the encoded polyline from the Apple-only + // side-store and attach it as a separate series to the workout we just finished. + // We only do this if the workout type supports a distance (GPS) route. + if let workout, + Self.distanceTypeId(forSport: row.sport) != nil, + store.authorizationStatus(for: HKSeriesType.workoutRoute()) == .sharingAuthorized, + let route = RouteStore.load(startTs: row.startTs, sport: row.sport), + !route.polyline.isEmpty { + do { + let routeBuilder = HKWorkoutRouteBuilder(healthStore: store, device: .local()) + let points = RouteMath.decode(route.polyline) + let duration = end.timeIntervalSince(start) + let locs = points.enumerated().map { i, p in + // Interpolate timestamps across the workout's span, since RoutePoint is lat/lon only. + let ts = start.addingTimeInterval(duration * Double(i) / Double(max(points.count - 1, 1))) + return CLLocation(coordinate: CLLocationCoordinate2D(latitude: p.lat, longitude: p.lon), + altitude: 0, horizontalAccuracy: 5, verticalAccuracy: -1, timestamp: ts) + } + if !locs.isEmpty { + try await routeBuilder.insertRouteData(locs) + try await routeBuilder.finishRoute(with: workout, metadata: nil) + } + } catch { + // Route is an enrichment; don't fail the whole workout write if the route fails. + print("Failed to attach route to HealthKit workout: \(error)") + } + } } catch { builder.discardWorkout() throw error