Extract metadata
This commit is contained in:
parent
8ace8e9319
commit
0088e5df2e
@ -31,6 +31,12 @@
|
|||||||
885002992B5D15D200E7D4DB /* HKWorkoutSwimmingLocationType+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 885002982B5D15D200E7D4DB /* HKWorkoutSwimmingLocationType+Extensions.swift */; };
|
885002992B5D15D200E7D4DB /* HKWorkoutSwimmingLocationType+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 885002982B5D15D200E7D4DB /* HKWorkoutSwimmingLocationType+Extensions.swift */; };
|
||||||
8850029B2B5D16E200E7D4DB /* TimeInterval+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8850029A2B5D16E200E7D4DB /* TimeInterval+Extensions.swift */; };
|
8850029B2B5D16E200E7D4DB /* TimeInterval+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8850029A2B5D16E200E7D4DB /* TimeInterval+Extensions.swift */; };
|
||||||
8850029D2B5D197300E7D4DB /* EventDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8850029C2B5D197300E7D4DB /* EventDetailView.swift */; };
|
8850029D2B5D197300E7D4DB /* EventDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8850029C2B5D197300E7D4DB /* EventDetailView.swift */; };
|
||||||
|
8850029F2B5D1C7000E7D4DB /* DBMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8850029E2B5D1C7000E7D4DB /* DBMetadata.swift */; };
|
||||||
|
885002A12B5D1E7400E7D4DB /* DBMetadataKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 885002A02B5D1E7400E7D4DB /* DBMetadataKey.swift */; };
|
||||||
|
885002A32B5D217600E7D4DB /* MetadataValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 885002A22B5D217600E7D4DB /* MetadataValue.swift */; };
|
||||||
|
885002A62B5D296700E7D4DB /* Collections in Frameworks */ = {isa = PBXBuildFile; productRef = 885002A52B5D296700E7D4DB /* Collections */; };
|
||||||
|
885002A82B5D296700E7D4DB /* DequeModule in Frameworks */ = {isa = PBXBuildFile; productRef = 885002A72B5D296700E7D4DB /* DequeModule */; };
|
||||||
|
885002AA2B5D296700E7D4DB /* OrderedCollections in Frameworks */ = {isa = PBXBuildFile; productRef = 885002A92B5D296700E7D4DB /* OrderedCollections */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
@ -58,6 +64,9 @@
|
|||||||
885002982B5D15D200E7D4DB /* HKWorkoutSwimmingLocationType+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "HKWorkoutSwimmingLocationType+Extensions.swift"; sourceTree = "<group>"; };
|
885002982B5D15D200E7D4DB /* HKWorkoutSwimmingLocationType+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "HKWorkoutSwimmingLocationType+Extensions.swift"; sourceTree = "<group>"; };
|
||||||
8850029A2B5D16E200E7D4DB /* TimeInterval+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TimeInterval+Extensions.swift"; sourceTree = "<group>"; };
|
8850029A2B5D16E200E7D4DB /* TimeInterval+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TimeInterval+Extensions.swift"; sourceTree = "<group>"; };
|
||||||
8850029C2B5D197300E7D4DB /* EventDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventDetailView.swift; sourceTree = "<group>"; };
|
8850029C2B5D197300E7D4DB /* EventDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventDetailView.swift; sourceTree = "<group>"; };
|
||||||
|
8850029E2B5D1C7000E7D4DB /* DBMetadata.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DBMetadata.swift; sourceTree = "<group>"; };
|
||||||
|
885002A02B5D1E7400E7D4DB /* DBMetadataKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DBMetadataKey.swift; sourceTree = "<group>"; };
|
||||||
|
885002A22B5D217600E7D4DB /* MetadataValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetadataValue.swift; sourceTree = "<group>"; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
@ -65,7 +74,10 @@
|
|||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
885002A62B5D296700E7D4DB /* Collections in Frameworks */,
|
||||||
885002772B5C2FC400E7D4DB /* SQLite in Frameworks */,
|
885002772B5C2FC400E7D4DB /* SQLite in Frameworks */,
|
||||||
|
885002AA2B5D296700E7D4DB /* OrderedCollections in Frameworks */,
|
||||||
|
885002A82B5D296700E7D4DB /* DequeModule in Frameworks */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@ -130,6 +142,8 @@
|
|||||||
8850027A2B5C35BF00E7D4DB /* DBWorkout.swift */,
|
8850027A2B5C35BF00E7D4DB /* DBWorkout.swift */,
|
||||||
8850027C2B5C360300E7D4DB /* DBWorkoutEvent.swift */,
|
8850027C2B5C360300E7D4DB /* DBWorkoutEvent.swift */,
|
||||||
885002882B5C873C00E7D4DB /* DBWorkoutActivity.swift */,
|
885002882B5C873C00E7D4DB /* DBWorkoutActivity.swift */,
|
||||||
|
8850029E2B5D1C7000E7D4DB /* DBMetadata.swift */,
|
||||||
|
885002A02B5D1E7400E7D4DB /* DBMetadataKey.swift */,
|
||||||
);
|
);
|
||||||
path = "Database Entries";
|
path = "Database Entries";
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -140,6 +154,7 @@
|
|||||||
8850027E2B5C36A700E7D4DB /* Workout.swift */,
|
8850027E2B5C36A700E7D4DB /* Workout.swift */,
|
||||||
885002842B5C7AD600E7D4DB /* WorkoutEvent.swift */,
|
885002842B5C7AD600E7D4DB /* WorkoutEvent.swift */,
|
||||||
8850028A2B5C896C00E7D4DB /* WorkoutActivity.swift */,
|
8850028A2B5C896C00E7D4DB /* WorkoutActivity.swift */,
|
||||||
|
885002A22B5D217600E7D4DB /* MetadataValue.swift */,
|
||||||
);
|
);
|
||||||
path = Model;
|
path = Model;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -176,6 +191,9 @@
|
|||||||
name = HealthImport;
|
name = HealthImport;
|
||||||
packageProductDependencies = (
|
packageProductDependencies = (
|
||||||
885002762B5C2FC400E7D4DB /* SQLite */,
|
885002762B5C2FC400E7D4DB /* SQLite */,
|
||||||
|
885002A52B5D296700E7D4DB /* Collections */,
|
||||||
|
885002A72B5D296700E7D4DB /* DequeModule */,
|
||||||
|
885002A92B5D296700E7D4DB /* OrderedCollections */,
|
||||||
);
|
);
|
||||||
productName = HealthImport;
|
productName = HealthImport;
|
||||||
productReference = 885002572B5C273C00E7D4DB /* HealthImport.app */;
|
productReference = 885002572B5C273C00E7D4DB /* HealthImport.app */;
|
||||||
@ -207,6 +225,7 @@
|
|||||||
mainGroup = 8850024E2B5C273C00E7D4DB;
|
mainGroup = 8850024E2B5C273C00E7D4DB;
|
||||||
packageReferences = (
|
packageReferences = (
|
||||||
885002752B5C2FC400E7D4DB /* XCRemoteSwiftPackageReference "SQLite" */,
|
885002752B5C2FC400E7D4DB /* XCRemoteSwiftPackageReference "SQLite" */,
|
||||||
|
885002A42B5D296700E7D4DB /* XCRemoteSwiftPackageReference "swift-collections" */,
|
||||||
);
|
);
|
||||||
productRefGroup = 885002582B5C273C00E7D4DB /* Products */;
|
productRefGroup = 885002582B5C273C00E7D4DB /* Products */;
|
||||||
projectDirPath = "";
|
projectDirPath = "";
|
||||||
@ -245,10 +264,13 @@
|
|||||||
885002852B5C7AD600E7D4DB /* WorkoutEvent.swift in Sources */,
|
885002852B5C7AD600E7D4DB /* WorkoutEvent.swift in Sources */,
|
||||||
885002912B5D0F9200E7D4DB /* HKWorkoutEventType+Extensions.swift in Sources */,
|
885002912B5D0F9200E7D4DB /* HKWorkoutEventType+Extensions.swift in Sources */,
|
||||||
8850027F2B5C36A700E7D4DB /* Workout.swift in Sources */,
|
8850027F2B5C36A700E7D4DB /* Workout.swift in Sources */,
|
||||||
|
885002A12B5D1E7400E7D4DB /* DBMetadataKey.swift in Sources */,
|
||||||
885002712B5C299900E7D4DB /* HealthDatabase.swift in Sources */,
|
885002712B5C299900E7D4DB /* HealthDatabase.swift in Sources */,
|
||||||
885002932B5D129300E7D4DB /* ActivityDetailView.swift in Sources */,
|
885002932B5D129300E7D4DB /* ActivityDetailView.swift in Sources */,
|
||||||
|
8850029F2B5D1C7000E7D4DB /* DBMetadata.swift in Sources */,
|
||||||
8850027B2B5C35BF00E7D4DB /* DBWorkout.swift in Sources */,
|
8850027B2B5C35BF00E7D4DB /* DBWorkout.swift in Sources */,
|
||||||
8850028D2B5D0B5000E7D4DB /* WorkoutDetailView.swift in Sources */,
|
8850028D2B5D0B5000E7D4DB /* WorkoutDetailView.swift in Sources */,
|
||||||
|
885002A32B5D217600E7D4DB /* MetadataValue.swift in Sources */,
|
||||||
8850028B2B5C896C00E7D4DB /* WorkoutActivity.swift in Sources */,
|
8850028B2B5C896C00E7D4DB /* WorkoutActivity.swift in Sources */,
|
||||||
885002952B5D147100E7D4DB /* DetailRow.swift in Sources */,
|
885002952B5D147100E7D4DB /* DetailRow.swift in Sources */,
|
||||||
8850029D2B5D197300E7D4DB /* EventDetailView.swift in Sources */,
|
8850029D2B5D197300E7D4DB /* EventDetailView.swift in Sources */,
|
||||||
@ -470,6 +492,14 @@
|
|||||||
minimumVersion = 0.14.1;
|
minimumVersion = 0.14.1;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
885002A42B5D296700E7D4DB /* XCRemoteSwiftPackageReference "swift-collections" */ = {
|
||||||
|
isa = XCRemoteSwiftPackageReference;
|
||||||
|
repositoryURL = "https://github.com/apple/swift-collections.git";
|
||||||
|
requirement = {
|
||||||
|
kind = upToNextMajorVersion;
|
||||||
|
minimumVersion = 1.0.6;
|
||||||
|
};
|
||||||
|
};
|
||||||
/* End XCRemoteSwiftPackageReference section */
|
/* End XCRemoteSwiftPackageReference section */
|
||||||
|
|
||||||
/* Begin XCSwiftPackageProductDependency section */
|
/* Begin XCSwiftPackageProductDependency section */
|
||||||
@ -478,6 +508,21 @@
|
|||||||
package = 885002752B5C2FC400E7D4DB /* XCRemoteSwiftPackageReference "SQLite" */;
|
package = 885002752B5C2FC400E7D4DB /* XCRemoteSwiftPackageReference "SQLite" */;
|
||||||
productName = SQLite;
|
productName = SQLite;
|
||||||
};
|
};
|
||||||
|
885002A52B5D296700E7D4DB /* Collections */ = {
|
||||||
|
isa = XCSwiftPackageProductDependency;
|
||||||
|
package = 885002A42B5D296700E7D4DB /* XCRemoteSwiftPackageReference "swift-collections" */;
|
||||||
|
productName = Collections;
|
||||||
|
};
|
||||||
|
885002A72B5D296700E7D4DB /* DequeModule */ = {
|
||||||
|
isa = XCSwiftPackageProductDependency;
|
||||||
|
package = 885002A42B5D296700E7D4DB /* XCRemoteSwiftPackageReference "swift-collections" */;
|
||||||
|
productName = DequeModule;
|
||||||
|
};
|
||||||
|
885002A92B5D296700E7D4DB /* OrderedCollections */ = {
|
||||||
|
isa = XCSwiftPackageProductDependency;
|
||||||
|
package = 885002A42B5D296700E7D4DB /* XCRemoteSwiftPackageReference "swift-collections" */;
|
||||||
|
productName = OrderedCollections;
|
||||||
|
};
|
||||||
/* End XCSwiftPackageProductDependency section */
|
/* End XCSwiftPackageProductDependency section */
|
||||||
};
|
};
|
||||||
rootObject = 8850024F2B5C273C00E7D4DB /* Project object */;
|
rootObject = 8850024F2B5C273C00E7D4DB /* Project object */;
|
||||||
|
@ -8,6 +8,15 @@
|
|||||||
"revision" : "7a2e3cd27de56f6d396e84f63beefd0267b55ccb",
|
"revision" : "7a2e3cd27de56f6d396e84f63beefd0267b55ccb",
|
||||||
"version" : "0.14.1"
|
"version" : "0.14.1"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "swift-collections",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/apple/swift-collections.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "d029d9d39c87bed85b1c50adee7c41795261a192",
|
||||||
|
"version" : "1.0.6"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"version" : 2
|
"version" : 2
|
||||||
|
57
HealthImport/Database Entries/DBMetadata.swift
Normal file
57
HealthImport/Database Entries/DBMetadata.swift
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
import Foundation
|
||||||
|
import SQLite
|
||||||
|
|
||||||
|
struct DBMetadata {
|
||||||
|
|
||||||
|
private static let table = Table("metadata_values")
|
||||||
|
|
||||||
|
private static let rowKeyId = Expression<Int?>("key_id")
|
||||||
|
|
||||||
|
private static let rowObjectId = Expression<Int?>("object_id")
|
||||||
|
|
||||||
|
private static let rowValueType = Expression<Int>("value_type")
|
||||||
|
|
||||||
|
private static let rowStringValue = Expression<String?>("string_value")
|
||||||
|
|
||||||
|
private static let rowNumericalValue = Expression<Double?>("numerical_value")
|
||||||
|
|
||||||
|
private static let rowDateValue = Expression<Double?>("date_value")
|
||||||
|
|
||||||
|
private static let rowDataValue = Expression<Data?>("data_value")
|
||||||
|
|
||||||
|
static func readAll(in database: Connection) throws -> [Self] {
|
||||||
|
try database.prepare(table).map(Self.init)
|
||||||
|
}
|
||||||
|
|
||||||
|
static func metadata(for workoutId: Int, in database: Connection) throws -> [Self] {
|
||||||
|
try database.prepare(table.filter(rowObjectId == workoutId)).map(Self.init)
|
||||||
|
}
|
||||||
|
|
||||||
|
let keyId: Int?
|
||||||
|
|
||||||
|
let objectId: Int?
|
||||||
|
|
||||||
|
let valueType: Int
|
||||||
|
|
||||||
|
let string: String?
|
||||||
|
|
||||||
|
let number: Double?
|
||||||
|
|
||||||
|
let date: Double?
|
||||||
|
|
||||||
|
let data: Data?
|
||||||
|
}
|
||||||
|
|
||||||
|
extension DBMetadata {
|
||||||
|
|
||||||
|
init(row: Row) {
|
||||||
|
self.keyId = row[DBMetadata.rowKeyId]
|
||||||
|
self.objectId = row[DBMetadata.rowObjectId]
|
||||||
|
self.valueType = row[DBMetadata.rowValueType]
|
||||||
|
self.string = row[DBMetadata.rowStringValue]
|
||||||
|
self.number = row[DBMetadata.rowNumericalValue]
|
||||||
|
self.date = row[DBMetadata.rowDateValue]
|
||||||
|
self.data = row[DBMetadata.rowDataValue]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
21
HealthImport/Database Entries/DBMetadataKey.swift
Normal file
21
HealthImport/Database Entries/DBMetadataKey.swift
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import Foundation
|
||||||
|
import SQLite
|
||||||
|
|
||||||
|
struct DBMetadataKey {
|
||||||
|
|
||||||
|
private static let table = Table("metadata_keys")
|
||||||
|
|
||||||
|
private static let rowId = Expression<Int>("ROWID")
|
||||||
|
|
||||||
|
private static let rowKey = Expression<String>("key")
|
||||||
|
|
||||||
|
static func key(for keyId: Int, in database: Connection) throws -> String {
|
||||||
|
try database.prepare(table.filter(rowId == keyId).limit(1)).map { $0[rowKey] }.first!
|
||||||
|
}
|
||||||
|
|
||||||
|
static func readAll(in database: Connection) throws -> [ Int: String] {
|
||||||
|
try database.prepare(table).reduce(into: [:]) { dict, row in
|
||||||
|
dict[row[rowId]] = row[rowKey]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -21,10 +21,15 @@ final class HealthDatabase: ObservableObject {
|
|||||||
func readAllWorkouts() {
|
func readAllWorkouts() {
|
||||||
do {
|
do {
|
||||||
let dbWorkouts = try DBWorkout.readAll(in: database)
|
let dbWorkouts = try DBWorkout.readAll(in: database)
|
||||||
|
let metadataKeys = try DBMetadataKey.readAll(in: database)
|
||||||
let workouts = try dbWorkouts.map { entry in
|
let workouts = try dbWorkouts.map { entry in
|
||||||
let events = try DBWorkoutEvent.events(for: entry.dataId, in: database)
|
let events = try DBWorkoutEvent.events(for: entry.dataId, in: database)
|
||||||
let activities = try DBWorkoutActivity.activities(for: entry.dataId, in: database)
|
let activities = try DBWorkoutActivity.activities(for: entry.dataId, in: database)
|
||||||
return Workout(entry: entry, events: events, activities: activities)
|
let metadata: [String : MetadataValue] = try DBMetadata.metadata(for: entry.dataId, in: database).reduce(into: [:]) { dict, item in
|
||||||
|
let key = metadataKeys[item.keyId!]!
|
||||||
|
dict[key] = MetadataValue(entry: item)
|
||||||
|
}
|
||||||
|
return Workout(entry: entry, events: events, activities: activities, metadata: metadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
|
65
HealthImport/Model/MetadataValue.swift
Normal file
65
HealthImport/Model/MetadataValue.swift
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
enum MetadataValue {
|
||||||
|
|
||||||
|
case string(value: String)
|
||||||
|
case number(value: Double)
|
||||||
|
case date(value: Date)
|
||||||
|
case numerical(value: Double, unit: String)
|
||||||
|
case data(value: Data)
|
||||||
|
|
||||||
|
enum ValueType: Int {
|
||||||
|
|
||||||
|
/// Uses only the `string_value` column
|
||||||
|
case string = 0
|
||||||
|
|
||||||
|
/// Uses only the `numerical_value` column
|
||||||
|
case number = 1
|
||||||
|
|
||||||
|
/// Uses only the `date_value` column
|
||||||
|
case date = 2
|
||||||
|
|
||||||
|
/// Uses the `string_value` column for the unit, and the `numerical_value` column for the number
|
||||||
|
case numerical = 3
|
||||||
|
|
||||||
|
/// Uses only the `data_value` column
|
||||||
|
case data = 4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension MetadataValue {
|
||||||
|
|
||||||
|
init(entry: DBMetadata) {
|
||||||
|
let valueType = ValueType(rawValue: entry.valueType)!
|
||||||
|
switch valueType {
|
||||||
|
case .string:
|
||||||
|
self = .string(value: entry.string!)
|
||||||
|
case .number:
|
||||||
|
self = .number(value: entry.number!)
|
||||||
|
case .date:
|
||||||
|
self = .date(value: .init(timeIntervalSinceReferenceDate: entry.date!))
|
||||||
|
case .numerical:
|
||||||
|
self = .numerical(value: entry.number!, unit: entry.string!)
|
||||||
|
case .data:
|
||||||
|
self = .data(value: entry.data!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension MetadataValue: CustomStringConvertible {
|
||||||
|
|
||||||
|
var description: String {
|
||||||
|
switch self {
|
||||||
|
case .string(let value):
|
||||||
|
return value
|
||||||
|
case .number(let value):
|
||||||
|
return "\(value)"
|
||||||
|
case .date(let value):
|
||||||
|
return value.timeAndDateText
|
||||||
|
case .numerical(let value, let unit):
|
||||||
|
return String(format: "%.3f %s", value, unit)
|
||||||
|
case .data(let value):
|
||||||
|
return value.description
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
import Collections
|
||||||
|
|
||||||
private let df: DateFormatter = {
|
private let df: DateFormatter = {
|
||||||
let df = DateFormatter()
|
let df = DateFormatter()
|
||||||
@ -28,6 +28,8 @@ struct Workout {
|
|||||||
|
|
||||||
let activities: [WorkoutActivity]
|
let activities: [WorkoutActivity]
|
||||||
|
|
||||||
|
let metadata: OrderedDictionary<String, MetadataValue>
|
||||||
|
|
||||||
var firstActivityDate: Date? {
|
var firstActivityDate: Date? {
|
||||||
activities.map { $0.startDate }.min()
|
activities.map { $0.startDate }.min()
|
||||||
}
|
}
|
||||||
@ -51,7 +53,7 @@ struct Workout {
|
|||||||
activities.first?.activityType.description ?? "Unknown activity"
|
activities.first?.activityType.description ?? "Unknown activity"
|
||||||
}
|
}
|
||||||
|
|
||||||
init(id: Int, totalDistance: Double? = nil, goalType: Int? = nil, goal: Double? = nil, condenserVersion: Int? = nil, condenserDate: Date? = nil, events: [WorkoutEvent] = [], activities: [WorkoutActivity] = []) {
|
init(id: Int, totalDistance: Double? = nil, goalType: Int? = nil, goal: Double? = nil, condenserVersion: Int? = nil, condenserDate: Date? = nil, events: [WorkoutEvent] = [], activities: [WorkoutActivity] = [], metadata: [String : MetadataValue] = [:]) {
|
||||||
self.id = id
|
self.id = id
|
||||||
self.totalDistance = totalDistance
|
self.totalDistance = totalDistance
|
||||||
self.goalType = goalType
|
self.goalType = goalType
|
||||||
@ -60,12 +62,13 @@ struct Workout {
|
|||||||
self.condenserDate = condenserDate
|
self.condenserDate = condenserDate
|
||||||
self.events = events
|
self.events = events
|
||||||
self.activities = activities
|
self.activities = activities
|
||||||
|
self.metadata = .init(uniqueKeys: metadata.keys, values: metadata.values)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Workout {
|
extension Workout {
|
||||||
|
|
||||||
init(entry: DBWorkout, events: [DBWorkoutEvent], activities: [DBWorkoutActivity]) {
|
init(entry: DBWorkout, events: [DBWorkoutEvent], activities: [DBWorkoutActivity], metadata: [String : MetadataValue]) {
|
||||||
self.id = entry.dataId
|
self.id = entry.dataId
|
||||||
self.totalDistance = entry.totalDistance
|
self.totalDistance = entry.totalDistance
|
||||||
self.goalType = entry.goalType
|
self.goalType = entry.goalType
|
||||||
@ -74,6 +77,7 @@ extension Workout {
|
|||||||
self.condenserDate = entry.condenserDate.map { Date(timeIntervalSinceReferenceDate: $0) }
|
self.condenserDate = entry.condenserDate.map { Date(timeIntervalSinceReferenceDate: $0) }
|
||||||
self.events = events.map(WorkoutEvent.init)
|
self.events = events.map(WorkoutEvent.init)
|
||||||
self.activities = activities.map(WorkoutActivity.init)
|
self.activities = activities.map(WorkoutActivity.init)
|
||||||
|
self.metadata = .init(uniqueKeys: metadata.keys, values: metadata.values)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import Collections
|
||||||
|
|
||||||
struct WorkoutDetailView: View {
|
struct WorkoutDetailView: View {
|
||||||
|
|
||||||
@ -37,6 +38,11 @@ struct WorkoutDetailView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if !workout.metadata.isEmpty {
|
||||||
|
ForEach(workout.metadata.elements, id:\.key) { (key, value) in
|
||||||
|
DetailRow(key, value: value)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.navigationTitle(workout.typeString)
|
.navigationTitle(workout.typeString)
|
||||||
.navigationDestination(for: WorkoutActivity.self) { activity in
|
.navigationDestination(for: WorkoutActivity.self) { activity in
|
||||||
@ -81,3 +87,8 @@ struct WorkoutDetailView: View {
|
|||||||
]))
|
]))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension String: Identifiable {
|
||||||
|
|
||||||
|
public var id: Self { self }
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user