From 5d1b3d88f757809778613ee22595f3cbb8648674 Mon Sep 17 00:00:00 2001 From: Christoph Hagen Date: Tue, 19 Mar 2024 17:57:43 +0100 Subject: [PATCH] Show errors on import --- HealthImport/WorkoutDetailView.swift | 157 ++++++++++++++++----------- 1 file changed, 95 insertions(+), 62 deletions(-) diff --git a/HealthImport/WorkoutDetailView.swift b/HealthImport/WorkoutDetailView.swift index ad4da05..d51e744 100644 --- a/HealthImport/WorkoutDetailView.swift +++ b/HealthImport/WorkoutDetailView.swift @@ -31,6 +31,15 @@ struct WorkoutDetailView: View { @State 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)] { workout.metadata.sorted { $0.key } } @@ -39,9 +48,6 @@ struct WorkoutDetailView: View { privateMetadata.sorted { $0.key } } - @State - private var isProcessingWorkout = false - private var averageHeartRate: Int { let sum = heartRateSamples.reduce(0) { $0 + $1.beatsPerMinute } return (Double(sum) / Double(heartRateSamples.count)).roundedInt @@ -61,24 +67,24 @@ struct WorkoutDetailView: View { .padding(.vertical, 8) .listRowBackground(Color.accentColor) } else { - Button(action: addWorkoutToHealth) { - HStack { - Spacer() - if isProcessingWorkout { - ProgressView() - .progressViewStyle(.circular) - .padding(.trailing, 10) - Text("Adding workout to health...") - .foregroundStyle(.accent) - } else { - Text("Add workout to health") - .foregroundStyle(.accent) - } - Spacer() + Button(action: addWorkoutToHealth) { + HStack { + Spacer() + if isProcessingWorkout { + ProgressView() + .progressViewStyle(.circular) + .padding(.trailing, 10) + Text("Adding workout to health...") + .foregroundStyle(.accent) + } else { + Text("Add workout to health") + .foregroundStyle(.accent) } - .padding(.vertical, 8) + Spacer() } - .disabled(isProcessingWorkout) + .padding(.vertical, 8) + } + .disabled(isProcessingWorkout) } Section("Info") { DetailRow("Start", date: workout.startDate) @@ -158,6 +164,18 @@ struct WorkoutDetailView: View { EventDetailView(event: event) } .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() { @@ -168,58 +186,76 @@ struct WorkoutDetailView: View { self.isProcessingWorkout = true } Task { - do { - try await insert(workout: workout, using: db) - } catch { - print("Failed to insert workout: \(error)") - } + await insert(workout: workout, using: db) DispatchQueue.main.async { self.isProcessingWorkout = false } } } - private func insert(workout: Workout, using db: HealthDatabase) async throws { - try await store.requestAuthorization(toShare: HKWorkout.self, read: HKWorkout.self) + private func requestAuthorization() async -> Bool { + 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 || store.authorizationStatus(for: HKWorkoutRoute.self) == .notDetermined { - print("Requesting workout sharing permission") - try await store.requestAuthorization(toShare: HKWorkout.self, HKWorkoutRoute.self, read: HKWorkout.self, HKWorkoutRoute.self) + print("Requesting workout permissions") + guard await requestAuthorization() else { return } } guard store.authorizationStatus(for: HKWorkout.self) == .sharingAuthorized else { - print("No sharing permission for workouts") + show("No sharing permission for workouts") return } guard store.authorizationStatus(for: HKWorkoutRoute.self) == .sharingAuthorized else { - print("No sharing permission for workout routes") + show("No sharing permission for workout routes") return } + let samples: [HKSample] do { - print("Getting samples") - let samples = try db.store.samples(associatedWith: workout) - let route = try db.store.route(associatedWith: workout) - .map { try db.store.locations(associatedWith: $0) } ?? [] - - print("Saving workout in Health: \(samples.count) samples, \(route.count) locations") - - let savedWorkout = try await workout.insert( + samples = try db.store.samples(associatedWith: workout) + } catch { + show("Failed to access samples associated with workout: \(error)") + return + } + let route: WorkoutRoute? + do { + 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, samples: samples, - route: route) - 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") - } + route: locations) } 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.samples = graphSamples } - print("Loaded \(samples.count) heart rate samples from database") } 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") } 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 { self.privateMetadata = metadata } - print("Loaded \(metadata.count) private metadata fields") } 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 { try await checkPermissionsAndFindWorkout() } 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() return @unknown default: - print("Unknown permission for workouts") + show("Unknown permission for workouts") return } } @@ -309,7 +343,7 @@ struct WorkoutDetailView: View { private func findWorkoutInHealth() async { guard let activityType = workout.workoutActivities.first?.workoutConfiguration.activityType else { - print("No activity type to find workout") + show("No activity type associated with workout") return } @@ -322,12 +356,11 @@ struct WorkoutDetailView: View { return } - print("Found matching workout in health") DispatchQueue.main.async { self.healthWorkout = workout } } catch { - print("Failed to search for matching workout: \(error)") + show("Failed to search for matching workout: \(error)") } } }