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