103 lines
3.6 KiB
Swift
103 lines
3.6 KiB
Swift
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")"
|
|
}
|