Show private metadata and statistics for activity
This commit is contained in:
parent
26e06ffc06
commit
e5670afc22
@ -13,9 +13,17 @@ struct ActivityDetailView: View {
|
||||
|
||||
let activity: HKWorkoutActivity
|
||||
|
||||
@State var locations: [CLLocation] = []
|
||||
@State
|
||||
private var statistics: [(type: HKQuantityType, statistics: Statistics)] = []
|
||||
|
||||
@State var sampleCount: Int = 0
|
||||
@State
|
||||
private var privateMetadata: [(key: String, value: Any)] = []
|
||||
|
||||
@State
|
||||
private var showErrorMessage = false
|
||||
|
||||
@State
|
||||
private var errorMessage: String = ""
|
||||
|
||||
private var metadata: [(key: String, value: Any)] {
|
||||
activity.metadata?.sorted { $0.key } ?? []
|
||||
@ -33,22 +41,12 @@ struct ActivityDetailView: View {
|
||||
DetailRow("End", date: activity.endDate)
|
||||
DetailRow("Duration", duration: activity.duration)
|
||||
}
|
||||
Section("Data") {
|
||||
if !locations.isEmpty {
|
||||
NavigationLink(value: locations) {
|
||||
DetailRow("Locations", value: "\(locations.count)")
|
||||
if !statistics.isEmpty {
|
||||
Section("Statistics") {
|
||||
ForEach(statistics, id: \.type) { (type, statistic) in
|
||||
let name = HKQuantityTypeIdentifier(rawValue: type.identifier).description
|
||||
DetailRow(name, value: statistic.average)
|
||||
}
|
||||
} else {
|
||||
DetailRow("Locations", value: "0")
|
||||
}
|
||||
if sampleCount != 0 {
|
||||
NavigationLink {
|
||||
ActivitySamplesView(activity: activity)
|
||||
} label: {
|
||||
DetailRow("Samples", value: "\(sampleCount)")
|
||||
}
|
||||
} else {
|
||||
DetailRow("Samples", value: "0")
|
||||
}
|
||||
}
|
||||
if !(activity.metadata?.isEmpty ?? true) {
|
||||
@ -58,12 +56,28 @@ struct ActivityDetailView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
if !privateMetadata.isEmpty {
|
||||
Section("Private Metadata") {
|
||||
ForEach(privateMetadata, id:\.key) { (key, value) in
|
||||
DetailRow(MetadataKeyName(key), value: "\(value)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle("Activity")
|
||||
.navigationDestination(for: [CLLocation].self) { locations in
|
||||
LocationSampleListView(samples: locations)
|
||||
}
|
||||
.onAppear(perform: load)
|
||||
.alert("Processing error", isPresented: $showErrorMessage) {
|
||||
Button("Dismiss", role: .cancel) { }
|
||||
} message: {
|
||||
Text(errorMessage)
|
||||
}
|
||||
}
|
||||
|
||||
private func show(_ error: String) {
|
||||
DispatchQueue.main.async {
|
||||
self.errorMessage = error
|
||||
self.showErrorMessage = true
|
||||
}
|
||||
}
|
||||
|
||||
private func load() {
|
||||
@ -71,22 +85,36 @@ struct ActivityDetailView: View {
|
||||
return
|
||||
}
|
||||
Task {
|
||||
do {
|
||||
guard let route = try store.route(associatedWith: workout) else {
|
||||
return
|
||||
await loadStatistics(db: store)
|
||||
await loadPrivateMetadata(db: store)
|
||||
}
|
||||
let samples = try store.locations(associatedWith: route)
|
||||
.sorted { $0.timestamp }
|
||||
//let sampleCount = try HealthDatabase.shared.sampleCount(for: activity)
|
||||
}
|
||||
|
||||
private func loadStatistics(db: HealthDatabase) async {
|
||||
do {
|
||||
let statistics = try db.store.statistics(associatedWith: activity)
|
||||
DispatchQueue.main.async {
|
||||
self.locations = samples
|
||||
//self.sampleCount = sampleCount
|
||||
self.statistics = statistics
|
||||
.sorted { $0.key.description }
|
||||
.map { ($0, $1) }
|
||||
}
|
||||
} catch {
|
||||
print("Failed to load location samples for activity: \(error)")
|
||||
print("Failed to load statistics from database: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
private func loadPrivateMetadata(db: HealthDatabase) async {
|
||||
do {
|
||||
let metadata = try db.store.metadata(for: workout.uuid, includePrivateMetadata: true)
|
||||
.filter { $0.key.hasPrefix("_HKPrivate") }
|
||||
DispatchQueue.main.async {
|
||||
self.privateMetadata = metadata.sorted { $0.key }
|
||||
}
|
||||
} catch {
|
||||
show("Failed to load private metadata from database: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#Preview {
|
||||
|
@ -29,7 +29,7 @@ struct WorkoutDetailView: View {
|
||||
private var locationSamples: [CLLocation] = []
|
||||
|
||||
@State
|
||||
private var privateMetadata: [String : Any] = [:]
|
||||
private var privateMetadata: [(key: String, value: Any)] = []
|
||||
|
||||
@State
|
||||
private var showErrorMessage = false
|
||||
@ -44,10 +44,6 @@ struct WorkoutDetailView: View {
|
||||
workout.metadata.sorted { $0.key }
|
||||
}
|
||||
|
||||
private var privateMetadataFields: [(key: String, value: Any)] {
|
||||
privateMetadata.sorted { $0.key }
|
||||
}
|
||||
|
||||
private var averageHeartRate: Int {
|
||||
let sum = heartRateSamples.reduce(0) { $0 + $1.beatsPerMinute }
|
||||
return (Double(sum) / Double(heartRateSamples.count)).roundedInt
|
||||
@ -122,7 +118,7 @@ struct WorkoutDetailView: View {
|
||||
DetailRow("Metadata", value: workout.metadata.count)
|
||||
}
|
||||
DisclosureGroup {
|
||||
ForEach(privateMetadataFields, id:\.key) { (key, value) in
|
||||
ForEach(privateMetadata, id:\.key) { (key, value) in
|
||||
DetailRow(MetadataKeyName(key), value: "\(value)")
|
||||
}
|
||||
} label: {
|
||||
@ -152,6 +148,9 @@ struct WorkoutDetailView: View {
|
||||
Text("")
|
||||
.frame(height: 150)
|
||||
.listRowBackground(WorkoutMapView(locations: locationSamples))
|
||||
.overlay(
|
||||
NavigationLink(value: locationSamples) { }
|
||||
.opacity(0))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -163,6 +162,9 @@ struct WorkoutDetailView: View {
|
||||
.navigationDestination(for: HKWorkoutEvent.self) { event in
|
||||
EventDetailView(event: event)
|
||||
}
|
||||
.navigationDestination(for: [CLLocation].self) { locations in
|
||||
LocationSampleListView(samples: locations)
|
||||
}
|
||||
.onAppear(perform: loadSamples)
|
||||
.alert("Processing error", isPresented: $showErrorMessage) {
|
||||
Button("Dismiss", role: .cancel) { }
|
||||
@ -305,7 +307,7 @@ struct WorkoutDetailView: View {
|
||||
let metadata = try db.store.metadata(for: workout.uuid, includePrivateMetadata: true)
|
||||
.filter { $0.key.hasPrefix("_HKPrivate") }
|
||||
DispatchQueue.main.async {
|
||||
self.privateMetadata = metadata
|
||||
self.privateMetadata = metadata.sorted { $0.key }
|
||||
}
|
||||
} catch {
|
||||
show("Failed to load private metadata from database: \(error)")
|
||||
|
Loading…
Reference in New Issue
Block a user