feat(watchos): mode Test (FC simulée) — valide le flux sans HealthKit
Bouton "Test (FC simulée)" qui envoie des samples synthétiques (FC ~110-150, calories, distance, durée) via WCSession toutes les 2s, sans démarrer de séance HealthKit. Permet de valider watch -> iPhone -> /live indépendamment de l'autorisation Santé (bloquée par un sous-système Santé coincé sur la Watch). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -45,10 +45,18 @@ struct ActivityPickerView: View {
|
|||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
.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 {
|
if let message = workout.statusMessage {
|
||||||
Text(message)
|
Text(message)
|
||||||
.font(.footnote)
|
.font(.footnote)
|
||||||
.foregroundStyle(.red)
|
.foregroundStyle(.secondary)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(.horizontal, 4)
|
.padding(.horizontal, 4)
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ final class WorkoutManager: NSObject, ObservableObject {
|
|||||||
|
|
||||||
private var lastSentAt = Date.distantPast
|
private var lastSentAt = Date.distantPast
|
||||||
private let sendInterval: TimeInterval = 2
|
private let sendInterval: TimeInterval = 2
|
||||||
|
private var simTask: Task<Void, Never>?
|
||||||
|
|
||||||
private override init() { super.init() }
|
private override init() { super.init() }
|
||||||
|
|
||||||
@@ -86,7 +87,47 @@ final class WorkoutManager: NSObject, ObservableObject {
|
|||||||
|
|
||||||
func pause() { session?.pause() }
|
func pause() { session?.pause() }
|
||||||
func resume() { session?.resume() }
|
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).
|
/// Appelé sur le main depuis le callback de collecte (hors-main).
|
||||||
private func ingest(hr: Double?, kcal: Double?, dist: Double?, elapsed: TimeInterval) {
|
private func ingest(hr: Double?, kcal: Double?, dist: Double?, elapsed: TimeInterval) {
|
||||||
|
|||||||
Reference in New Issue
Block a user