Allow deletion of workouts
This commit is contained in:
parent
7e66f81aa6
commit
ee1993e757
@ -40,6 +40,9 @@ struct WorkoutDetailView: View {
|
||||
@State
|
||||
private var isProcessingWorkout = false
|
||||
|
||||
@State
|
||||
private var healthButtonText = "Checking for workout in Health..."
|
||||
|
||||
private var metadataFields: [(key: String, value: Any)] {
|
||||
workout.metadata.sorted { $0.key }
|
||||
}
|
||||
@ -51,37 +54,28 @@ struct WorkoutDetailView: View {
|
||||
|
||||
var body: some View {
|
||||
List {
|
||||
if healthWorkout != nil {
|
||||
Button(action: addOrDeleteHealthWorkout) {
|
||||
HStack {
|
||||
Spacer()
|
||||
if isProcessingWorkout {
|
||||
ProgressView()
|
||||
.progressViewStyle(.circular)
|
||||
.padding(.trailing, 10)
|
||||
}
|
||||
VStack {
|
||||
Text("Matching workout found in Health")
|
||||
.foregroundStyle(.black)
|
||||
Text(healthButtonText)
|
||||
.foregroundStyle(.accent)
|
||||
if healthWorkout != nil {
|
||||
Text("Tap to delete")
|
||||
.font(.caption)
|
||||
.foregroundStyle(.gray)
|
||||
}
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
.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()
|
||||
}
|
||||
.padding(.vertical, 8)
|
||||
}
|
||||
.disabled(isProcessingWorkout)
|
||||
}
|
||||
.disabled(isProcessingWorkout)
|
||||
Section("Info") {
|
||||
DetailRow("Start", date: workout.startDate)
|
||||
DetailRow("Duration", duration: workout.duration)
|
||||
@ -180,21 +174,65 @@ struct WorkoutDetailView: View {
|
||||
}
|
||||
}
|
||||
|
||||
private func addWorkoutToHealth() {
|
||||
guard let db = database.store else {
|
||||
private func updateButtonText() {
|
||||
if isProcessingWorkout {
|
||||
if healthWorkout == nil {
|
||||
self.healthButtonText = "Adding workout to Health..."
|
||||
} else {
|
||||
self.healthButtonText = "Deleting workout from Health..."
|
||||
}
|
||||
} else {
|
||||
if healthWorkout == nil {
|
||||
healthButtonText = "Add workout to Health"
|
||||
} else {
|
||||
healthButtonText = "Found matching workout in Health"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func addOrDeleteHealthWorkout() {
|
||||
guard !isProcessingWorkout else {
|
||||
return
|
||||
}
|
||||
DispatchQueue.main.async {
|
||||
self.isProcessingWorkout = true
|
||||
updateButtonText()
|
||||
}
|
||||
Task {
|
||||
await insert(workout: workout, using: db)
|
||||
if let healthWorkout {
|
||||
await delete(healthWorkout: healthWorkout)
|
||||
} else {
|
||||
await addWorkoutToHealth()
|
||||
}
|
||||
DispatchQueue.main.async {
|
||||
self.isProcessingWorkout = false
|
||||
updateButtonText()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func delete(healthWorkout: HKWorkout) async {
|
||||
do {
|
||||
try await store.store.delete(healthWorkout)
|
||||
DispatchQueue.main.async {
|
||||
self.healthWorkout = nil
|
||||
self.isProcessingWorkout = false
|
||||
}
|
||||
} catch {
|
||||
show("Failed to delete workout: \(error)")
|
||||
DispatchQueue.main.async {
|
||||
self.isProcessingWorkout = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func addWorkoutToHealth() async {
|
||||
guard let db = database.store else {
|
||||
return
|
||||
}
|
||||
await insert(workout: workout, using: db)
|
||||
}
|
||||
|
||||
private func requestAuthorization() async -> Bool {
|
||||
do {
|
||||
try await store.requestAuthorization(
|
||||
@ -222,16 +260,28 @@ struct WorkoutDetailView: View {
|
||||
show("No sharing permission for workout routes")
|
||||
return
|
||||
}
|
||||
let samples: [HKSample]
|
||||
var samples: [HKSample]
|
||||
do {
|
||||
samples = try db.store.samples(associatedWith: workout)
|
||||
} catch {
|
||||
show("Failed to access samples associated with workout: \(error)")
|
||||
return
|
||||
}
|
||||
|
||||
// Add missing samples from statistics
|
||||
let newSamples = makeStatisticSamples(workout, in: db)
|
||||
for sample in newSamples {
|
||||
guard !samples.contains(where: { $0.sampleType == sample.sampleType }) else {
|
||||
continue
|
||||
}
|
||||
let typeName = HKQuantityTypeIdentifier(rawValue: sample.quantityType.identifier).description
|
||||
print("Adding missing sample \(typeName)")
|
||||
samples.append(sample)
|
||||
}
|
||||
|
||||
let route: WorkoutRoute?
|
||||
do {
|
||||
route = try db.store.route(associatedWith: workout)
|
||||
route = try db.route(associatedWith: workout)
|
||||
} catch {
|
||||
show("Failed to get route associated with workout: \(error)")
|
||||
return
|
||||
@ -239,7 +289,7 @@ struct WorkoutDetailView: View {
|
||||
let locations: [CLLocation]
|
||||
do {
|
||||
locations = try route.map {
|
||||
try db.store.locations(associatedWith: $0)
|
||||
try db.locations(associatedWith: $0)
|
||||
} ?? []
|
||||
} catch {
|
||||
show("Failed to get locations associated with route: \(error)")
|
||||
@ -261,6 +311,22 @@ struct WorkoutDetailView: View {
|
||||
}
|
||||
}
|
||||
|
||||
private func makeStatisticSamples(_ workout: Workout, in db: HealthDatabase) -> [HKQuantitySample] {
|
||||
let startDate = workout.startDate
|
||||
let endDate = workout.endDate
|
||||
let activity = workout.workoutActivities[0]
|
||||
|
||||
do {
|
||||
let statistics = try db.statistics(associatedWith: activity)
|
||||
return statistics.map {
|
||||
.init(type: $0, quantity: $1.average, start: startDate, end: endDate)
|
||||
}
|
||||
} catch {
|
||||
print("Failed to get statistics for activity: \(error)")
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
private func loadSamples() {
|
||||
Task {
|
||||
await checkPermissionsAndSearchHealth()
|
||||
@ -318,11 +384,19 @@ struct WorkoutDetailView: View {
|
||||
guard HKHealthStore.isHealthDataAvailable() else {
|
||||
return
|
||||
}
|
||||
DispatchQueue.main.async {
|
||||
self.healthButtonText = "Checking for matching workout..."
|
||||
self.isProcessingWorkout = true
|
||||
}
|
||||
do {
|
||||
try await checkPermissionsAndFindWorkout()
|
||||
} catch {
|
||||
show("Failed to search for similar workout in Health: \(error)")
|
||||
}
|
||||
DispatchQueue.main.async {
|
||||
self.isProcessingWorkout = false
|
||||
updateButtonText()
|
||||
}
|
||||
}
|
||||
|
||||
private func checkPermissionsAndFindWorkout() async throws {
|
||||
|
Loading…
Reference in New Issue
Block a user