feat(watch): coches au grain exercice sur la montre
Pendant natif du commit coach_sportif 01bad9e. Les 24 exercices sont cochables un par un pendant la séance, comme sur le web. - RoutineExercise + items dans RoutineBlock ; les exercices voyagent avec le snapshot, rien n'est codé en dur côté watchOS - l'en-tête de bloc est tappable : coche/décoche tous ses exercices d'un coup, pour enchaîner une série sans s'arrêter à chaque ligne - compteur n/N par bloc + progression globale en exercices - CoachRoutineBridge.sanitizeBlocks transmet désormais les items Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,13 @@ import os
|
||||
|
||||
private let routineLog = Logger(subsystem: "ch.hypnotruck.coach.watchkitapp", category: "routine")
|
||||
|
||||
/// Un exercice — c'est à ce grain que l'on coche, sur la montre comme sur le web.
|
||||
struct RoutineExercise: Codable, Identifiable, Equatable {
|
||||
let id: String
|
||||
let name: String
|
||||
let dose: String
|
||||
}
|
||||
|
||||
/// Un bloc de la routine quotidienne, tel que poussé par l'iPhone.
|
||||
struct RoutineBlock: Codable, Identifiable, Equatable {
|
||||
let id: String
|
||||
@@ -11,9 +18,10 @@ struct RoutineBlock: Codable, Identifiable, Equatable {
|
||||
let title: String
|
||||
let durationMin: Int
|
||||
let moment: String // "matin" | "après la course"
|
||||
let items: [RoutineExercise]
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case id, num, title
|
||||
case id, num, title, items
|
||||
case durationMin = "duration_min"
|
||||
case moment
|
||||
}
|
||||
@@ -51,6 +59,18 @@ final class RoutineStore: ObservableObject {
|
||||
var morningBlocks: [RoutineBlock] { blocks.filter { $0.moment == "matin" } }
|
||||
var otherBlocks: [RoutineBlock] { blocks.filter { $0.moment != "matin" } }
|
||||
|
||||
/// Nombre total d'exercices — le dénominateur de la progression.
|
||||
var totalExercises: Int { blocks.reduce(0) { $0 + $1.items.count } }
|
||||
|
||||
func doneCount(in block: RoutineBlock) -> Int {
|
||||
block.items.filter { done.contains($0.id) }.count
|
||||
}
|
||||
|
||||
/// Un bloc est fait quand tous ses exercices le sont. Dérivé, jamais stocké.
|
||||
func isComplete(_ block: RoutineBlock) -> Bool {
|
||||
!block.items.isEmpty && doneCount(in: block) == block.items.count
|
||||
}
|
||||
|
||||
// MARK: - Persistance locale
|
||||
|
||||
private func load() {
|
||||
@@ -102,12 +122,25 @@ final class RoutineStore: ObservableObject {
|
||||
|
||||
// MARK: - Watch → iPhone
|
||||
|
||||
func toggle(_ blockId: String) {
|
||||
func toggle(_ exerciseId: String) {
|
||||
resetIfStale()
|
||||
if done.contains(blockId) {
|
||||
done.remove(blockId)
|
||||
if done.contains(exerciseId) {
|
||||
done.remove(exerciseId)
|
||||
} else {
|
||||
done.insert(blockId)
|
||||
done.insert(exerciseId)
|
||||
}
|
||||
persist()
|
||||
push()
|
||||
}
|
||||
|
||||
/// Coche/décoche un bloc entier — pratique quand on enchaîne une série
|
||||
/// complète sans s'arrêter à chaque exercice.
|
||||
func toggleBlock(_ block: RoutineBlock) {
|
||||
resetIfStale()
|
||||
if isComplete(block) {
|
||||
block.items.forEach { done.remove($0.id) }
|
||||
} else {
|
||||
block.items.forEach { done.insert($0.id) }
|
||||
}
|
||||
persist()
|
||||
push()
|
||||
|
||||
Reference in New Issue
Block a user