import SwiftUI struct WatchExerciseListView: View { private var hapticProtocols: [WatchRegulationProtocol] { WatchProtocolCatalog.protocols.filter { $0.mode == .haptic } } var body: some View { List(hapticProtocols) { regulation in NavigationLink { WatchExercisePlayerView(regulation: regulation) } label: { VStack(alignment: .leading, spacing: 3) { Text(regulation.title).font(.subheadline.bold()) Text(formatDuration(regulation.durationSeconds)).font(.caption2).foregroundStyle(WatchTheme.secondary) } } } .navigationTitle("Exercices") .haptikaScreen() } } struct WatchExercisePlayerView: View { @EnvironmentObject private var model: WatchAppModel @Environment(\.dismiss) private var dismiss let regulation: WatchRegulationProtocol @State private var startedAt: Date? @State private var errorMessage: String? @State private var sessionSaved = false var body: some View { ScrollView { VStack(spacing: 10) { if model.haptics.isRunning, let startedAt { BreathOrb(regulation: regulation, startedAt: startedAt) ProgressView(value: model.haptics.elapsedSeconds, total: regulation.durationSeconds) Text("\(Int(max(0, regulation.durationSeconds - model.haptics.elapsedSeconds))) s") .font(.caption.monospacedDigit()).foregroundStyle(WatchTheme.secondary) Button("Arrêter", role: .cancel) { saveSession(state: .cancelled) model.haptics.cancel() } } else if startedAt != nil { Image(systemName: "checkmark.circle.fill").font(.title).foregroundStyle(WatchTheme.mint) Text("Exercice terminé").font(.headline) Button("Terminer") { dismiss() }.buttonStyle(.borderedProminent) .onAppear { saveSession(state: .completed) } } else { Text(regulation.eyebrow).font(.caption2.bold()).foregroundStyle(WatchTheme.accent) Text(regulation.title).font(.headline).multilineTextAlignment(.center) Text(regulation.summary).font(.caption2).foregroundStyle(WatchTheme.secondary).multilineTextAlignment(.center) Text("Aucune mesure cardiaque pendant les vibrations.").font(.caption2.bold()).foregroundStyle(.orange) Button("Commencer") { start() }.buttonStyle(.borderedProminent) NavigationLink("Parcours avant / après") { WatchBeforeAfterFlowView(regulation: regulation) } .font(.caption) } if let errorMessage { Text(errorMessage).font(.caption2).foregroundStyle(.orange) } } .padding(.horizontal, 4) } .navigationTitle(regulation.title) .haptikaScreen() .onDisappear { if model.haptics.isRunning { saveSession(state: .cancelled) model.haptics.cancel() } } } private func start() { let now = Date() Task { do { try await model.haptics.start(protocol: regulation) startedAt = now } catch { errorMessage = error.localizedDescription } } } private func saveSession(state: WatchRegulationSessionState) { guard !sessionSaved, let startedAt else { return } sessionSaved = true model.enqueueSession(.init( protocolID: regulation.id, startedAt: startedAt, endedAt: Date(), durationSeconds: model.haptics.elapsedSeconds, state: state )) } } private func formatDuration(_ duration: TimeInterval) -> String { let seconds = Int(duration) return seconds < 60 ? "\(seconds) s" : "\(seconds / 60) min \(seconds % 60 == 0 ? "" : "\(seconds % 60) s")" }