From e371d0a561f2fec49d5d260b90c871f21ab43685 Mon Sep 17 00:00:00 2001 From: Sylvain Bettinelli Date: Wed, 13 May 2026 04:43:30 +0000 Subject: [PATCH] feat(health): expose getCharacteristics (DOB / sexe / groupe sanguin) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Nouvelle méthode CoachHealthRoutePlugin.getCharacteristics qui lit les caractéristiques read-only depuis HealthKit : - dateOfBirth (ISO8601 date) - biologicalSex (male/female/other) - bloodType (A+/A-/B+/B-/AB+/AB-/O+/O-) Auth étendue : on demande aussi read sur dateOfBirth, biologicalSex et bloodType en plus de workoutType + workoutRoute. Utilisé côté web par la page Profil pour auto-remplir les champs sans saisie manuelle (cf. /api/profile/healthkit-suggestions). Co-Authored-By: Claude Opus 4.7 (1M context) --- ios/App/App/CoachHealthRoute.swift | 44 +++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/ios/App/App/CoachHealthRoute.swift b/ios/App/App/CoachHealthRoute.swift index ffa7967..87da1a6 100644 --- a/ios/App/App/CoachHealthRoute.swift +++ b/ios/App/App/CoachHealthRoute.swift @@ -24,6 +24,7 @@ public class CoachHealthRoutePlugin: CAPPlugin, CAPBridgedPlugin { CAPPluginMethod(name: "isAvailable", returnType: CAPPluginReturnPromise), CAPPluginMethod(name: "requestAuthorization", returnType: CAPPluginReturnPromise), CAPPluginMethod(name: "getRoute", returnType: CAPPluginReturnPromise), + CAPPluginMethod(name: "getCharacteristics", returnType: CAPPluginReturnPromise), ] private lazy var store = HKHealthStore() @@ -42,10 +43,14 @@ public class CoachHealthRoutePlugin: CAPPlugin, CAPBridgedPlugin { call.resolve(["granted": false, "reason": "HealthKit unavailable"]) return } - let types: Set = [ + var types: Set = [ HKObjectType.workoutType(), HKSeriesType.workoutRoute(), ] + // Caractéristiques (DOB, sexe, groupe sanguin) — read-only depuis HK + 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 if let err = err { call.resolve(["granted": false, "reason": err.localizedDescription]) @@ -55,6 +60,43 @@ public class CoachHealthRoutePlugin: CAPPlugin, CAPBridgedPlugin { } } + @objc func getCharacteristics(_ call: CAPPluginCall) { + guard HKHealthStore.isHealthDataAvailable() else { + call.resolve(["available": false, "reason": "HealthKit unavailable"]) + return + } + var result: [String: Any] = ["available": true] + + // Date de naissance + if let dob = try? store.dateOfBirthComponents() { + if let date = Calendar.current.date(from: dob) { + let fmt = ISO8601DateFormatter() + fmt.formatOptions = [.withFullDate] + result["dateOfBirth"] = fmt.string(from: date) + } + } + // Sexe biologique + if let bs = try? store.biologicalSex() { + switch bs.biologicalSex { + case .male: result["biologicalSex"] = "male" + case .female: result["biologicalSex"] = "female" + case .other: result["biologicalSex"] = "other" + default: break // notSet + } + } + // Groupe sanguin + if let bt = try? store.bloodType() { + let map: [HKBloodType: String] = [ + .aPositive: "A+", .aNegative: "A-", + .bPositive: "B+", .bNegative: "B-", + .abPositive: "AB+", .abNegative: "AB-", + .oPositive: "O+", .oNegative: "O-", + ] + if let s = map[bt.bloodType] { result["bloodType"] = s } + } + call.resolve(result) + } + @objc func getRoute(_ call: CAPPluginCall) { guard HKHealthStore.isHealthDataAvailable() else { call.resolve(["available": false, "reason": "HealthKit unavailable", "route": []])