From fac7d95cd1e877dcc3b319cfb3e1a5de92c70fd9 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Tue, 26 May 2026 20:07:17 +0200 Subject: [PATCH] =?UTF-8?q?feat(healthkit):=20deleteWorkout=20dans=20Coach?= =?UTF-8?q?HealthRoute=20(supprime=20workout=20d'Apple=20Sant=C3=A9)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Nouveau plugin method @objc deleteWorkout(workoutUUID:) -> retrouve le HKWorkout par UUID puis HKHealthStore.delete. requestAuthorization étendu pour inclure share auth de HKWorkoutType (requise pour delete). Utilisé par la page /workouts au swipe-to-delete pour synchroniser la suppression avec Apple Santé. Co-Authored-By: Claude Opus 4.7 (1M context) --- ios/App/App/CoachHealthRoute.swift | 48 +++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/ios/App/App/CoachHealthRoute.swift b/ios/App/App/CoachHealthRoute.swift index 87da1a6..d1f60e2 100644 --- a/ios/App/App/CoachHealthRoute.swift +++ b/ios/App/App/CoachHealthRoute.swift @@ -25,6 +25,7 @@ public class CoachHealthRoutePlugin: CAPPlugin, CAPBridgedPlugin { CAPPluginMethod(name: "requestAuthorization", returnType: CAPPluginReturnPromise), CAPPluginMethod(name: "getRoute", returnType: CAPPluginReturnPromise), CAPPluginMethod(name: "getCharacteristics", returnType: CAPPluginReturnPromise), + CAPPluginMethod(name: "deleteWorkout", returnType: CAPPluginReturnPromise), ] private lazy var store = HKHealthStore() @@ -51,7 +52,10 @@ public class CoachHealthRoutePlugin: CAPPlugin, CAPBridgedPlugin { if let dob = HKObjectType.characteristicType(forIdentifier: .dateOfBirth) { types.insert(dob) } if let sex = HKObjectType.characteristicType(forIdentifier: .biologicalSex) { types.insert(sex) } if let blood = HKObjectType.characteristicType(forIdentifier: .bloodType) { types.insert(blood) } - store.requestAuthorization(toShare: nil, read: types) { ok, err in + // Share workoutType requis pour deleteWorkout (HKHealthStore.delete + // exige share auth). Read-only pour les autres types. + let shareTypes: Set = [HKObjectType.workoutType()] + store.requestAuthorization(toShare: shareTypes, read: types) { ok, err in if let err = err { call.resolve(["granted": false, "reason": err.localizedDescription]) } else { @@ -201,4 +205,46 @@ public class CoachHealthRoutePlugin: CAPPlugin, CAPBridgedPlugin { call.resolve(["available": true, "route": payload, "count": payload.count]) } } + + /// Supprime un workout d'Apple Santé via son UUID. Requiert l'autorisation + /// share pour HKWorkoutType (demandée dans requestAuthorization). Renvoie + /// {deleted: true} si trouvé+supprimé, {deleted: false, reason: ...} si + /// introuvable, ou reject() si erreur HealthKit. + @objc func deleteWorkout(_ call: CAPPluginCall) { + guard HKHealthStore.isHealthDataAvailable() else { + call.reject("HealthKit unavailable") + return + } + guard let uuidStr = call.getString("workoutUUID"), + let uuid = UUID(uuidString: uuidStr) else { + call.reject("workoutUUID requis (UUID string)") + return + } + // 1. Retrouver le HKWorkout via son UUID (même pattern que getRoute). + let pred = HKQuery.predicateForObject(with: uuid) + let wq = HKSampleQuery( + sampleType: HKObjectType.workoutType(), + predicate: pred, + limit: 1, + sortDescriptors: nil + ) { _, results, err in + if let err = err { + call.reject("Workout fetch failed: \(err.localizedDescription)") + return + } + guard let workout = results?.first as? HKWorkout else { + call.resolve(["deleted": false, "reason": "workout not found"]) + return + } + // 2. Supprime le workout d'Apple Santé (requiert share auth pour workoutType). + self.store.delete(workout) { success, err in + if let err = err { + call.reject("Delete failed: \(err.localizedDescription)") + return + } + call.resolve(["deleted": success]) + } + } + store.execute(wq) + } }