Show errors on import
This commit is contained in:
parent
c8ab7909ee
commit
5d1b3d88f7
@ -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)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user