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