Show errors on import

This commit is contained in:
Christoph Hagen 2024-03-19 17:57:43 +01:00
parent c8ab7909ee
commit 5d1b3d88f7

View File

@ -31,6 +31,15 @@ struct WorkoutDetailView: View {
@State @State
private var privateMetadata: [String : Any] = [:] private var privateMetadata: [String : Any] = [:]
@State
private var showErrorMessage = false
@State
private var errorMessage: String = ""
@State
private var isProcessingWorkout = false
private var metadataFields: [(key: String, value: Any)] { private var metadataFields: [(key: String, value: Any)] {
workout.metadata.sorted { $0.key } workout.metadata.sorted { $0.key }
} }
@ -39,9 +48,6 @@ struct WorkoutDetailView: View {
privateMetadata.sorted { $0.key } privateMetadata.sorted { $0.key }
} }
@State
private var isProcessingWorkout = false
private var averageHeartRate: Int { private var averageHeartRate: Int {
let sum = heartRateSamples.reduce(0) { $0 + $1.beatsPerMinute } let sum = heartRateSamples.reduce(0) { $0 + $1.beatsPerMinute }
return (Double(sum) / Double(heartRateSamples.count)).roundedInt return (Double(sum) / Double(heartRateSamples.count)).roundedInt
@ -61,24 +67,24 @@ struct WorkoutDetailView: View {
.padding(.vertical, 8) .padding(.vertical, 8)
.listRowBackground(Color.accentColor) .listRowBackground(Color.accentColor)
} else { } else {
Button(action: addWorkoutToHealth) { Button(action: addWorkoutToHealth) {
HStack { HStack {
Spacer() Spacer()
if isProcessingWorkout { if isProcessingWorkout {
ProgressView() ProgressView()
.progressViewStyle(.circular) .progressViewStyle(.circular)
.padding(.trailing, 10) .padding(.trailing, 10)
Text("Adding workout to health...") Text("Adding workout to health...")
.foregroundStyle(.accent) .foregroundStyle(.accent)
} else { } else {
Text("Add workout to health") Text("Add workout to health")
.foregroundStyle(.accent) .foregroundStyle(.accent)
}
Spacer()
} }
.padding(.vertical, 8) Spacer()
} }
.disabled(isProcessingWorkout) .padding(.vertical, 8)
}
.disabled(isProcessingWorkout)
} }
Section("Info") { Section("Info") {
DetailRow("Start", date: workout.startDate) DetailRow("Start", date: workout.startDate)
@ -158,6 +164,18 @@ struct WorkoutDetailView: View {
EventDetailView(event: event) EventDetailView(event: event)
} }
.onAppear(perform: loadSamples) .onAppear(perform: loadSamples)
.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 addWorkoutToHealth() { private func addWorkoutToHealth() {
@ -168,58 +186,76 @@ struct WorkoutDetailView: View {
self.isProcessingWorkout = true self.isProcessingWorkout = true
} }
Task { Task {
do { await insert(workout: workout, using: db)
try await insert(workout: workout, using: db)
} catch {
print("Failed to insert workout: \(error)")
}
DispatchQueue.main.async { DispatchQueue.main.async {
self.isProcessingWorkout = false self.isProcessingWorkout = false
} }
} }
} }
private func insert(workout: Workout, using db: HealthDatabase) async throws { private func requestAuthorization() async -> Bool {
try await store.requestAuthorization(toShare: HKWorkout.self, read: HKWorkout.self) do {
try await store.requestAuthorization(
toShare: HKWorkout.self, HKWorkoutRoute.self,
read: HKWorkout.self, HKWorkoutRoute.self)
return true
} catch {
show("Failed to check for workout permissions: \(error)")
return false
}
}
private func insert(workout: Workout, using db: HealthDatabase) async {
guard await requestAuthorization() else { return }
if store.authorizationStatus(for: HKWorkout.self) == .notDetermined || if store.authorizationStatus(for: HKWorkout.self) == .notDetermined ||
store.authorizationStatus(for: HKWorkoutRoute.self) == .notDetermined { store.authorizationStatus(for: HKWorkoutRoute.self) == .notDetermined {
print("Requesting workout sharing permission") print("Requesting workout permissions")
try await store.requestAuthorization(toShare: HKWorkout.self, HKWorkoutRoute.self, read: HKWorkout.self, HKWorkoutRoute.self) guard await requestAuthorization() else { return }
} }
guard store.authorizationStatus(for: HKWorkout.self) == .sharingAuthorized else { guard store.authorizationStatus(for: HKWorkout.self) == .sharingAuthorized else {
print("No sharing permission for workouts") show("No sharing permission for workouts")
return return
} }
guard store.authorizationStatus(for: HKWorkoutRoute.self) == .sharingAuthorized else { guard store.authorizationStatus(for: HKWorkoutRoute.self) == .sharingAuthorized else {
print("No sharing permission for workout routes") show("No sharing permission for workout routes")
return return
} }
let samples: [HKSample]
do { do {
print("Getting samples") samples = try db.store.samples(associatedWith: workout)
let samples = try db.store.samples(associatedWith: workout) } catch {
let route = try db.store.route(associatedWith: workout) show("Failed to access samples associated with workout: \(error)")
.map { try db.store.locations(associatedWith: $0) } ?? [] return
}
print("Saving workout in Health: \(samples.count) samples, \(route.count) locations") let route: WorkoutRoute?
do {
let savedWorkout = try await workout.insert( route = try db.store.route(associatedWith: workout)
} catch {
show("Failed to get route associated with workout: \(error)")
return
}
let locations: [CLLocation]
do {
locations = try route.map {
try db.store.locations(associatedWith: $0)
} ?? []
} catch {
show("Failed to get locations associated with route: \(error)")
return
}
print("Saving workout in Health: \(samples.count) samples, \(locations.count) locations")
let savedWorkout: HKWorkout
do {
savedWorkout = try await workout.insert(
into: store.store, into: store.store,
samples: samples, samples: samples,
route: route) route: locations)
DispatchQueue.main.async {
self.healthWorkout = savedWorkout
}
print("Saved workout in Health")
let energySamples: [ActiveEnergyBurned] = try await store.samples(associatedWith: savedWorkout)
print("Found \(energySamples.count) energy samples")
if let route = try await store.route(associatedWith: savedWorkout) {
let locations = try await store.locations(associatedWith: route)
print("Found \(locations.count)/\(locationSamples.count) locations associated with saved workout")
} else {
print("No route associated with saved workout")
}
} catch { } catch {
print("Failed to add workout to health: \(error)") show("Failed to insert workout: \(error)")
return
}
DispatchQueue.main.async {
self.healthWorkout = savedWorkout
} }
} }
@ -243,9 +279,8 @@ struct WorkoutDetailView: View {
self.heartRateSamples = samples self.heartRateSamples = samples
self.samples = graphSamples self.samples = graphSamples
} }
print("Loaded \(samples.count) heart rate samples from database")
} catch { } catch {
print("Failed to load heart rate samples from database: \(error)") show("Failed to load heart rate samples from database: \(error)")
} }
} }
@ -261,7 +296,7 @@ struct WorkoutDetailView: View {
} }
print("Loaded \(locations.count) locations from database") print("Loaded \(locations.count) locations from database")
} catch { } catch {
print("Failed to load locations or route from database: \(error)") show("Failed to load locations or route from database: \(error)")
} }
} }
@ -272,9 +307,8 @@ struct WorkoutDetailView: View {
DispatchQueue.main.async { DispatchQueue.main.async {
self.privateMetadata = metadata self.privateMetadata = metadata
} }
print("Loaded \(metadata.count) private metadata fields")
} catch { } catch {
print("Failed to load private metadata from database: \(error)") show("Failed to load private metadata from database: \(error)")
} }
} }
@ -285,7 +319,7 @@ struct WorkoutDetailView: View {
do { do {
try await checkPermissionsAndFindWorkout() try await checkPermissionsAndFindWorkout()
} catch { } catch {
print("Failed to search for workout: \(error)") show("Failed to search for similar workout in Health: \(error)")
} }
} }
@ -298,7 +332,7 @@ struct WorkoutDetailView: View {
await findWorkoutInHealth() await findWorkoutInHealth()
return return
@unknown default: @unknown default:
print("Unknown permission for workouts") show("Unknown permission for workouts")
return return
} }
} }
@ -309,7 +343,7 @@ struct WorkoutDetailView: View {
private func findWorkoutInHealth() async { private func findWorkoutInHealth() async {
guard let activityType = workout.workoutActivities.first?.workoutConfiguration.activityType else { guard let activityType = workout.workoutActivities.first?.workoutConfiguration.activityType else {
print("No activity type to find workout") show("No activity type associated with workout")
return return
} }
@ -322,12 +356,11 @@ struct WorkoutDetailView: View {
return return
} }
print("Found matching workout in health")
DispatchQueue.main.async { DispatchQueue.main.async {
self.healthWorkout = workout self.healthWorkout = workout
} }
} catch { } catch {
print("Failed to search for matching workout: \(error)") show("Failed to search for matching workout: \(error)")
} }
} }
} }