Show private metadata and statistics for activity

This commit is contained in:
Christoph Hagen 2024-03-20 10:19:08 +01:00
parent 26e06ffc06
commit e5670afc22
2 changed files with 71 additions and 41 deletions

View File

@ -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
}
let samples = try store.locations(associatedWith: route)
.sorted { $0.timestamp }
//let sampleCount = try HealthDatabase.shared.sampleCount(for: activity)
DispatchQueue.main.async {
self.locations = samples
//self.sampleCount = sampleCount
}
} catch {
print("Failed to load location samples for activity: \(error)")
}
await loadStatistics(db: store)
await loadPrivateMetadata(db: store)
}
}
private func loadStatistics(db: HealthDatabase) async {
do {
let statistics = try db.store.statistics(associatedWith: activity)
DispatchQueue.main.async {
self.statistics = statistics
.sorted { $0.key.description }
.map { ($0, $1) }
}
} catch {
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 {

View File

@ -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)")