Switch to HKWorkoutEvent
This commit is contained in:
parent
dbe088a402
commit
c36ee29afb
24
EventMetadata.proto
Normal file
24
EventMetadata.proto
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
// Wrapper for workout event metadata
|
||||||
|
message WorkoutEventMetadata {
|
||||||
|
|
||||||
|
// All metadata elements
|
||||||
|
repeated Element elements = 1;
|
||||||
|
|
||||||
|
message Element {
|
||||||
|
|
||||||
|
string key = 1;
|
||||||
|
|
||||||
|
optional uint64 unsignedValue = 4;
|
||||||
|
|
||||||
|
Quantity quantity = 6;
|
||||||
|
|
||||||
|
message Quantity {
|
||||||
|
|
||||||
|
double value = 1;
|
||||||
|
|
||||||
|
string unit = 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -58,6 +58,8 @@
|
|||||||
E27BC6962B5FD61D003A8873 /* WorkoutEvent+Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = E27BC6952B5FD61D003A8873 /* WorkoutEvent+Mock.swift */; };
|
E27BC6962B5FD61D003A8873 /* WorkoutEvent+Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = E27BC6952B5FD61D003A8873 /* WorkoutEvent+Mock.swift */; };
|
||||||
E27BC6982B5FD76F003A8873 /* Data+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E27BC6972B5FD76F003A8873 /* Data+Extensions.swift */; };
|
E27BC6982B5FD76F003A8873 /* Data+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E27BC6972B5FD76F003A8873 /* Data+Extensions.swift */; };
|
||||||
E2FDFF162B6AFD990080A7B3 /* BinaryCodable in Frameworks */ = {isa = PBXBuildFile; productRef = E2FDFF152B6AFD990080A7B3 /* BinaryCodable */; };
|
E2FDFF162B6AFD990080A7B3 /* BinaryCodable in Frameworks */ = {isa = PBXBuildFile; productRef = E2FDFF152B6AFD990080A7B3 /* BinaryCodable */; };
|
||||||
|
E2FDFF202B6BE34C0080A7B3 /* SwiftProtobuf in Frameworks */ = {isa = PBXBuildFile; productRef = E2FDFF1F2B6BE34C0080A7B3 /* SwiftProtobuf */; };
|
||||||
|
E2FDFF222B6BE35B0080A7B3 /* EventMetadata.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FDFF212B6BE35B0080A7B3 /* EventMetadata.pb.swift */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
@ -108,6 +110,7 @@
|
|||||||
E27BC6932B5FD587003A8873 /* Workout+Mock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Workout+Mock.swift"; sourceTree = "<group>"; };
|
E27BC6932B5FD587003A8873 /* Workout+Mock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Workout+Mock.swift"; sourceTree = "<group>"; };
|
||||||
E27BC6952B5FD61D003A8873 /* WorkoutEvent+Mock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WorkoutEvent+Mock.swift"; sourceTree = "<group>"; };
|
E27BC6952B5FD61D003A8873 /* WorkoutEvent+Mock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WorkoutEvent+Mock.swift"; sourceTree = "<group>"; };
|
||||||
E27BC6972B5FD76F003A8873 /* Data+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Data+Extensions.swift"; sourceTree = "<group>"; };
|
E27BC6972B5FD76F003A8873 /* Data+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Data+Extensions.swift"; sourceTree = "<group>"; };
|
||||||
|
E2FDFF212B6BE35B0080A7B3 /* EventMetadata.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EventMetadata.pb.swift; sourceTree = "<group>"; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
@ -120,6 +123,7 @@
|
|||||||
885002772B5C2FC400E7D4DB /* SQLite in Frameworks */,
|
885002772B5C2FC400E7D4DB /* SQLite in Frameworks */,
|
||||||
885002AA2B5D296700E7D4DB /* OrderedCollections in Frameworks */,
|
885002AA2B5D296700E7D4DB /* OrderedCollections in Frameworks */,
|
||||||
885002A82B5D296700E7D4DB /* DequeModule in Frameworks */,
|
885002A82B5D296700E7D4DB /* DequeModule in Frameworks */,
|
||||||
|
E2FDFF202B6BE34C0080A7B3 /* SwiftProtobuf in Frameworks */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@ -198,6 +202,7 @@
|
|||||||
885002812B5C37B700E7D4DB /* Model */ = {
|
885002812B5C37B700E7D4DB /* Model */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
E2FDFF212B6BE35B0080A7B3 /* EventMetadata.pb.swift */,
|
||||||
E201EC802B631708005B83D3 /* Goal.swift */,
|
E201EC802B631708005B83D3 /* Goal.swift */,
|
||||||
E27BC6792B5D99AC003A8873 /* LocationSample.swift */,
|
E27BC6792B5D99AC003A8873 /* LocationSample.swift */,
|
||||||
E201EC7A2B6275CA005B83D3 /* Metadata.swift */,
|
E201EC7A2B6275CA005B83D3 /* Metadata.swift */,
|
||||||
@ -235,6 +240,16 @@
|
|||||||
path = Support;
|
path = Support;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
E2FDFF1D2B6BD1F00080A7B3 /* API */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
E2FDFF172B6BB61D0080A7B3 /* HKHealthStoreInterface.swift */,
|
||||||
|
E2FDFF192B6BB6A40080A7B3 /* HKHealthStore+Interface.swift */,
|
||||||
|
E2FDFF1B2B6BD0D20080A7B3 /* HKDatabaseFile+Interface.swift */,
|
||||||
|
);
|
||||||
|
path = API;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
/* End PBXGroup section */
|
/* End PBXGroup section */
|
||||||
|
|
||||||
/* Begin PBXNativeTarget section */
|
/* Begin PBXNativeTarget section */
|
||||||
@ -257,6 +272,7 @@
|
|||||||
885002A72B5D296700E7D4DB /* DequeModule */,
|
885002A72B5D296700E7D4DB /* DequeModule */,
|
||||||
885002A92B5D296700E7D4DB /* OrderedCollections */,
|
885002A92B5D296700E7D4DB /* OrderedCollections */,
|
||||||
E2FDFF152B6AFD990080A7B3 /* BinaryCodable */,
|
E2FDFF152B6AFD990080A7B3 /* BinaryCodable */,
|
||||||
|
E2FDFF1F2B6BE34C0080A7B3 /* SwiftProtobuf */,
|
||||||
);
|
);
|
||||||
productName = HealthImport;
|
productName = HealthImport;
|
||||||
productReference = 885002572B5C273C00E7D4DB /* HealthImport.app */;
|
productReference = 885002572B5C273C00E7D4DB /* HealthImport.app */;
|
||||||
@ -290,6 +306,7 @@
|
|||||||
885002752B5C2FC400E7D4DB /* XCRemoteSwiftPackageReference "SQLite" */,
|
885002752B5C2FC400E7D4DB /* XCRemoteSwiftPackageReference "SQLite" */,
|
||||||
885002A42B5D296700E7D4DB /* XCRemoteSwiftPackageReference "swift-collections" */,
|
885002A42B5D296700E7D4DB /* XCRemoteSwiftPackageReference "swift-collections" */,
|
||||||
E2FDFF142B6AFD990080A7B3 /* XCRemoteSwiftPackageReference "BinaryCodable" */,
|
E2FDFF142B6AFD990080A7B3 /* XCRemoteSwiftPackageReference "BinaryCodable" */,
|
||||||
|
E2FDFF1E2B6BE34C0080A7B3 /* XCRemoteSwiftPackageReference "swift-protobuf" */,
|
||||||
);
|
);
|
||||||
productRefGroup = 885002582B5C273C00E7D4DB /* Products */;
|
productRefGroup = 885002582B5C273C00E7D4DB /* Products */;
|
||||||
projectDirPath = "";
|
projectDirPath = "";
|
||||||
@ -336,6 +353,7 @@
|
|||||||
E27BC6902B5FCEA4003A8873 /* WorkoutActivity+SQLite.swift in Sources */,
|
E27BC6902B5FCEA4003A8873 /* WorkoutActivity+SQLite.swift in Sources */,
|
||||||
E201EC772B626FC1005B83D3 /* MetadataKey.swift in Sources */,
|
E201EC772B626FC1005B83D3 /* MetadataKey.swift in Sources */,
|
||||||
8850027F2B5C36A700E7D4DB /* Workout.swift in Sources */,
|
8850027F2B5C36A700E7D4DB /* Workout.swift in Sources */,
|
||||||
|
E2FDFF222B6BE35B0080A7B3 /* EventMetadata.pb.swift in Sources */,
|
||||||
885002712B5C299900E7D4DB /* HealthDatabase.swift in Sources */,
|
885002712B5C299900E7D4DB /* HealthDatabase.swift in Sources */,
|
||||||
E27BC6842B5E76A4003A8873 /* Location+Mock.swift in Sources */,
|
E27BC6842B5E76A4003A8873 /* Location+Mock.swift in Sources */,
|
||||||
885002932B5D129300E7D4DB /* ActivityDetailView.swift in Sources */,
|
885002932B5D129300E7D4DB /* ActivityDetailView.swift in Sources */,
|
||||||
@ -591,6 +609,14 @@
|
|||||||
minimumVersion = 2.0.3;
|
minimumVersion = 2.0.3;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
E2FDFF1E2B6BE34C0080A7B3 /* XCRemoteSwiftPackageReference "swift-protobuf" */ = {
|
||||||
|
isa = XCRemoteSwiftPackageReference;
|
||||||
|
repositoryURL = "https://github.com/apple/swift-protobuf.git";
|
||||||
|
requirement = {
|
||||||
|
kind = upToNextMajorVersion;
|
||||||
|
minimumVersion = 1.25.2;
|
||||||
|
};
|
||||||
|
};
|
||||||
/* End XCRemoteSwiftPackageReference section */
|
/* End XCRemoteSwiftPackageReference section */
|
||||||
|
|
||||||
/* Begin XCSwiftPackageProductDependency section */
|
/* Begin XCSwiftPackageProductDependency section */
|
||||||
@ -619,6 +645,11 @@
|
|||||||
package = E2FDFF142B6AFD990080A7B3 /* XCRemoteSwiftPackageReference "BinaryCodable" */;
|
package = E2FDFF142B6AFD990080A7B3 /* XCRemoteSwiftPackageReference "BinaryCodable" */;
|
||||||
productName = BinaryCodable;
|
productName = BinaryCodable;
|
||||||
};
|
};
|
||||||
|
E2FDFF1F2B6BE34C0080A7B3 /* SwiftProtobuf */ = {
|
||||||
|
isa = XCSwiftPackageProductDependency;
|
||||||
|
package = E2FDFF1E2B6BE34C0080A7B3 /* XCRemoteSwiftPackageReference "swift-protobuf" */;
|
||||||
|
productName = SwiftProtobuf;
|
||||||
|
};
|
||||||
/* End XCSwiftPackageProductDependency section */
|
/* End XCSwiftPackageProductDependency section */
|
||||||
};
|
};
|
||||||
rootObject = 8850024F2B5C273C00E7D4DB /* Project object */;
|
rootObject = 8850024F2B5C273C00E7D4DB /* Project object */;
|
||||||
|
@ -26,6 +26,15 @@
|
|||||||
"revision" : "d029d9d39c87bed85b1c50adee7c41795261a192",
|
"revision" : "d029d9d39c87bed85b1c50adee7c41795261a192",
|
||||||
"version" : "1.0.6"
|
"version" : "1.0.6"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "swift-protobuf",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/apple/swift-protobuf.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "65e8f29b2d63c4e38e736b25c27b83e012159be8",
|
||||||
|
"version" : "1.25.2"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"version" : 2
|
"version" : 2
|
||||||
|
@ -1,20 +1,21 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import HealthKit
|
||||||
|
|
||||||
struct EventDetailView: View {
|
struct EventDetailView: View {
|
||||||
|
|
||||||
let event: WorkoutEvent
|
let event: HKWorkoutEvent
|
||||||
|
|
||||||
var metadata: [(key: String, value: Any)] {
|
var metadata: [(key: String, value: Any)] {
|
||||||
event.metadata.sorted { $0.key < $1.key }
|
event.metadata?.sorted { $0.key < $1.key } ?? []
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
List {
|
List {
|
||||||
DetailRow("Date", date: event.date)
|
DetailRow("Date", date: event.dateInterval.start)
|
||||||
DetailRow("Type", value: event.type)
|
DetailRow("Type", value: event.type)
|
||||||
DetailRow("Duration", duration: event.duration)
|
DetailRow("Duration", duration: event.dateInterval.duration)
|
||||||
DetailRow("Session UUID", value: event.sessionUUID)
|
//DetailRow("Session UUID", value: event.sessionUUID)
|
||||||
DetailRow("Error", value: event.error)
|
//DetailRow("Error", value: event.error)
|
||||||
Section("Metadata") {
|
Section("Metadata") {
|
||||||
ForEach(metadata, id: \.key) { (key, value) in
|
ForEach(metadata, id: \.key) { (key, value) in
|
||||||
DetailRow(key, value: "\(value)")
|
DetailRow(key, value: "\(value)")
|
||||||
|
208
HealthImport/Model/EventMetadata.pb.swift
Normal file
208
HealthImport/Model/EventMetadata.pb.swift
Normal file
@ -0,0 +1,208 @@
|
|||||||
|
// DO NOT EDIT.
|
||||||
|
// swift-format-ignore-file
|
||||||
|
//
|
||||||
|
// Generated by the Swift generator plugin for the protocol buffer compiler.
|
||||||
|
// Source: EventMetadata.proto
|
||||||
|
//
|
||||||
|
// For information on using the generated types, please see the documentation:
|
||||||
|
// https://github.com/apple/swift-protobuf/
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import SwiftProtobuf
|
||||||
|
|
||||||
|
// If the compiler emits an error on this type, it is because this file
|
||||||
|
// was generated by a version of the `protoc` Swift plug-in that is
|
||||||
|
// incompatible with the version of SwiftProtobuf to which you are linking.
|
||||||
|
// Please ensure that you are building against the same version of the API
|
||||||
|
// that was used to generate this file.
|
||||||
|
fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck {
|
||||||
|
struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {}
|
||||||
|
typealias Version = _2
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wrapper for workout event metadata
|
||||||
|
struct WorkoutEventMetadata {
|
||||||
|
// SwiftProtobuf.Message conformance is added in an extension below. See the
|
||||||
|
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
|
||||||
|
// methods supported on all messages.
|
||||||
|
|
||||||
|
/// All metadata elements
|
||||||
|
var elements: [WorkoutEventMetadata.Element] = []
|
||||||
|
|
||||||
|
var unknownFields = SwiftProtobuf.UnknownStorage()
|
||||||
|
|
||||||
|
struct Element {
|
||||||
|
// SwiftProtobuf.Message conformance is added in an extension below. See the
|
||||||
|
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
|
||||||
|
// methods supported on all messages.
|
||||||
|
|
||||||
|
var key: String = String()
|
||||||
|
|
||||||
|
var unsignedValue: UInt64 {
|
||||||
|
get {return _unsignedValue ?? 0}
|
||||||
|
set {_unsignedValue = newValue}
|
||||||
|
}
|
||||||
|
/// Returns true if `unsignedValue` has been explicitly set.
|
||||||
|
var hasUnsignedValue: Bool {return self._unsignedValue != nil}
|
||||||
|
/// Clears the value of `unsignedValue`. Subsequent reads from it will return its default value.
|
||||||
|
mutating func clearUnsignedValue() {self._unsignedValue = nil}
|
||||||
|
|
||||||
|
var quantity: WorkoutEventMetadata.Element.Quantity {
|
||||||
|
get {return _quantity ?? WorkoutEventMetadata.Element.Quantity()}
|
||||||
|
set {_quantity = newValue}
|
||||||
|
}
|
||||||
|
/// Returns true if `quantity` has been explicitly set.
|
||||||
|
var hasQuantity: Bool {return self._quantity != nil}
|
||||||
|
/// Clears the value of `quantity`. Subsequent reads from it will return its default value.
|
||||||
|
mutating func clearQuantity() {self._quantity = nil}
|
||||||
|
|
||||||
|
var unknownFields = SwiftProtobuf.UnknownStorage()
|
||||||
|
|
||||||
|
struct Quantity {
|
||||||
|
// SwiftProtobuf.Message conformance is added in an extension below. See the
|
||||||
|
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
|
||||||
|
// methods supported on all messages.
|
||||||
|
|
||||||
|
var value: Double = 0
|
||||||
|
|
||||||
|
var unit: String = String()
|
||||||
|
|
||||||
|
var unknownFields = SwiftProtobuf.UnknownStorage()
|
||||||
|
|
||||||
|
init() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {}
|
||||||
|
|
||||||
|
fileprivate var _unsignedValue: UInt64? = nil
|
||||||
|
fileprivate var _quantity: WorkoutEventMetadata.Element.Quantity? = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if swift(>=5.5) && canImport(_Concurrency)
|
||||||
|
extension WorkoutEventMetadata: @unchecked Sendable {}
|
||||||
|
extension WorkoutEventMetadata.Element: @unchecked Sendable {}
|
||||||
|
extension WorkoutEventMetadata.Element.Quantity: @unchecked Sendable {}
|
||||||
|
#endif // swift(>=5.5) && canImport(_Concurrency)
|
||||||
|
|
||||||
|
// MARK: - Code below here is support for the SwiftProtobuf runtime.
|
||||||
|
|
||||||
|
extension WorkoutEventMetadata: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
|
||||||
|
static let protoMessageName: String = "WorkoutEventMetadata"
|
||||||
|
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
|
||||||
|
1: .same(proto: "elements"),
|
||||||
|
]
|
||||||
|
|
||||||
|
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
|
||||||
|
while let fieldNumber = try decoder.nextFieldNumber() {
|
||||||
|
// The use of inline closures is to circumvent an issue where the compiler
|
||||||
|
// allocates stack space for every case branch when no optimizations are
|
||||||
|
// enabled. https://github.com/apple/swift-protobuf/issues/1034
|
||||||
|
switch fieldNumber {
|
||||||
|
case 1: try { try decoder.decodeRepeatedMessageField(value: &self.elements) }()
|
||||||
|
default: break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
|
||||||
|
if !self.elements.isEmpty {
|
||||||
|
try visitor.visitRepeatedMessageField(value: self.elements, fieldNumber: 1)
|
||||||
|
}
|
||||||
|
try unknownFields.traverse(visitor: &visitor)
|
||||||
|
}
|
||||||
|
|
||||||
|
static func ==(lhs: WorkoutEventMetadata, rhs: WorkoutEventMetadata) -> Bool {
|
||||||
|
if lhs.elements != rhs.elements {return false}
|
||||||
|
if lhs.unknownFields != rhs.unknownFields {return false}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension WorkoutEventMetadata.Element: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
|
||||||
|
static let protoMessageName: String = WorkoutEventMetadata.protoMessageName + ".Element"
|
||||||
|
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
|
||||||
|
1: .same(proto: "key"),
|
||||||
|
4: .same(proto: "unsignedValue"),
|
||||||
|
6: .same(proto: "quantity"),
|
||||||
|
]
|
||||||
|
|
||||||
|
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
|
||||||
|
while let fieldNumber = try decoder.nextFieldNumber() {
|
||||||
|
// The use of inline closures is to circumvent an issue where the compiler
|
||||||
|
// allocates stack space for every case branch when no optimizations are
|
||||||
|
// enabled. https://github.com/apple/swift-protobuf/issues/1034
|
||||||
|
switch fieldNumber {
|
||||||
|
case 1: try { try decoder.decodeSingularStringField(value: &self.key) }()
|
||||||
|
case 4: try { try decoder.decodeSingularUInt64Field(value: &self._unsignedValue) }()
|
||||||
|
case 6: try { try decoder.decodeSingularMessageField(value: &self._quantity) }()
|
||||||
|
default: break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
|
||||||
|
// The use of inline closures is to circumvent an issue where the compiler
|
||||||
|
// allocates stack space for every if/case branch local when no optimizations
|
||||||
|
// are enabled. https://github.com/apple/swift-protobuf/issues/1034 and
|
||||||
|
// https://github.com/apple/swift-protobuf/issues/1182
|
||||||
|
if !self.key.isEmpty {
|
||||||
|
try visitor.visitSingularStringField(value: self.key, fieldNumber: 1)
|
||||||
|
}
|
||||||
|
try { if let v = self._unsignedValue {
|
||||||
|
try visitor.visitSingularUInt64Field(value: v, fieldNumber: 4)
|
||||||
|
} }()
|
||||||
|
try { if let v = self._quantity {
|
||||||
|
try visitor.visitSingularMessageField(value: v, fieldNumber: 6)
|
||||||
|
} }()
|
||||||
|
try unknownFields.traverse(visitor: &visitor)
|
||||||
|
}
|
||||||
|
|
||||||
|
static func ==(lhs: WorkoutEventMetadata.Element, rhs: WorkoutEventMetadata.Element) -> Bool {
|
||||||
|
if lhs.key != rhs.key {return false}
|
||||||
|
if lhs._unsignedValue != rhs._unsignedValue {return false}
|
||||||
|
if lhs._quantity != rhs._quantity {return false}
|
||||||
|
if lhs.unknownFields != rhs.unknownFields {return false}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension WorkoutEventMetadata.Element.Quantity: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
|
||||||
|
static let protoMessageName: String = WorkoutEventMetadata.Element.protoMessageName + ".Quantity"
|
||||||
|
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
|
||||||
|
1: .same(proto: "value"),
|
||||||
|
2: .same(proto: "unit"),
|
||||||
|
]
|
||||||
|
|
||||||
|
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
|
||||||
|
while let fieldNumber = try decoder.nextFieldNumber() {
|
||||||
|
// The use of inline closures is to circumvent an issue where the compiler
|
||||||
|
// allocates stack space for every case branch when no optimizations are
|
||||||
|
// enabled. https://github.com/apple/swift-protobuf/issues/1034
|
||||||
|
switch fieldNumber {
|
||||||
|
case 1: try { try decoder.decodeSingularDoubleField(value: &self.value) }()
|
||||||
|
case 2: try { try decoder.decodeSingularStringField(value: &self.unit) }()
|
||||||
|
default: break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
|
||||||
|
if self.value != 0 {
|
||||||
|
try visitor.visitSingularDoubleField(value: self.value, fieldNumber: 1)
|
||||||
|
}
|
||||||
|
if !self.unit.isEmpty {
|
||||||
|
try visitor.visitSingularStringField(value: self.unit, fieldNumber: 2)
|
||||||
|
}
|
||||||
|
try unknownFields.traverse(visitor: &visitor)
|
||||||
|
}
|
||||||
|
|
||||||
|
static func ==(lhs: WorkoutEventMetadata.Element.Quantity, rhs: WorkoutEventMetadata.Element.Quantity) -> Bool {
|
||||||
|
if lhs.value != rhs.value {return false}
|
||||||
|
if lhs.unit != rhs.unit {return false}
|
||||||
|
if lhs.unknownFields != rhs.unknownFields {return false}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import SQLite
|
import SQLite
|
||||||
|
import HealthKit
|
||||||
|
|
||||||
extension Workout {
|
extension Workout {
|
||||||
|
|
||||||
@ -29,7 +30,7 @@ extension Workout {
|
|||||||
return try database.prepare(table).map { row in
|
return try database.prepare(table).map { row in
|
||||||
let id = row[columnDataId]
|
let id = row[columnDataId]
|
||||||
|
|
||||||
let events = try WorkoutEvent.events(for: id, in: database)
|
let events = try HKWorkoutEventTable.events(for: id, in: database)
|
||||||
let activities = try WorkoutActivity.activities(for: id, in: database)
|
let activities = try WorkoutActivity.activities(for: id, in: database)
|
||||||
let metadata = try Metadata.metadata(for: id, in: database, keyMap: metadataKeys)
|
let metadata = try Metadata.metadata(for: id, in: database, keyMap: metadataKeys)
|
||||||
return .init(
|
return .init(
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import Collections
|
import Collections
|
||||||
|
import HealthKit
|
||||||
|
|
||||||
private let df: DateFormatter = {
|
private let df: DateFormatter = {
|
||||||
let df = DateFormatter()
|
let df = DateFormatter()
|
||||||
@ -22,7 +23,7 @@ struct Workout {
|
|||||||
|
|
||||||
let condenserDate: Date?
|
let condenserDate: Date?
|
||||||
|
|
||||||
let events: [WorkoutEvent]
|
let events: [HKWorkoutEvent]
|
||||||
|
|
||||||
let activities: [WorkoutActivity]
|
let activities: [WorkoutActivity]
|
||||||
|
|
||||||
@ -33,7 +34,7 @@ struct Workout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var firstEventDate: Date? {
|
var firstEventDate: Date? {
|
||||||
events.map { $0.date }.min()
|
events.map { $0.dateInterval.start }.min()
|
||||||
}
|
}
|
||||||
|
|
||||||
var firstAvailableDate: Date? {
|
var firstAvailableDate: Date? {
|
||||||
@ -51,7 +52,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] = [], 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: [WorkoutActivity] = [], metadata: [Metadata.Key : Metadata.Value] = [:]) {
|
||||||
self.id = id
|
self.id = id
|
||||||
self.totalDistance = totalDistance
|
self.totalDistance = totalDistance
|
||||||
self.goal = .init(goalType: goalType, goal: goal)
|
self.goal = .init(goalType: goalType, goal: goal)
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import SQLite
|
import SQLite
|
||||||
|
import HealthKit
|
||||||
|
|
||||||
extension WorkoutEvent {
|
enum HKWorkoutEventTable {
|
||||||
|
|
||||||
private static let table = Table("workout_events")
|
private static let table = Table("workout_events")
|
||||||
|
|
||||||
@ -29,22 +30,22 @@ extension WorkoutEvent {
|
|||||||
// error BLOB
|
// error BLOB
|
||||||
private static let columnError = Expression<Data?>("error")
|
private static let columnError = Expression<Data?>("error")
|
||||||
|
|
||||||
static func readAll(in database: Connection) throws -> [Self] {
|
static func readAll(in database: Connection) throws -> [HKWorkoutEvent] {
|
||||||
try database.prepare(table).map(from)
|
try database.prepare(table).map(event)
|
||||||
}
|
}
|
||||||
|
|
||||||
static func events(for workoutId: Int, in database: Connection) throws -> [Self] {
|
static func events(for workoutId: Int, in database: Connection) throws -> [HKWorkoutEvent] {
|
||||||
try database.prepare(table.filter(columnOwnerId == workoutId)).map(from)
|
try database.prepare(table.filter(columnOwnerId == workoutId)).map(event)
|
||||||
}
|
}
|
||||||
|
|
||||||
private static func from(row: Row) -> WorkoutEvent {
|
private static func event(from row: Row) -> HKWorkoutEvent {
|
||||||
.init(
|
let start = Date(timeIntervalSinceReferenceDate: row[columnDate])
|
||||||
date: Date(timeIntervalSinceReferenceDate: row[columnDate]),
|
let interval = DateInterval(start: start, duration: row[columnDuration])
|
||||||
type: .init(rawValue: row[columnType])!,
|
let metadata = metadata(row[columnMetadata])
|
||||||
duration: row[columnDuration],
|
let type = HKWorkoutEventType(rawValue: row[columnType])!
|
||||||
metadata: metadata(row[columnMetadata]),
|
// let sessionUUID = row[columnSessionUUID]
|
||||||
sessionUUID: row[columnSessionUUID],
|
// let error = row[columnError]
|
||||||
error: row[columnError])
|
return .init(type: type, dateInterval: interval, metadata: metadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
private static func metadata(_ data: Data?) -> [String : Any] {
|
private static func metadata(_ data: Data?) -> [String : Any] {
|
||||||
@ -54,7 +55,7 @@ extension WorkoutEvent {
|
|||||||
return decode(metadata: data)
|
return decode(metadata: data)
|
||||||
}
|
}
|
||||||
|
|
||||||
static func createTable(in database: Database) throws {
|
static func create(in database: Database) throws {
|
||||||
// try database.execute("CREATE TABLE workout_events (ROWID INTEGER PRIMARY KEY AUTOINCREMENT, owner_id INTEGER NOT NULL REFERENCES workouts (data_id) ON DELETE CASCADE, date REAL NOT NULL, type INTEGER NOT NULL, duration REAL NOT NULL, metadata BLOB, session_uuid BLOB, error BLOB)")
|
// try database.execute("CREATE TABLE workout_events (ROWID INTEGER PRIMARY KEY AUTOINCREMENT, owner_id INTEGER NOT NULL REFERENCES workouts (data_id) ON DELETE CASCADE, date REAL NOT NULL, type INTEGER NOT NULL, duration REAL NOT NULL, metadata BLOB, session_uuid BLOB, error BLOB)")
|
||||||
try database.run(table.create { t in
|
try database.run(table.create { t in
|
||||||
t.column(columnRowId, primaryKey: .autoincrement)
|
t.column(columnRowId, primaryKey: .autoincrement)
|
||||||
@ -68,19 +69,22 @@ extension WorkoutEvent {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func insert(in database: Database, dataId: Int) throws {
|
static func insert(_ element: HKWorkoutEvent, dataId: Int, in database: Database) throws {
|
||||||
try WorkoutEvent.insert(self, dataId: dataId, in: database)
|
|
||||||
}
|
|
||||||
|
|
||||||
private static func insert(_ element: WorkoutEvent, dataId: Int, in database: Database) throws {
|
|
||||||
try database.run(table.insert(
|
try database.run(table.insert(
|
||||||
columnOwnerId <- dataId,
|
columnOwnerId <- dataId,
|
||||||
columnDate <- element.date.timeIntervalSinceReferenceDate,
|
columnDate <- element.dateInterval.start.timeIntervalSinceReferenceDate,
|
||||||
columnType <- element.type.rawValue,
|
columnType <- element.type.rawValue,
|
||||||
columnDuration <- element.duration,
|
columnDuration <- element.dateInterval.duration,
|
||||||
columnMetadata <- encode(metadata: element.metadata),
|
columnMetadata <- encode(metadata: element.metadata ?? [:]))
|
||||||
columnSessionUUID <- element.sessionUUID,
|
// columnSessionUUID <- element.sessionUUID
|
||||||
columnError <- element.error)
|
// columnError <- element.error)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension HKWorkoutEvent {
|
||||||
|
|
||||||
|
func insert(in database: Database, dataId: Int) throws {
|
||||||
|
try HKWorkoutEventTable.insert(self, dataId: dataId, in: database)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,59 +1,20 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import HealthKit
|
import HealthKit
|
||||||
import BinaryCodable
|
import SwiftProtobuf
|
||||||
|
|
||||||
struct WorkoutEvent {
|
extension HKWorkoutEvent: Identifiable {
|
||||||
|
|
||||||
let date: Date
|
public var id: Double {
|
||||||
|
dateInterval.start.timeIntervalSinceReferenceDate * Double(type.rawValue) * dateInterval.duration
|
||||||
let type: HKWorkoutEventType
|
|
||||||
|
|
||||||
let duration: TimeInterval
|
|
||||||
|
|
||||||
let metadata: [String : Any]
|
|
||||||
|
|
||||||
let sessionUUID: Data?
|
|
||||||
|
|
||||||
let error: Data?
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
extension WorkoutEvent: Equatable {
|
|
||||||
|
|
||||||
static func == (lhs: WorkoutEvent, rhs: WorkoutEvent) -> Bool {
|
|
||||||
lhs.date == rhs.date && lhs.type == rhs.type && lhs.duration == rhs.duration
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension WorkoutEvent: Comparable {
|
extension HKWorkoutEventTable {
|
||||||
|
|
||||||
static func < (lhs: WorkoutEvent, rhs: WorkoutEvent) -> Bool {
|
|
||||||
lhs.date < rhs.date
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension WorkoutEvent: Hashable {
|
|
||||||
|
|
||||||
func hash(into hasher: inout Hasher) {
|
|
||||||
hasher.combine(date)
|
|
||||||
hasher.combine(type.rawValue)
|
|
||||||
hasher.combine(duration)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension WorkoutEvent: Identifiable {
|
|
||||||
|
|
||||||
var id: Double {
|
|
||||||
date.timeIntervalSinceReferenceDate * Double(type.rawValue) * duration
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension WorkoutEvent {
|
|
||||||
|
|
||||||
static func decode(metadata data: Data) -> [String : Any] {
|
static func decode(metadata data: Data) -> [String : Any] {
|
||||||
let metadata: WorkoutEventMetadata
|
let metadata: WorkoutEventMetadata
|
||||||
do {
|
do {
|
||||||
metadata = try ProtobufDecoder.decode(WorkoutEventMetadata.self, from: data)
|
metadata = try WorkoutEventMetadata(serializedData: data)
|
||||||
} catch {
|
} catch {
|
||||||
print("Failed to decode event metadata: \(error)")
|
print("Failed to decode event metadata: \(error)")
|
||||||
print(data.hex)
|
print(data.hex)
|
||||||
@ -71,14 +32,14 @@ extension WorkoutEvent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static func encode(metadata: [String : Any]) -> Data? {
|
static func encode(metadata: [String : Any]) -> Data? {
|
||||||
let wrapper = WorkoutEventMetadata(elements: metadata.compactMap {
|
let wrapper = WorkoutEventMetadata.with {
|
||||||
.init(key: $0.key, value: $0.value)
|
$0.elements = metadata.compactMap { .from(key: $0.key, value: $0.value) }
|
||||||
})
|
}
|
||||||
guard !wrapper.elements.isEmpty else {
|
guard !wrapper.elements.isEmpty else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
do {
|
do {
|
||||||
return try ProtobufEncoder().encode(wrapper)
|
return try wrapper.serializedData()
|
||||||
} catch {
|
} catch {
|
||||||
print("Failed to encode event metadata: \(error)")
|
print("Failed to encode event metadata: \(error)")
|
||||||
return nil
|
return nil
|
||||||
@ -86,90 +47,49 @@ extension WorkoutEvent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private struct WorkoutEventMetadata {
|
private extension WorkoutEventMetadata.Element {
|
||||||
|
|
||||||
let elements: [Element]
|
|
||||||
}
|
|
||||||
|
|
||||||
extension WorkoutEventMetadata.Element {
|
|
||||||
|
|
||||||
var value: Any? {
|
var value: Any? {
|
||||||
if let unsignedValue {
|
if hasUnsignedValue {
|
||||||
return unsignedValue
|
return unsignedValue
|
||||||
}
|
}
|
||||||
if let quantity {
|
if hasQuantity {
|
||||||
return HKQuantity(unit: .init(from: quantity.unit), doubleValue: quantity.value)
|
return HKQuantity(unit: .init(from: quantity.unit), doubleValue: quantity.value)
|
||||||
}
|
}
|
||||||
return UInt(0)
|
return UInt(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
init?(key: String, value: Any) {
|
static func from(key: String, value: Any) -> Self? {
|
||||||
self.key = key
|
|
||||||
|
|
||||||
if let value = value as? UInt {
|
if let value = value as? UInt {
|
||||||
self.unsignedValue = value
|
return .with {
|
||||||
self.quantity = nil
|
$0.key = key
|
||||||
return
|
$0.unsignedValue = UInt64(value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
guard let value = value as? HKQuantity else {
|
guard let value = value as? HKQuantity else {
|
||||||
print("Unknown value type for metadata key \(key): \(value)")
|
print("Unknown value type for metadata key \(key): \(value)")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
self.unsignedValue = nil
|
|
||||||
|
|
||||||
|
let number: Double
|
||||||
|
let unit: String
|
||||||
if value.is(compatibleWith: .meter()) {
|
if value.is(compatibleWith: .meter()) {
|
||||||
self.quantity = .init(value: value.doubleValue(for: .meter()), unit: "m")
|
number = value.doubleValue(for: .meter())
|
||||||
|
unit = "m"
|
||||||
} else if value.is(compatibleWith: .second()) {
|
} else if value.is(compatibleWith: .second()) {
|
||||||
self.quantity = .init(value: value.doubleValue(for: .second()), unit: "s")
|
number = value.doubleValue(for: .second())
|
||||||
|
unit = "s"
|
||||||
} else {
|
} else {
|
||||||
print("Unhandled quantity type for metadata key \(key): \(value)")
|
print("Unhandled quantity type for metadata key \(key): \(value)")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
return .with { el in
|
||||||
|
el.key = key
|
||||||
extension WorkoutEventMetadata {
|
el.quantity = .with {
|
||||||
|
$0.value = number
|
||||||
struct Element {
|
$0.unit = unit
|
||||||
|
}
|
||||||
let key: String
|
}
|
||||||
|
|
||||||
let unsignedValue: UInt?
|
|
||||||
|
|
||||||
let quantity: Quantity?
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension WorkoutEventMetadata.Element {
|
|
||||||
|
|
||||||
struct Quantity {
|
|
||||||
|
|
||||||
let value: Double
|
|
||||||
|
|
||||||
let unit: String
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension WorkoutEventMetadata: Codable {
|
|
||||||
|
|
||||||
enum CodingKeys: Int, CodingKey {
|
|
||||||
case elements = 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension WorkoutEventMetadata.Element: Codable {
|
|
||||||
|
|
||||||
enum CodingKeys: Int, CodingKey {
|
|
||||||
case key = 1
|
|
||||||
case unsignedValue = 4
|
|
||||||
case quantity = 6
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension WorkoutEventMetadata.Element.Quantity: Codable {
|
|
||||||
|
|
||||||
enum CodingKeys: Int, CodingKey {
|
|
||||||
case value = 1
|
|
||||||
case unit = 2
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import SQLite
|
import SQLite
|
||||||
|
import HealthKit
|
||||||
|
|
||||||
extension HealthDatabase {
|
extension HealthDatabase {
|
||||||
|
|
||||||
@ -18,7 +19,7 @@ extension HealthDatabase {
|
|||||||
let database = try Connection(.inMemory)
|
let database = try Connection(.inMemory)
|
||||||
|
|
||||||
try Workout.createTable(in: database)
|
try Workout.createTable(in: database)
|
||||||
try WorkoutEvent.createTable(in: database)
|
try HKWorkoutEventTable.create(in: database)
|
||||||
try WorkoutActivity.createTable(in: database)
|
try WorkoutActivity.createTable(in: database)
|
||||||
try Metadata.createTables(in: database)
|
try Metadata.createTables(in: database)
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
import HealthKit
|
||||||
|
|
||||||
extension Workout {
|
extension Workout {
|
||||||
|
|
||||||
@ -9,7 +10,7 @@ extension Workout {
|
|||||||
goal: 19800.0,
|
goal: 19800.0,
|
||||||
condenserVersion: 3,
|
condenserVersion: 3,
|
||||||
condenserDate: Date(timeIntervalSinceReferenceDate: 716801471.790011),
|
condenserDate: Date(timeIntervalSinceReferenceDate: 716801471.790011),
|
||||||
events: WorkoutEvent.mock1,
|
events: HKWorkoutEvent.mock1,
|
||||||
activities: [.mock1],
|
activities: [.mock1],
|
||||||
metadata: Metadata.mock1)
|
metadata: Metadata.mock1)
|
||||||
}
|
}
|
||||||
|
@ -1,34 +1,26 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import HealthKit
|
import HealthKit
|
||||||
|
|
||||||
extension WorkoutEvent {
|
extension HKWorkoutEvent {
|
||||||
|
|
||||||
static var mock1: [WorkoutEvent] {
|
static var mock1: [HKWorkoutEvent] {
|
||||||
[
|
[
|
||||||
.init(date: .init(timeIntervalSinceReferenceDate: 702107518.84307),
|
.init(type: .init(rawValue: 7)!,
|
||||||
type: .init(rawValue: 7)!,
|
dateInterval: .init(start: Date(timeIntervalSinceReferenceDate: 702107518.84307),
|
||||||
duration: 1114.56374406815,
|
duration: 1114.56374406815),
|
||||||
metadata: WorkoutEvent.decode(metadata: .init(hex: mock1Event1Metadata)!),
|
metadata: HKWorkoutEventTable.decode(metadata: .init(hex: mock1Event1Metadata)!)),
|
||||||
sessionUUID: nil,
|
.init(type: .init(rawValue: 7)!,
|
||||||
error: nil),
|
dateInterval: .init(start: Date(timeIntervalSinceReferenceDate: 702107518.84307),
|
||||||
.init(date: .init(timeIntervalSinceReferenceDate: 702107518.84307),
|
duration: 1972.17168283463),
|
||||||
type: .init(rawValue: 7)!,
|
metadata: HKWorkoutEventTable.decode(metadata: .init(hex: mock1Event2Metadata)!)),
|
||||||
duration: 1972.17168283463,
|
.init(type: .init(rawValue: 1)!,
|
||||||
metadata: WorkoutEvent.decode(metadata: .init(hex: mock1Event2Metadata)!),
|
dateInterval: .init(start: Date(timeIntervalSinceReferenceDate: 702112942.707113),
|
||||||
sessionUUID: nil,
|
duration: 0.0),
|
||||||
error: nil),
|
metadata: HKWorkoutEventTable.decode(metadata: .init(hex: mock1Event2Metadata)!)),
|
||||||
.init(date: .init(timeIntervalSinceReferenceDate: 702112942.707113),
|
.init(type: .init(rawValue: 2)!,
|
||||||
type: .init(rawValue: 1)!,
|
dateInterval: .init(start: Date(timeIntervalSinceReferenceDate: 702113161.221132),
|
||||||
duration: 0.0,
|
duration: 0.0),
|
||||||
metadata: [:],
|
metadata: [:])
|
||||||
sessionUUID: nil,
|
|
||||||
error: nil),
|
|
||||||
.init(date: .init(timeIntervalSinceReferenceDate: 702113161.221132),
|
|
||||||
type: .init(rawValue: 2)!,
|
|
||||||
duration: 0.0,
|
|
||||||
metadata: [:],
|
|
||||||
sessionUUID: nil,
|
|
||||||
error: nil),
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
import Collections
|
import Collections
|
||||||
|
import HealthKit
|
||||||
|
|
||||||
struct WorkoutDetailView: View {
|
struct WorkoutDetailView: View {
|
||||||
|
|
||||||
@ -32,7 +33,7 @@ struct WorkoutDetailView: View {
|
|||||||
Section("Events") {
|
Section("Events") {
|
||||||
ForEach(workout.events) { event in
|
ForEach(workout.events) { event in
|
||||||
NavigationLink(value: event) {
|
NavigationLink(value: event) {
|
||||||
DetailRow(event.type.description, date: event.date)
|
DetailRow(event.type.description, date: event.dateInterval.start)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -50,7 +51,7 @@ struct WorkoutDetailView: View {
|
|||||||
ActivityDetailView(activity: activity)
|
ActivityDetailView(activity: activity)
|
||||||
.environmentObject(database)
|
.environmentObject(database)
|
||||||
}
|
}
|
||||||
.navigationDestination(for: WorkoutEvent.self) { event in
|
.navigationDestination(for: HKWorkoutEvent.self) { event in
|
||||||
EventDetailView(event: event)
|
EventDetailView(event: event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user