Switch to HKWorkoutEvent
This commit is contained in:
@@ -1,20 +1,21 @@
|
||||
import SwiftUI
|
||||
import HealthKit
|
||||
|
||||
struct EventDetailView: View {
|
||||
|
||||
let event: WorkoutEvent
|
||||
let event: HKWorkoutEvent
|
||||
|
||||
var metadata: [(key: String, value: Any)] {
|
||||
event.metadata.sorted { $0.key < $1.key }
|
||||
event.metadata?.sorted { $0.key < $1.key } ?? []
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
List {
|
||||
DetailRow("Date", date: event.date)
|
||||
DetailRow("Date", date: event.dateInterval.start)
|
||||
DetailRow("Type", value: event.type)
|
||||
DetailRow("Duration", duration: event.duration)
|
||||
DetailRow("Session UUID", value: event.sessionUUID)
|
||||
DetailRow("Error", value: event.error)
|
||||
DetailRow("Duration", duration: event.dateInterval.duration)
|
||||
//DetailRow("Session UUID", value: event.sessionUUID)
|
||||
//DetailRow("Error", value: event.error)
|
||||
Section("Metadata") {
|
||||
ForEach(metadata, id: \.key) { (key, value) in
|
||||
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 SQLite
|
||||
import HealthKit
|
||||
|
||||
extension Workout {
|
||||
|
||||
@@ -29,7 +30,7 @@ extension Workout {
|
||||
return try database.prepare(table).map { row in
|
||||
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 metadata = try Metadata.metadata(for: id, in: database, keyMap: metadataKeys)
|
||||
return .init(
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import Foundation
|
||||
import Collections
|
||||
import HealthKit
|
||||
|
||||
private let df: DateFormatter = {
|
||||
let df = DateFormatter()
|
||||
@@ -22,8 +23,8 @@ struct Workout {
|
||||
|
||||
let condenserDate: Date?
|
||||
|
||||
let events: [WorkoutEvent]
|
||||
|
||||
let events: [HKWorkoutEvent]
|
||||
|
||||
let activities: [WorkoutActivity]
|
||||
|
||||
let metadata: OrderedDictionary<Metadata.Key, Metadata.Value>
|
||||
@@ -33,7 +34,7 @@ struct Workout {
|
||||
}
|
||||
|
||||
var firstEventDate: Date? {
|
||||
events.map { $0.date }.min()
|
||||
events.map { $0.dateInterval.start }.min()
|
||||
}
|
||||
|
||||
var firstAvailableDate: Date? {
|
||||
@@ -51,7 +52,7 @@ struct Workout {
|
||||
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.totalDistance = totalDistance
|
||||
self.goal = .init(goalType: goalType, goal: goal)
|
||||
|
@@ -1,7 +1,8 @@
|
||||
import Foundation
|
||||
import SQLite
|
||||
import HealthKit
|
||||
|
||||
extension WorkoutEvent {
|
||||
enum HKWorkoutEventTable {
|
||||
|
||||
private static let table = Table("workout_events")
|
||||
|
||||
@@ -29,22 +30,22 @@ extension WorkoutEvent {
|
||||
// error BLOB
|
||||
private static let columnError = Expression<Data?>("error")
|
||||
|
||||
static func readAll(in database: Connection) throws -> [Self] {
|
||||
try database.prepare(table).map(from)
|
||||
static func readAll(in database: Connection) throws -> [HKWorkoutEvent] {
|
||||
try database.prepare(table).map(event)
|
||||
}
|
||||
|
||||
static func events(for workoutId: Int, in database: Connection) throws -> [Self] {
|
||||
try database.prepare(table.filter(columnOwnerId == workoutId)).map(from)
|
||||
static func events(for workoutId: Int, in database: Connection) throws -> [HKWorkoutEvent] {
|
||||
try database.prepare(table.filter(columnOwnerId == workoutId)).map(event)
|
||||
}
|
||||
|
||||
private static func from(row: Row) -> WorkoutEvent {
|
||||
.init(
|
||||
date: Date(timeIntervalSinceReferenceDate: row[columnDate]),
|
||||
type: .init(rawValue: row[columnType])!,
|
||||
duration: row[columnDuration],
|
||||
metadata: metadata(row[columnMetadata]),
|
||||
sessionUUID: row[columnSessionUUID],
|
||||
error: row[columnError])
|
||||
private static func event(from row: Row) -> HKWorkoutEvent {
|
||||
let start = Date(timeIntervalSinceReferenceDate: row[columnDate])
|
||||
let interval = DateInterval(start: start, duration: row[columnDuration])
|
||||
let metadata = metadata(row[columnMetadata])
|
||||
let type = HKWorkoutEventType(rawValue: row[columnType])!
|
||||
// let sessionUUID = row[columnSessionUUID]
|
||||
// let error = row[columnError]
|
||||
return .init(type: type, dateInterval: interval, metadata: metadata)
|
||||
}
|
||||
|
||||
private static func metadata(_ data: Data?) -> [String : Any] {
|
||||
@@ -54,7 +55,7 @@ extension WorkoutEvent {
|
||||
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.run(table.create { t in
|
||||
t.column(columnRowId, primaryKey: .autoincrement)
|
||||
@@ -68,19 +69,22 @@ extension WorkoutEvent {
|
||||
})
|
||||
}
|
||||
|
||||
func insert(in database: Database, dataId: Int) throws {
|
||||
try WorkoutEvent.insert(self, dataId: dataId, in: database)
|
||||
}
|
||||
|
||||
private static func insert(_ element: WorkoutEvent, dataId: Int, in database: Database) throws {
|
||||
static func insert(_ element: HKWorkoutEvent, dataId: Int, in database: Database) throws {
|
||||
try database.run(table.insert(
|
||||
columnOwnerId <- dataId,
|
||||
columnDate <- element.date.timeIntervalSinceReferenceDate,
|
||||
columnDate <- element.dateInterval.start.timeIntervalSinceReferenceDate,
|
||||
columnType <- element.type.rawValue,
|
||||
columnDuration <- element.duration,
|
||||
columnMetadata <- encode(metadata: element.metadata),
|
||||
columnSessionUUID <- element.sessionUUID,
|
||||
columnError <- element.error)
|
||||
columnDuration <- element.dateInterval.duration,
|
||||
columnMetadata <- encode(metadata: element.metadata ?? [:]))
|
||||
// columnSessionUUID <- element.sessionUUID
|
||||
// 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 HealthKit
|
||||
import BinaryCodable
|
||||
import SwiftProtobuf
|
||||
|
||||
struct WorkoutEvent {
|
||||
|
||||
let date: Date
|
||||
|
||||
let type: HKWorkoutEventType
|
||||
|
||||
let duration: TimeInterval
|
||||
|
||||
let metadata: [String : Any]
|
||||
|
||||
let sessionUUID: Data?
|
||||
|
||||
let error: Data?
|
||||
|
||||
}
|
||||
extension HKWorkoutEvent: Identifiable {
|
||||
|
||||
extension WorkoutEvent: Equatable {
|
||||
|
||||
static func == (lhs: WorkoutEvent, rhs: WorkoutEvent) -> Bool {
|
||||
lhs.date == rhs.date && lhs.type == rhs.type && lhs.duration == rhs.duration
|
||||
public var id: Double {
|
||||
dateInterval.start.timeIntervalSinceReferenceDate * Double(type.rawValue) * dateInterval.duration
|
||||
}
|
||||
}
|
||||
|
||||
extension WorkoutEvent: Comparable {
|
||||
|
||||
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 {
|
||||
extension HKWorkoutEventTable {
|
||||
|
||||
static func decode(metadata data: Data) -> [String : Any] {
|
||||
let metadata: WorkoutEventMetadata
|
||||
do {
|
||||
metadata = try ProtobufDecoder.decode(WorkoutEventMetadata.self, from: data)
|
||||
metadata = try WorkoutEventMetadata(serializedData: data)
|
||||
} catch {
|
||||
print("Failed to decode event metadata: \(error)")
|
||||
print(data.hex)
|
||||
@@ -71,14 +32,14 @@ extension WorkoutEvent {
|
||||
}
|
||||
|
||||
static func encode(metadata: [String : Any]) -> Data? {
|
||||
let wrapper = WorkoutEventMetadata(elements: metadata.compactMap {
|
||||
.init(key: $0.key, value: $0.value)
|
||||
})
|
||||
let wrapper = WorkoutEventMetadata.with {
|
||||
$0.elements = metadata.compactMap { .from(key: $0.key, value: $0.value) }
|
||||
}
|
||||
guard !wrapper.elements.isEmpty else {
|
||||
return nil
|
||||
}
|
||||
do {
|
||||
return try ProtobufEncoder().encode(wrapper)
|
||||
return try wrapper.serializedData()
|
||||
} catch {
|
||||
print("Failed to encode event metadata: \(error)")
|
||||
return nil
|
||||
@@ -86,90 +47,49 @@ extension WorkoutEvent {
|
||||
}
|
||||
}
|
||||
|
||||
private struct WorkoutEventMetadata {
|
||||
|
||||
let elements: [Element]
|
||||
}
|
||||
|
||||
extension WorkoutEventMetadata.Element {
|
||||
private extension WorkoutEventMetadata.Element {
|
||||
|
||||
var value: Any? {
|
||||
if let unsignedValue {
|
||||
if hasUnsignedValue {
|
||||
return unsignedValue
|
||||
}
|
||||
if let quantity {
|
||||
if hasQuantity {
|
||||
return HKQuantity(unit: .init(from: quantity.unit), doubleValue: quantity.value)
|
||||
}
|
||||
return UInt(0)
|
||||
}
|
||||
|
||||
init?(key: String, value: Any) {
|
||||
self.key = key
|
||||
|
||||
static func from(key: String, value: Any) -> Self? {
|
||||
if let value = value as? UInt {
|
||||
self.unsignedValue = value
|
||||
self.quantity = nil
|
||||
return
|
||||
return .with {
|
||||
$0.key = key
|
||||
$0.unsignedValue = UInt64(value)
|
||||
}
|
||||
}
|
||||
guard let value = value as? HKQuantity else {
|
||||
print("Unknown value type for metadata key \(key): \(value)")
|
||||
return nil
|
||||
}
|
||||
self.unsignedValue = nil
|
||||
|
||||
let number: Double
|
||||
let unit: String
|
||||
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()) {
|
||||
self.quantity = .init(value: value.doubleValue(for: .second()), unit: "s")
|
||||
number = value.doubleValue(for: .second())
|
||||
unit = "s"
|
||||
} else {
|
||||
print("Unhandled quantity type for metadata key \(key): \(value)")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension WorkoutEventMetadata {
|
||||
|
||||
struct Element {
|
||||
|
||||
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
|
||||
|
||||
return .with { el in
|
||||
el.key = key
|
||||
el.quantity = .with {
|
||||
$0.value = number
|
||||
$0.unit = unit
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import Foundation
|
||||
import SQLite
|
||||
import HealthKit
|
||||
|
||||
extension HealthDatabase {
|
||||
|
||||
@@ -18,7 +19,7 @@ extension HealthDatabase {
|
||||
let database = try Connection(.inMemory)
|
||||
|
||||
try Workout.createTable(in: database)
|
||||
try WorkoutEvent.createTable(in: database)
|
||||
try HKWorkoutEventTable.create(in: database)
|
||||
try WorkoutActivity.createTable(in: database)
|
||||
try Metadata.createTables(in: database)
|
||||
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import Foundation
|
||||
import HealthKit
|
||||
|
||||
extension Workout {
|
||||
|
||||
@@ -9,7 +10,7 @@ extension Workout {
|
||||
goal: 19800.0,
|
||||
condenserVersion: 3,
|
||||
condenserDate: Date(timeIntervalSinceReferenceDate: 716801471.790011),
|
||||
events: WorkoutEvent.mock1,
|
||||
events: HKWorkoutEvent.mock1,
|
||||
activities: [.mock1],
|
||||
metadata: Metadata.mock1)
|
||||
}
|
||||
|
@@ -1,34 +1,26 @@
|
||||
import Foundation
|
||||
import HealthKit
|
||||
|
||||
extension WorkoutEvent {
|
||||
extension HKWorkoutEvent {
|
||||
|
||||
static var mock1: [WorkoutEvent] {
|
||||
static var mock1: [HKWorkoutEvent] {
|
||||
[
|
||||
.init(date: .init(timeIntervalSinceReferenceDate: 702107518.84307),
|
||||
type: .init(rawValue: 7)!,
|
||||
duration: 1114.56374406815,
|
||||
metadata: WorkoutEvent.decode(metadata: .init(hex: mock1Event1Metadata)!),
|
||||
sessionUUID: nil,
|
||||
error: nil),
|
||||
.init(date: .init(timeIntervalSinceReferenceDate: 702107518.84307),
|
||||
type: .init(rawValue: 7)!,
|
||||
duration: 1972.17168283463,
|
||||
metadata: WorkoutEvent.decode(metadata: .init(hex: mock1Event2Metadata)!),
|
||||
sessionUUID: nil,
|
||||
error: nil),
|
||||
.init(date: .init(timeIntervalSinceReferenceDate: 702112942.707113),
|
||||
type: .init(rawValue: 1)!,
|
||||
duration: 0.0,
|
||||
metadata: [:],
|
||||
sessionUUID: nil,
|
||||
error: nil),
|
||||
.init(date: .init(timeIntervalSinceReferenceDate: 702113161.221132),
|
||||
type: .init(rawValue: 2)!,
|
||||
duration: 0.0,
|
||||
metadata: [:],
|
||||
sessionUUID: nil,
|
||||
error: nil),
|
||||
.init(type: .init(rawValue: 7)!,
|
||||
dateInterval: .init(start: Date(timeIntervalSinceReferenceDate: 702107518.84307),
|
||||
duration: 1114.56374406815),
|
||||
metadata: HKWorkoutEventTable.decode(metadata: .init(hex: mock1Event1Metadata)!)),
|
||||
.init(type: .init(rawValue: 7)!,
|
||||
dateInterval: .init(start: Date(timeIntervalSinceReferenceDate: 702107518.84307),
|
||||
duration: 1972.17168283463),
|
||||
metadata: HKWorkoutEventTable.decode(metadata: .init(hex: mock1Event2Metadata)!)),
|
||||
.init(type: .init(rawValue: 1)!,
|
||||
dateInterval: .init(start: Date(timeIntervalSinceReferenceDate: 702112942.707113),
|
||||
duration: 0.0),
|
||||
metadata: HKWorkoutEventTable.decode(metadata: .init(hex: mock1Event2Metadata)!)),
|
||||
.init(type: .init(rawValue: 2)!,
|
||||
dateInterval: .init(start: Date(timeIntervalSinceReferenceDate: 702113161.221132),
|
||||
duration: 0.0),
|
||||
metadata: [:])
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import SwiftUI
|
||||
import Collections
|
||||
import HealthKit
|
||||
|
||||
struct WorkoutDetailView: View {
|
||||
|
||||
@@ -32,7 +33,7 @@ struct WorkoutDetailView: View {
|
||||
Section("Events") {
|
||||
ForEach(workout.events) { event in
|
||||
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)
|
||||
.environmentObject(database)
|
||||
}
|
||||
.navigationDestination(for: WorkoutEvent.self) { event in
|
||||
.navigationDestination(for: HKWorkoutEvent.self) { event in
|
||||
EventDetailView(event: event)
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user