From 84bf4c0d67208248a51b54feb54d343bd73a75b5 Mon Sep 17 00:00:00 2001 From: Sylvain Bettinelli Date: Tue, 12 May 2026 22:14:37 +0000 Subject: [PATCH] feat(workoutkit): support label per step (WorkoutStep.displayName) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plugin accepte maintenant un champ optionnel `label` sur chaque step (work/rest ou blockSteps[]). Si fourni, override l'affichage natif Apple "Travail"/"Récupération" sur l'Apple Watch par le label custom (ex. "Course"/"Marche"). API étendue : - work: { duration_min, hr_zone, label? } - rest: { duration_min, hr_zone, label? } - blockSteps: [{ type, duration_min, hr_zone, label? }] Aucune régression : si label absent ou vide, comportement inchangé. Nécessite rebuild iOS pour prendre effet (Mac mini en attente macOS 26). Co-Authored-By: Claude Opus 4.7 (1M context) --- ios/App/App/CoachWorkoutKit.swift | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/ios/App/App/CoachWorkoutKit.swift b/ios/App/App/CoachWorkoutKit.swift index 4a1aba0..e90f6af 100644 --- a/ios/App/App/CoachWorkoutKit.swift +++ b/ios/App/App/CoachWorkoutKit.swift @@ -60,30 +60,36 @@ public class CoachWorkoutKitPlugin: CAPPlugin, CAPBridgedPlugin { let cooldownMin = call.getDouble("cooldownMin") ?? 10 let repeats = max(1, min(50, call.getInt("repeats") ?? 6)) - // Nouveau format : blockSteps = [{ type, duration_min, hr_zone }, ...] + // Nouveau format : blockSteps = [{ type, duration_min, hr_zone, label }, ...] // type = "work" | "recovery" + // label (optional) = displayName affiché sur l'Apple Watch pour ce step + // (ex. "Course", "Marche", "Vélo"). Si nil → Watch affiche le label + // natif Apple "Travail" / "Récupération". // Backward compat : si pas blockSteps, lit work + rest comme avant. - var stepsSpec: [(min: Double, hrZone: Int?, isWork: Bool)] = [] + var stepsSpec: [(min: Double, hrZone: Int?, isWork: Bool, label: String?)] = [] if let raw = call.getArray("blockSteps") as? [[String: Any]], !raw.isEmpty { for (i, s) in raw.enumerated() { let durMin = (s["duration_min"] as? Double) ?? 0 if durMin <= 0 { continue } let hrZone = s["hr_zone"] as? Int let typeStr = (s["type"] as? String) ?? (i % 2 == 0 ? "work" : "recovery") - stepsSpec.append((durMin, hrZone, typeStr == "work")) + let label = s["label"] as? String + stepsSpec.append((durMin, hrZone, typeStr == "work", label)) } } else { // Backward compat — work + rest (rest skipped si 0) let workDict = call.getObject("work") ?? [:] let workMin = workDict["duration_min"] as? Double ?? 3 let workHrZone = workDict["hr_zone"] as? Int - stepsSpec.append((workMin, workHrZone, true)) + let workLabel = workDict["label"] as? String + stepsSpec.append((workMin, workHrZone, true, workLabel)) let restDict = call.getObject("rest") ?? [:] let restMin = restDict["duration_min"] as? Double ?? 1.5 let restHrZone = restDict["hr_zone"] as? Int + let restLabel = restDict["label"] as? String if restMin > 0 { - stepsSpec.append((restMin, restHrZone, false)) + stepsSpec.append((restMin, restHrZone, false, restLabel)) } } @@ -140,7 +146,7 @@ public class CoachWorkoutKitPlugin: CAPPlugin, CAPBridgedPlugin { activity: HKWorkoutActivityType, displayName: String, warmupMin: Double, - stepsSpec: [(min: Double, hrZone: Int?, isWork: Bool)], + stepsSpec: [(min: Double, hrZone: Int?, isWork: Bool, label: String?)], repeats: Int, cooldownMin: Double ) async throws { @@ -149,12 +155,17 @@ public class CoachWorkoutKitPlugin: CAPPlugin, CAPBridgedPlugin { let warmupStep = WorkoutStep(goal: .time(warmupMin * 60, .seconds)) // 2. Construit la liste d'IntervalSteps depuis stepsSpec. - // Chaque entrée = (durée min, zone HR optionnelle, isWork bool). + // Chaque entrée = (durée min, zone HR optionnelle, isWork bool, label optionnel). + // Si label fourni, override l'affichage natif Apple ("Travail"/"Récupération") + // par "Course"/"Marche"/"Vélo" etc. selon le sport. let intervalSteps: [IntervalStep] = stepsSpec.map { spec in var ws = WorkoutStep(goal: .time(spec.min * 60, .seconds)) if let zone = spec.hrZone { ws.alert = HeartRateZoneAlert(zone: zone) } + if let label = spec.label, !label.isEmpty { + ws.displayName = label + } return IntervalStep(spec.isWork ? .work : .recovery, step: ws) }