From 0bd3c8830036dbe5c890d52a51cdae1dc1f2c3f4 Mon Sep 17 00:00:00 2001 From: Sylvain Bettinelli Date: Fri, 8 May 2026 10:38:45 +0000 Subject: [PATCH] WorkoutKit Swift : sendInterval accepte blockSteps[] (N steps custom) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Avant : work + rest fixes (2 steps) avec repeats sur le couple. Maintenant : payload optionnel blockSteps = [{type, duration_min, hr_zone}, ...] qui permet N steps custom dans le bloc à répéter. Backward compat : si pas de blockSteps, lit work + rest comme avant. Refactor de sendCustomWorkout : prend stepsSpec: [(min, hrZone, isWork)] au lieu de work*/rest* params séparés. Génère N IntervalSteps depuis le spec, met dans 1 IntervalBlock avec iterations=repeats. --- ios/App/App/CoachWorkoutKit.swift | 73 +++++++++++++++++++------------ 1 file changed, 44 insertions(+), 29 deletions(-) diff --git a/ios/App/App/CoachWorkoutKit.swift b/ios/App/App/CoachWorkoutKit.swift index 0b927f0..4a1aba0 100644 --- a/ios/App/App/CoachWorkoutKit.swift +++ b/ios/App/App/CoachWorkoutKit.swift @@ -60,13 +60,37 @@ public class CoachWorkoutKitPlugin: CAPPlugin, CAPBridgedPlugin { let cooldownMin = call.getDouble("cooldownMin") ?? 10 let repeats = max(1, min(50, call.getInt("repeats") ?? 6)) - let workDict = call.getObject("work") ?? [:] - let workMin = workDict["duration_min"] as? Double ?? 3 - let workHrZone = workDict["hr_zone"] as? Int + // Nouveau format : blockSteps = [{ type, duration_min, hr_zone }, ...] + // type = "work" | "recovery" + // Backward compat : si pas blockSteps, lit work + rest comme avant. + var stepsSpec: [(min: Double, hrZone: Int?, isWork: Bool)] = [] + 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")) + } + } 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 restDict = call.getObject("rest") ?? [:] - let restMin = restDict["duration_min"] as? Double ?? 1.5 - let restHrZone = restDict["hr_zone"] as? Int + let restDict = call.getObject("rest") ?? [:] + let restMin = restDict["duration_min"] as? Double ?? 1.5 + let restHrZone = restDict["hr_zone"] as? Int + if restMin > 0 { + stepsSpec.append((restMin, restHrZone, false)) + } + } + + if stepsSpec.isEmpty { + call.reject("Au moins 1 step (durée > 0) requis dans le bloc") + return + } let activity = workoutActivity(from: activityStr) @@ -76,10 +100,7 @@ public class CoachWorkoutKitPlugin: CAPPlugin, CAPBridgedPlugin { activity: activity, displayName: displayName, warmupMin: warmupMin, - workMin: workMin, - workHrZone: workHrZone, - restMin: restMin, - restHrZone: restHrZone, + stepsSpec: stepsSpec, repeats: repeats, cooldownMin: cooldownMin ) @@ -119,10 +140,7 @@ public class CoachWorkoutKitPlugin: CAPPlugin, CAPBridgedPlugin { activity: HKWorkoutActivityType, displayName: String, warmupMin: Double, - workMin: Double, - workHrZone: Int?, - restMin: Double, - restHrZone: Int?, + stepsSpec: [(min: Double, hrZone: Int?, isWork: Bool)], repeats: Int, cooldownMin: Double ) async throws { @@ -130,24 +148,21 @@ public class CoachWorkoutKitPlugin: CAPPlugin, CAPBridgedPlugin { // 1. Warmup — un WorkoutStep avec un goal time let warmupStep = WorkoutStep(goal: .time(warmupMin * 60, .seconds)) - // 2. Work step — wrapped dans un IntervalStep avec purpose .work - var workWorkoutStep = WorkoutStep(goal: .time(workMin * 60, .seconds)) - if let zone = workHrZone { - workWorkoutStep.alert = HeartRateZoneAlert(zone: zone) + // 2. Construit la liste d'IntervalSteps depuis stepsSpec. + // Chaque entrée = (durée min, zone HR optionnelle, isWork bool). + 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) + } + return IntervalStep(spec.isWork ? .work : .recovery, step: ws) } - let workInterval = IntervalStep(.work, step: workWorkoutStep) - // 3. Rest step — IntervalStep purpose .recovery - var restWorkoutStep = WorkoutStep(goal: .time(restMin * 60, .seconds)) - if let zone = restHrZone { - restWorkoutStep.alert = HeartRateZoneAlert(zone: zone) - } - let restInterval = IntervalStep(.recovery, step: restWorkoutStep) + // 3. Block répété N fois — la séquence COMPLÈTE de tous les steps + // se répète repeats fois. + let block = IntervalBlock(steps: intervalSteps, iterations: repeats) - // 4. Block répété N fois - let block = IntervalBlock(steps: [workInterval, restInterval], iterations: repeats) - - // 5. Cooldown + // 4. Cooldown let cooldownStep = WorkoutStep(goal: .time(cooldownMin * 60, .seconds)) // 6. CustomWorkout — location selon l'activité (indoor pour