import SwiftUI struct WatchMeasurementView: View { @EnvironmentObject private var model: WatchAppModel @Environment(\.dismiss) private var dismiss @State private var errorMessage: String? @State private var previewStartedAt: Date? @State private var previewCompleted = false var body: some View { WatchFigmaCanvas { Group { if previewCompleted { WatchMeasurementResultView() } else if case let .completed(summary) = model.measurement.state, !WatchDesignMode.enabled { WatchMeasurementResultView(summary: summary) } else { TimelineView(.periodic(from: .now, by: 1)) { context in let seconds = remainingSeconds(at: context.date) VStack(spacing: 0) { WatchEyebrow(title: "Analyse") Spacer(minLength: 10) WatchSignalRing(progress: 1 - Double(seconds) / 60) Text(watchTime(TimeInterval(seconds))) .font(WatchTheme.font(15, weight: .bold)).monospacedDigit() .padding(.top, 12) Text("Restez immobile") .font(WatchTheme.font(14)).foregroundStyle(WatchTheme.secondary.opacity(0.6)) .padding(.top, 6) Spacer(minLength: 12) Button("Fermer") { close() }.buttonStyle(WatchPillStyle()) } .onChange(of: seconds) { _, value in if WatchDesignMode.enabled && !WatchDesignMode.frozen && value == 0 { previewCompleted = true } } } } }.padding(.horizontal, 20).padding(.vertical, 12) } .task { await startMeasurement() } .alert(errorMessage ?? "", isPresented: Binding(get: { errorMessage != nil }, set: { if !$0 { errorMessage = nil } })) { Button("Fermer") { close() } } .onChange(of: model.measurement.state) { _, state in if !WatchDesignMode.enabled, case let .failed(message) = state { errorMessage = message } } .onDisappear { guard !WatchDesignMode.enabled else { return } switch model.measurement.state { case .waitingForFirstSample, .collecting: model.measurement.cancel() default: break } } } private func remainingSeconds(at date: Date) -> Int { if WatchDesignMode.enabled { guard !WatchDesignMode.frozen, let previewStartedAt else { return 60 } return max(0, 60 - Int(date.timeIntervalSince(previewStartedAt))) } switch model.measurement.state { case let .collecting(progress): return max(0, Int((60 * (1 - progress)).rounded(.up))) case .finishing: return 0 default: return 60 } } private func startMeasurement() async { if WatchDesignMode.enabled { previewStartedAt = Date(); return } do { model.measurement.resetAfterResult() try await model.measurement.requestAuthorization() guard !Task.isCancelled else { return } try await model.measurement.start() } catch { errorMessage = error.localizedDescription } } private func close() { if !WatchDesignMode.enabled { model.measurement.cancel(); model.measurement.resetAfterResult() } dismiss() } } struct WatchSignalRing: View { let progress: Double var body: some View { ZStack { Circle().stroke(Color(red: 25 / 255, green: 32 / 255, blue: 44 / 255).opacity(0.4), lineWidth: 6) Circle().stroke(WatchTheme.accent.opacity(0.8), lineWidth: 6) Circle().stroke(WatchTheme.accent.opacity(0.6), lineWidth: 2) Circle().fill(WatchTheme.accent.opacity(0.2)).frame(width: 40, height: 40) .overlay(Circle().stroke(WatchTheme.accent.opacity(0.3))) .shadow(color: WatchTheme.accent.opacity(0.3), radius: 8) Image("WatchHeart").resizable().scaledToFit().frame(width: 16.667, height: 15.292) }.frame(width: 72, height: 72).padding(3) .accessibilityLabel("Analyse").accessibilityValue("\(Int(progress * 100)) pour cent") } } struct WatchMeasurementResultView: View { @EnvironmentObject private var model: WatchAppModel @Environment(\.dismiss) private var dismiss var summary: WatchMeasurementSummaryV1? = nil var body: some View { VStack(spacing: 0) { WatchEyebrow(title: "RÉSULTAT") Spacer(minLength: 12) HStack(alignment: .firstTextBaseline, spacing: 0) { Text(WatchDesignMode.enabled ? "72" : "—") .font(WatchTheme.font(64, weight: .bold)).tracking(-3.2) .foregroundStyle(LinearGradient(colors: [WatchTheme.mint, WatchTheme.accent], startPoint: .topLeading, endPoint: .bottomTrailing)) Text("%").font(WatchTheme.font(14, weight: .medium)).foregroundStyle(WatchTheme.mint.opacity(0.8)) }.foregroundStyle(WatchTheme.accent).shadow(color: WatchTheme.accent.opacity(0.5), radius: 5) WatchBalanceAxis(position: WatchDesignMode.enabled ? 0.77 : nil).padding(.top, 14) Text(WatchDesignMode.enabled ? "Équilibre favorable" : "") .font(WatchTheme.font(10)).foregroundStyle(WatchTheme.secondary).padding(.top, 14) Spacer(minLength: 12) Button("Fermer") { if !WatchDesignMode.enabled { model.measurement.resetAfterResult() } dismiss() }.buttonStyle(WatchPillStyle()) } } }