Files
coach-ios/ios/App/CoachWatch/ContentView.swift
Sylvain 678b5a41d0 chore: logs os_log bout-en-bout (watch sendSample + iPhone liveSample) + hook AUTOSIM
Logger unified (capturable via simctl) côté watch (envoi) et iPhone (réception)
pour valider le pont WCSession. Hook AUTOSIM (env var) démarre le mode test au
lancement, pour validation automatisée sur simulateur. Pont prouvé E2E sur sim
pair iPhone+Watch : HR identiques watch->iPhone.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 20:37:03 +02:00

152 lines
5.2 KiB
Swift

import SwiftUI
import HealthKit
struct ContentView: View {
@EnvironmentObject private var workout: WorkoutManager
var body: some View {
Group {
if workout.isRunning {
LiveWorkoutView()
} else {
ActivityPickerView()
}
}
.task {
// Déclenchement auto du mode test (validation simulateur) :
// SIMCTL_CHILD_AUTOSIM=1 xcrun simctl launch
if ProcessInfo.processInfo.environment["AUTOSIM"] == "1" {
workout.startSimulation()
}
}
}
}
struct ActivityPickerView: View {
@EnvironmentObject private var workout: WorkoutManager
private struct Activity: Identifiable {
let id = UUID()
let label: String
let symbol: String
let type: HKWorkoutActivityType
let location: HKWorkoutSessionLocationType
}
private let activities: [Activity] = [
.init(label: "Course", symbol: "figure.run", type: .running, location: .outdoor),
.init(label: "Vélo", symbol: "figure.outdoor.cycle", type: .cycling, location: .outdoor),
.init(label: "Marche", symbol: "figure.walk", type: .walking, location: .outdoor),
.init(label: "Renfo", symbol: "dumbbell", type: .traditionalStrengthTraining, location: .indoor)
]
var body: some View {
ScrollView {
VStack(spacing: 8) {
ForEach(activities) { activity in
Button {
Task {
await workout.startWorkout(activityType: activity.type,
locationType: activity.location)
}
} label: {
Label(activity.label, systemImage: activity.symbol)
.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(.secondary)
}
}
.padding(.horizontal, 4)
}
.navigationTitle("Coach")
}
}
struct LiveWorkoutView: View {
@EnvironmentObject private var workout: WorkoutManager
@EnvironmentObject private var connectivity: ConnectivityManager
var body: some View {
ScrollView {
VStack(alignment: .leading, spacing: 10) {
Metric(label: "FC", value: "\(Int(workout.heartRate))", unit: "bpm", color: .red)
Metric(label: "Calories", value: "\(Int(workout.activeEnergyKcal))", unit: "kcal", color: .orange)
Metric(label: "Distance",
value: String(format: "%.2f", workout.distanceMeters / 1000),
unit: "km", color: .blue)
Metric(label: "Durée", value: Self.timeString(workout.elapsedSec), unit: "", color: .green)
HStack(spacing: 4) {
Image(systemName: connectivity.isReachable
? "iphone.radiowaves.left.and.right" : "iphone.slash")
.foregroundStyle(connectivity.isReachable ? .green : .secondary)
Text(connectivity.isReachable ? "iPhone connecté" : "iPhone hors de portée")
.font(.caption2)
.foregroundStyle(.secondary)
}
Button(role: .destructive) {
workout.end()
} label: {
Label("Terminer", systemImage: "stop.fill")
.frame(maxWidth: .infinity)
}
}
.padding(.horizontal, 4)
}
}
private static func timeString(_ seconds: Double) -> String {
let total = Int(seconds)
let h = total / 3600
let m = (total % 3600) / 60
let s = total % 60
return h > 0
? String(format: "%d:%02d:%02d", h, m, s)
: String(format: "%02d:%02d", m, s)
}
}
private struct Metric: View {
let label: String
let value: String
let unit: String
let color: Color
var body: some View {
VStack(alignment: .leading, spacing: 0) {
Text(label)
.font(.caption2)
.foregroundStyle(.secondary)
HStack(alignment: .firstTextBaseline, spacing: 2) {
Text(value)
.font(.system(size: 30, weight: .semibold, design: .rounded))
.foregroundStyle(color)
if !unit.isEmpty {
Text(unit)
.font(.caption)
.foregroundStyle(.secondary)
}
}
}
}
}
#Preview {
ContentView()
.environmentObject(WorkoutManager.shared)
.environmentObject(ConnectivityManager.shared)
}