feat(workoutkit): support label per step (WorkoutStep.displayName)

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) <noreply@anthropic.com>
This commit is contained in:
Sylvain Bettinelli
2026-05-12 22:14:37 +00:00
parent 97e640fb57
commit 84bf4c0d67

View File

@@ -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)
}