diff --git a/ios/App/CoachWatch/ContentView.swift b/ios/App/CoachWatch/ContentView.swift index d0a1d5a..b3088d3 100644 --- a/ios/App/CoachWatch/ContentView.swift +++ b/ios/App/CoachWatch/ContentView.swift @@ -45,10 +45,18 @@ struct ActivityPickerView: View { .frame(maxWidth: .infinity, alignment: .leading) } } + Button { + workout.startSimulation() + } label: { + Label("Test (FC simulée)", systemImage: "waveform.path.ecg") + .frame(maxWidth: .infinity, alignment: .leading) + } + .tint(.gray) + if let message = workout.statusMessage { Text(message) .font(.footnote) - .foregroundStyle(.red) + .foregroundStyle(.secondary) } } .padding(.horizontal, 4) diff --git a/ios/App/CoachWatch/WorkoutManager.swift b/ios/App/CoachWatch/WorkoutManager.swift index ce95691..d8dd147 100644 --- a/ios/App/CoachWatch/WorkoutManager.swift +++ b/ios/App/CoachWatch/WorkoutManager.swift @@ -24,6 +24,7 @@ final class WorkoutManager: NSObject, ObservableObject { private var lastSentAt = Date.distantPast private let sendInterval: TimeInterval = 2 + private var simTask: Task? private override init() { super.init() } @@ -86,7 +87,47 @@ final class WorkoutManager: NSObject, ObservableObject { func pause() { session?.pause() } func resume() { session?.resume() } - func end() { session?.end() } + + func end() { + if let simTask { + simTask.cancel() + self.simTask = nil + isRunning = false + statusMessage = nil + } else { + session?.end() + } + } + + /// Mode test : FC simulée, sans HealthKit, pour valider le flux watch -> iPhone -> /live. + func startSimulation() { + guard !isRunning else { return } + isRunning = true + statusMessage = "Mode test — FC simulée" + let startedAt = Date() + simTask = Task { [weak self] in + while !Task.isCancelled { + guard let self else { return } + let elapsed = Date().timeIntervalSince(startedAt) + let hr = 110 + 40 * abs(sin(elapsed / 30)) + let kcal = elapsed / 6 + let dist = elapsed * 2.6 + self.heartRate = hr + self.activeEnergyKcal = kcal + self.distanceMeters = dist + self.elapsedSec = elapsed + ConnectivityManager.shared.sendSample([ + "heartRate": hr, + "activeEnergyKcal": kcal, + "distanceMeters": dist, + "elapsedSec": elapsed, + "ts": Date().timeIntervalSince1970, + "sim": true + ]) + try? await Task.sleep(nanoseconds: 2_000_000_000) + } + } + } /// Appelé sur le main depuis le callback de collecte (hors-main). private func ingest(hr: Double?, kcal: Double?, dist: Double?, elapsed: TimeInterval) {