diff --git a/HealthImport.xcodeproj/project.pbxproj b/HealthImport.xcodeproj/project.pbxproj index eeeeb83..3636071 100644 --- a/HealthImport.xcodeproj/project.pbxproj +++ b/HealthImport.xcodeproj/project.pbxproj @@ -19,7 +19,6 @@ 8850027F2B5C36A700E7D4DB /* Workout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8850027E2B5C36A700E7D4DB /* Workout.swift */; }; 885002852B5C7AD600E7D4DB /* WorkoutEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 885002842B5C7AD600E7D4DB /* WorkoutEvent.swift */; }; 885002872B5C7FA900E7D4DB /* HKWorkoutActivityType+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 885002862B5C7FA900E7D4DB /* HKWorkoutActivityType+Extensions.swift */; }; - 8850028B2B5C896C00E7D4DB /* WorkoutActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8850028A2B5C896C00E7D4DB /* WorkoutActivity.swift */; }; 8850028D2B5D0B5000E7D4DB /* WorkoutDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8850028C2B5D0B5000E7D4DB /* WorkoutDetailView.swift */; }; 8850028F2B5D0EAF00E7D4DB /* Date+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8850028E2B5D0EAF00E7D4DB /* Date+Extensions.swift */; }; 885002912B5D0F9200E7D4DB /* HKWorkoutEventType+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 885002902B5D0F9200E7D4DB /* HKWorkoutEventType+Extensions.swift */; }; @@ -78,7 +77,6 @@ 8850027E2B5C36A700E7D4DB /* Workout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Workout.swift; sourceTree = ""; }; 885002842B5C7AD600E7D4DB /* WorkoutEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkoutEvent.swift; sourceTree = ""; }; 885002862B5C7FA900E7D4DB /* HKWorkoutActivityType+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "HKWorkoutActivityType+Extensions.swift"; sourceTree = ""; }; - 8850028A2B5C896C00E7D4DB /* WorkoutActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkoutActivity.swift; sourceTree = ""; }; 8850028C2B5D0B5000E7D4DB /* WorkoutDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkoutDetailView.swift; sourceTree = ""; }; 8850028E2B5D0EAF00E7D4DB /* Date+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Date+Extensions.swift"; sourceTree = ""; }; 885002902B5D0F9200E7D4DB /* HKWorkoutEventType+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "HKWorkoutEventType+Extensions.swift"; sourceTree = ""; }; @@ -215,7 +213,6 @@ E27BC6892B5FC255003A8873 /* Sample+Unit.swift */, 8850027E2B5C36A700E7D4DB /* Workout.swift */, 8850027A2B5C35BF00E7D4DB /* Workout+SQLite.swift */, - 8850028A2B5C896C00E7D4DB /* WorkoutActivity.swift */, E27BC68F2B5FCEA4003A8873 /* WorkoutActivity+SQLite.swift */, 885002842B5C7AD600E7D4DB /* WorkoutEvent.swift */, E27BC68D2B5FCBD5003A8873 /* WorkoutEvent+SQLite.swift */, @@ -367,7 +364,6 @@ E2FDFF1C2B6BD0D20080A7B3 /* HKDatabaseFile+Interface.swift in Sources */, 885002A32B5D217600E7D4DB /* MetadataValue.swift in Sources */, E201EC7B2B6275CA005B83D3 /* Metadata.swift in Sources */, - 8850028B2B5C896C00E7D4DB /* WorkoutActivity.swift in Sources */, E27BC67A2B5D99AC003A8873 /* LocationSample.swift in Sources */, 885002952B5D147100E7D4DB /* DetailRow.swift in Sources */, E201EC732B626A30005B83D3 /* WorkoutActivity+Mock.swift in Sources */, diff --git a/HealthImport/ActivityDetailView.swift b/HealthImport/ActivityDetailView.swift index f630a95..3a46854 100644 --- a/HealthImport/ActivityDetailView.swift +++ b/HealthImport/ActivityDetailView.swift @@ -1,11 +1,12 @@ import SwiftUI +import HealthKit struct ActivityDetailView: View { @EnvironmentObject var database: HealthDatabase - let activity: WorkoutActivity + let activity: HKWorkoutActivity @State var locations: [LocationSample] = [] @@ -14,11 +15,10 @@ struct ActivityDetailView: View { var body: some View { List { DetailRow("UUID", value: activity.uuid) - DetailRow("Primary Activity", value: activity.isPrimaryActivity) - DetailRow("Activity", value: activity.activityType) - DetailRow("Location", value: activity.locationType) - DetailRow("Swimming Location", value: activity.swimmingLocationType) - DetailRow("Lap Length", value: activity.lapLength) + DetailRow("Activity", value: activity.workoutConfiguration.activityType) + DetailRow("Location", value: activity.workoutConfiguration.locationType) + DetailRow("Swimming Location", value: activity.workoutConfiguration.swimmingLocationType) + DetailRow("Lap Length", value: activity.workoutConfiguration.lapLength) DetailRow("Start", date: activity.startDate) DetailRow("End", date: activity.endDate) DetailRow("Duration", duration: activity.duration) diff --git a/HealthImport/ActivitySamplesView.swift b/HealthImport/ActivitySamplesView.swift index dbfe281..002e979 100644 --- a/HealthImport/ActivitySamplesView.swift +++ b/HealthImport/ActivitySamplesView.swift @@ -1,16 +1,17 @@ import SwiftUI import OrderedCollections +import HealthKit struct ActivitySamplesView: View { @EnvironmentObject var database: HealthDatabase - let activity: WorkoutActivity + let activity: HKWorkoutActivity @State var samples: [(type: Sample.DataType, samples: [Sample])] = [] - init(activity: WorkoutActivity) { + init(activity: HKWorkoutActivity) { self.activity = activity } diff --git a/HealthImport/HealthDatabase.swift b/HealthImport/HealthDatabase.swift index ed5160a..07ad7b9 100644 --- a/HealthImport/HealthDatabase.swift +++ b/HealthImport/HealthDatabase.swift @@ -1,6 +1,7 @@ import Foundation import SQLite import CoreLocation +import HealthKit typealias Database = Connection @@ -39,19 +40,25 @@ final class HealthDatabase: ObservableObject { } } - func locationSamples(for activity: WorkoutActivity) throws -> [LocationSample] { - try activity.locationSamples(in: database) + func locationSamples(for activity: HKWorkoutActivity) throws -> [LocationSample] { + try LocationSample.locationSamples(from: activity.startDate, to: activity.currentEndDate, in: database) } - func samples(for activity: WorkoutActivity) throws -> [Sample.DataType : [Sample]] { - try activity.samples(in: database) + func locationSampleCount(for activity: HKWorkoutActivity) throws -> Int { + try LocationSample.locationSampleCount(from: activity.startDate, to: activity.currentEndDate, in: database) } - func sampleCount(for activity: WorkoutActivity) throws -> Int { - try activity.sampleCount(in: database) + func samples(for activity: HKWorkoutActivity) throws -> [Sample.DataType : [Sample]] { + try Sample.samples(from: activity.startDate, to: activity.currentEndDate, in: database).reduce(into: [:]) { + $0[$1.dataType] = ($0[$1.dataType] ?? []) + [$1] + } } - var activities: [WorkoutActivity] { + func sampleCount(for activity: HKWorkoutActivity) throws -> Int { + try Sample.sampleCount(from: activity.startDate, to: activity.currentEndDate, in: database) + } + + var activities: [HKWorkoutActivity] { workouts.map { $0.activities }.joined().sorted() } @@ -59,11 +66,11 @@ final class HealthDatabase: ObservableObject { let activities = self.activities var current = activities.first! for next in activities.dropFirst() { - let overlap = next.startDate.timeIntervalSince(current.endDate) + let overlap = next.startDate.timeIntervalSince(current.currentEndDate) if overlap < 0 { print("Overlap \(-overlap.roundedInt) s:") - print(" Activity \(current.activityType.description): \(current.startDate.timeAndDateText) -> \(current.endDate.timeAndDateText)") - print(" Activity \(next.activityType.description): \(next.startDate.timeAndDateText) -> \(next.endDate.timeAndDateText)") + print(" Activity \(current.workoutConfiguration.activityType.description): \(current.startDate.timeAndDateText) -> \(current.currentEndDate.timeAndDateText)") + print(" Activity \(next.workoutConfiguration.activityType.description): \(next.startDate.timeAndDateText) -> \(next.currentEndDate.timeAndDateText)") } current = next } @@ -73,3 +80,10 @@ final class HealthDatabase: ObservableObject { self.init(fileUrl: .init(filePath: "/"), database: database) } } + +private extension HKWorkoutActivity { + + var currentEndDate: Date { + endDate ?? Date() + } +} diff --git a/HealthImport/Model/Workout+SQLite.swift b/HealthImport/Model/Workout+SQLite.swift index 5bcb517..b75a795 100644 --- a/HealthImport/Model/Workout+SQLite.swift +++ b/HealthImport/Model/Workout+SQLite.swift @@ -31,7 +31,7 @@ extension Workout { let id = row[columnDataId] let events = try HKWorkoutEventTable.events(for: id, in: database) - let activities = try WorkoutActivity.activities(for: id, in: database) + let activities = try WorkoutActivityTable.activities(for: id, in: database) let metadata = try Metadata.metadata(for: id, in: database, keyMap: metadataKeys) return .init( id: id, @@ -74,9 +74,14 @@ extension Workout { for event in element.events { try event.insert(in: database, dataId: dataId) } - for activity in element.activities { - try activity.insert(in: database, dataId: dataId) + + if let activity = element.activities.first { + try WorkoutActivityTable.insert(activity, isPrimaryActivity: true, dataId: dataId, in: database) } + for activity in element.activities.dropFirst() { + try WorkoutActivityTable.insert(activity, isPrimaryActivity: false, dataId: dataId, in: database) + } + for (key, value) in element.metadata { try Metadata.insert(value, for: key, of: dataId, in: database) } diff --git a/HealthImport/Model/Workout.swift b/HealthImport/Model/Workout.swift index ddd694b..ed02171 100644 --- a/HealthImport/Model/Workout.swift +++ b/HealthImport/Model/Workout.swift @@ -25,8 +25,8 @@ struct Workout { let events: [HKWorkoutEvent] - let activities: [WorkoutActivity] - + let activities: [HKWorkoutActivity] + let metadata: OrderedDictionary var firstActivityDate: Date? { @@ -49,10 +49,10 @@ struct Workout { } var typeString: String { - activities.first?.activityType.description ?? "Unknown activity" + activities.first?.workoutConfiguration.activityType.description ?? "Unknown activity" } - init(id: Int, totalDistance: Double? = nil, goalType: Int? = nil, goal: Double? = nil, condenserVersion: Int? = nil, condenserDate: Date? = nil, events: [HKWorkoutEvent] = [], activities: [WorkoutActivity] = [], metadata: [Metadata.Key : Metadata.Value] = [:]) { + init(id: Int, totalDistance: Double? = nil, goalType: Int? = nil, goal: Double? = nil, condenserVersion: Int? = nil, condenserDate: Date? = nil, events: [HKWorkoutEvent] = [], activities: [HKWorkoutActivity] = [], metadata: [Metadata.Key : Metadata.Value] = [:]) { self.id = id self.totalDistance = totalDistance self.goal = .init(goalType: goalType, goal: goal) diff --git a/HealthImport/Model/WorkoutActivity+SQLite.swift b/HealthImport/Model/WorkoutActivity+SQLite.swift index 28fffc3..551fa7e 100644 --- a/HealthImport/Model/WorkoutActivity+SQLite.swift +++ b/HealthImport/Model/WorkoutActivity+SQLite.swift @@ -2,7 +2,14 @@ import Foundation import SQLite import HealthKit -extension WorkoutActivity { +extension HKWorkoutActivity: Comparable { + + public static func < (lhs: HKWorkoutActivity, rhs: HKWorkoutActivity) -> Bool { + lhs.startDate < rhs.startDate + } +} + +enum WorkoutActivityTable { private static let table = Table("workout_activities") @@ -30,30 +37,38 @@ extension WorkoutActivity { private static let columnMetadata = Expression("metadata") - private static func readAll(in database: Connection) throws -> [Self] { - try database.prepare(table).map(from) + private static func readAll(in database: Connection) throws -> [HKWorkoutActivity] { + try database.prepare(table).map(activity) } - private static func from(row: Row) throws -> WorkoutActivity { - .init( - id: row[columnId], - uuid: row[columnUUID], - isPrimaryActivity: row[columnIsPrimaryActivity], - activityType: .init(rawValue: UInt(row[columnActivityType]))!, - locationType: .init(rawValue: row[columnLocationType])!, - swimmingLocationType: .init(rawValue: row[columnSwimmingLocationType])!, - lapLength: try row[columnLapLength].map(lapLength), - startDate: Date(timeIntervalSinceReferenceDate: row[columnStartDate]), - endDate: Date(timeIntervalSinceReferenceDate: row[columnEndDate]), - duration: row[columnDuration], - metadata: row[columnMetadata]) + private static func activity(from row: Row) throws -> HKWorkoutActivity { + let configuration = HKWorkoutConfiguration() + configuration.lapLength = try row[columnLapLength].map(lapLength) + configuration.activityType = .init(rawValue: UInt(row[columnActivityType]))! + configuration.locationType = .init(rawValue: row[columnLocationType])! + configuration.swimmingLocationType = .init(rawValue: row[columnSwimmingLocationType])! + + let start = Date(timeIntervalSinceReferenceDate: row[columnStartDate]) + let end = Date(timeIntervalSinceReferenceDate: row[columnEndDate]) + let uuid = row[columnUUID].uuidString + let metadata: [String : Any] = [HKMetadataKeyExternalUUID : uuid] + // duration: row[columnDuration] + // isPrimaryActivity: row[columnIsPrimaryActivity] + + // metadata: row[columnMetadata] + #warning("Decode metadata") + return .init( + workoutConfiguration: configuration, + start: start, + end: end, + metadata: metadata) } - static func activities(for workoutId: Int, in database: Connection) throws -> [Self] { - try database.prepare(table.filter(columnOwnerId == workoutId)).map(from) + static func activities(for workoutId: Int, in database: Connection) throws -> [HKWorkoutActivity] { + try database.prepare(table.filter(columnOwnerId == workoutId)).map(activity) } - static func createTable(in database: Connection) throws { + static func create(in database: Connection) throws { //try database.execute("CREATE TABLE workout_activities (ROWID INTEGER PRIMARY KEY AUTOINCREMENT, uuid BLOB UNIQUE NOT NULL, owner_id INTEGER NOT NULL REFERENCES workouts(data_id) ON DELETE CASCADE, is_primary_activity INTEGER NOT NULL, activity_type INTEGER NOT NULL, location_type INTEGER NOT NULL, swimming_location_type INTEGER NOT NULL, lap_length BLOB, start_date REAL NOT NULL, end_date REAL NOT NULL, duration REAL NOT NULL, metadata BLOB)") try database.run(table.create { t in t.column(columnId, primaryKey: .autoincrement) @@ -71,30 +86,36 @@ extension WorkoutActivity { }) } - func insert(in database: Connection, dataId: Int) throws { - try WorkoutActivity.insert(self, dataId: dataId, in: database) - } - - private static func insert(_ element: WorkoutActivity, dataId: Int, in database: Connection) throws { + static func insert(_ element: HKWorkoutActivity, isPrimaryActivity: Bool, dataId: Int, in database: Connection) throws { try database.run(table.insert( - columnUUID <- element.uuid, + columnUUID <- (element.externalUUID ?? element.uuid).uuidString.data(using: .utf8)!, columnOwnerId <- dataId, - columnIsPrimaryActivity <- element.isPrimaryActivity, - columnActivityType <- Int(element.activityType.rawValue), - columnLocationType <- element.locationType.rawValue, - columnSwimmingLocationType <- element.swimmingLocationType.rawValue, - columnLapLength <- try element.lapLengthData(), + columnIsPrimaryActivity <- isPrimaryActivity, + columnActivityType <- Int(element.workoutConfiguration.activityType.rawValue), + columnLocationType <- element.workoutConfiguration.locationType.rawValue, + columnSwimmingLocationType <- element.workoutConfiguration.swimmingLocationType.rawValue, + columnLapLength <- try lapLengthData(lapLength: element.workoutConfiguration.lapLength), columnStartDate <- element.startDate.timeIntervalSinceReferenceDate, - columnEndDate <- element.endDate.timeIntervalSinceReferenceDate, - columnDuration <- element.duration, - columnMetadata <- element.metadata) + columnEndDate <- element.endDate?.timeIntervalSinceReferenceDate ?? element.startDate.addingTimeInterval(element.duration).timeIntervalSinceReferenceDate, + columnDuration <- element.duration) + //columnMetadata <- element.metadata) ) } } -private extension WorkoutActivity { +private extension HKWorkoutActivity { - func lapLengthData() throws -> Data? { + var externalUUID: UUID? { + guard let string = metadata?[HKMetadataKeyExternalUUID] as? String else { + return nil + } + return UUID(uuidString: string) + } +} + +private extension WorkoutActivityTable { + + static func lapLengthData(lapLength: HKQuantity?) throws -> Data? { try lapLength.map { try NSKeyedArchiver.archivedData(withRootObject: $0, requiringSecureCoding: false) } } @@ -102,3 +123,19 @@ private extension WorkoutActivity { try NSKeyedUnarchiver.unarchivedObject(ofClass: HKQuantity.self, from: data) } } + +private extension Data { + + var uuidString: String { + let h = Array(self) + let parts = [h[0..<4], h[4..<6], h[6..<8], h[8..<10], h[10..<16]] + return parts.map { Data($0).hex }.joined(separator: "-") + } +} + +private extension UUID { + + init?(data: Data) { + self.init(uuidString: data.uuidString) + } +} diff --git a/HealthImport/Model/WorkoutActivity.swift b/HealthImport/Model/WorkoutActivity.swift deleted file mode 100644 index 37be2af..0000000 --- a/HealthImport/Model/WorkoutActivity.swift +++ /dev/null @@ -1,81 +0,0 @@ -import Foundation -import HealthKit - -struct WorkoutActivity { - - private let id: Int - - let uuid: Data - - let isPrimaryActivity: Bool - - let activityType: HKWorkoutActivityType - - let locationType: HKWorkoutSessionLocationType - - let swimmingLocationType: HKWorkoutSwimmingLocationType - - let lapLength: HKQuantity? - - #warning("Fix timezone for dates") - let startDate: Date - - let endDate: Date - - let duration: TimeInterval - - let metadata: Data? - - init(id: Int, uuid: Data, isPrimaryActivity: Bool, activityType: HKWorkoutActivityType, locationType: HKWorkoutSessionLocationType, swimmingLocationType: HKWorkoutSwimmingLocationType, lapLength: HKQuantity?, startDate: Date, endDate: Date, duration: TimeInterval, metadata: Data?) { - self.id = id - self.uuid = uuid - self.isPrimaryActivity = isPrimaryActivity - self.activityType = activityType - self.locationType = locationType - self.swimmingLocationType = swimmingLocationType - self.lapLength = lapLength - self.startDate = startDate - self.endDate = endDate - self.duration = duration - self.metadata = metadata - } - - func locationSamples(in database: Database) throws -> [LocationSample] { - try LocationSample.locationSamples(from: startDate, to: endDate, in: database) - } - - func locationSampleCount(in database: Database) throws -> Int { - try LocationSample.locationSampleCount(from: startDate, to: endDate, in: database) - } - - func sampleCount(in database: Database) throws -> Int { - try Sample.sampleCount(from: startDate, to: endDate, in: database) - } - - func samples(in database: Database) throws -> [Sample.DataType : [Sample]] { - try Sample.samples(from: startDate, to: endDate, in: database).reduce(into: [:]) { - $0[$1.dataType] = ($0[$1.dataType] ?? []) + [$1] - } - } -} - -extension WorkoutActivity: Equatable { - - static func == (lhs: WorkoutActivity, rhs: WorkoutActivity) -> Bool { - lhs.id == rhs.id - } -} - -extension WorkoutActivity: Comparable { - - static func < (lhs: WorkoutActivity, rhs: WorkoutActivity) -> Bool { - lhs.startDate < rhs.startDate - } -} - -extension WorkoutActivity: Hashable { - - func hash(into hasher: inout Hasher) { - hasher.combine(id) - } -} diff --git a/HealthImport/Model/WorkoutEvent.swift b/HealthImport/Model/WorkoutEvent.swift index 8f56c4a..d8aa31f 100644 --- a/HealthImport/Model/WorkoutEvent.swift +++ b/HealthImport/Model/WorkoutEvent.swift @@ -60,7 +60,7 @@ private extension WorkoutEventMetadata.Element { } static func from(key: String, value: Any) -> Self? { - if let value = value as? UInt { + if let value = value as? UInt64 { return .with { $0.key = key $0.unsignedValue = UInt64(value) diff --git a/HealthImport/Preview Content/HealthDatabase+Mock.swift b/HealthImport/Preview Content/HealthDatabase+Mock.swift index 51e0651..46f56a2 100644 --- a/HealthImport/Preview Content/HealthDatabase+Mock.swift +++ b/HealthImport/Preview Content/HealthDatabase+Mock.swift @@ -20,7 +20,7 @@ extension HealthDatabase { try Workout.createTable(in: database) try HKWorkoutEventTable.create(in: database) - try WorkoutActivity.createTable(in: database) + try WorkoutActivityTable.create(in: database) try Metadata.createTables(in: database) try Workout.mock1.insert(in: database) diff --git a/HealthImport/Preview Content/WorkoutActivity+Mock.swift b/HealthImport/Preview Content/WorkoutActivity+Mock.swift index db8f748..77422a1 100644 --- a/HealthImport/Preview Content/WorkoutActivity+Mock.swift +++ b/HealthImport/Preview Content/WorkoutActivity+Mock.swift @@ -1,18 +1,20 @@ import Foundation import HealthKit -extension WorkoutActivity { +extension HKWorkoutActivity { - static var mock1: WorkoutActivity = .init( - id: 744, - uuid: Data(hex: "0e0019a803d541e7b240feb7a360911a")!, - isPrimaryActivity: true, - activityType: .init(rawValue: 24)!, - locationType: .init(rawValue: 3)!, - swimmingLocationType: .init(rawValue: 0)!, - lapLength: nil, - startDate: .init(timeIntervalSinceReferenceDate: 702107518.84307), - endDate: .init(timeIntervalSinceReferenceDate: 702143189.432644), - duration: 27405.1830769777, - metadata: nil) + static var mock1: HKWorkoutActivity = { + let configuration = HKWorkoutConfiguration() + configuration.activityType = .init(rawValue: 24)! + configuration.locationType = .init(rawValue: 3)! + configuration.swimmingLocationType = .init(rawValue: 0)! + configuration.lapLength = nil + + let metadata: [String: Any] = [ HKMetadataKeyExternalUUID : "0E0019A803D541E7B240FEB7A360911A"] + return .init( + workoutConfiguration: configuration, + start: .init(timeIntervalSinceReferenceDate: 702107518.84307), + end: .init(timeIntervalSinceReferenceDate: 702143189.432644), + metadata: metadata) + }() } diff --git a/HealthImport/WorkoutDetailView.swift b/HealthImport/WorkoutDetailView.swift index 386c6b2..adb88be 100644 --- a/HealthImport/WorkoutDetailView.swift +++ b/HealthImport/WorkoutDetailView.swift @@ -22,7 +22,7 @@ struct WorkoutDetailView: View { Section("Activities") { ForEach(workout.activities, id: \.startDate) { activity in NavigationLink(value: activity) { - DetailRow(activity.activityType.description, + DetailRow(activity.workoutConfiguration.activityType.description, date: activity.startDate) } @@ -47,7 +47,7 @@ struct WorkoutDetailView: View { } } .navigationTitle(workout.typeString) - .navigationDestination(for: WorkoutActivity.self) { activity in + .navigationDestination(for: HKWorkoutActivity.self) { activity in ActivityDetailView(activity: activity) .environmentObject(database) }