import SwiftUI struct WatchRootView: View { @EnvironmentObject private var model: WatchAppModel var body: some View { Group { if WatchDesignMode.enabled || model.hasCompletedOnboarding { WatchHomeView() } else { WatchOnboardingView() } } .preferredColorScheme(.dark) } } struct WatchOnboardingView: View { @EnvironmentObject private var model: WatchAppModel @State private var page = 0 @State private var authorizationError: String? var body: some View { VStack(spacing: 8) { TabView(selection: $page) { onboardingPage(icon: "hand.raised.fill", title: "Vos données", text: "Les mesures restent sur vos appareils et se synchronisent avec votre iPhone appairé.").tag(0) onboardingPage(icon: "heart.text.square.fill", title: "Bien-être, pas diagnostic", text: "Haptika n’est pas un dispositif médical. Consultez un professionnel en cas de symptôme.").tag(1) onboardingPage(icon: "waveform.path.ecg", title: "Mesurer sans vibrer", text: "La Watch mesure le pouls optique. Les vibrations sont toujours arrêtées pendant cette phase.").tag(2) } .tabViewStyle(.verticalPage) if let authorizationError { Text(authorizationError).font(.caption2).foregroundStyle(.orange) } Button(page < 2 ? "Continuer" : "Autoriser Santé") { if page < 2 { withAnimation { page += 1 } } else { Task { do { try await model.measurement.requestAuthorization() model.hasCompletedOnboarding = true } catch { authorizationError = error.localizedDescription } } } } .buttonStyle(WatchPillStyle(primary: true)) Button("Plus tard") { model.hasCompletedOnboarding = true } .font(.system(size: 11)).buttonStyle(.plain).foregroundStyle(WatchTheme.secondary) } .padding(.horizontal, 6) .background(WatchTheme.background.ignoresSafeArea()) } private func onboardingPage(icon: String, title: String, text: String) -> some View { ScrollView { VStack(spacing: 10) { Image(systemName: icon).font(.system(size: 34)).foregroundStyle(WatchTheme.accent) Text(title).font(.headline).multilineTextAlignment(.center) Text(text).font(.system(size: 12)).foregroundStyle(WatchTheme.secondary).multilineTextAlignment(.center).fixedSize(horizontal: false, vertical: true) }.padding(.horizontal, 6) } } } enum WatchFigmaScreen: String, Hashable, CaseIterable { case home, measure, result, breathe, prepare, breathing, anchor, anchoring, meditate, meditation, sleep, wake, face } struct WatchHomeView: View { @State private var page = 0 @State private var path: [WatchFigmaScreen] = WatchDesignMode.initialScreen.flatMap { $0 == .home ? nil : [$0] } ?? [] var body: some View { NavigationStack(path: $path) { TabView(selection: $page) { dashboard.tag(0) WatchRoutineList(category: .breathe).tag(1) WatchRoutineList(category: .anchor).tag(2) WatchRoutineList(category: .meditate).tag(3) WatchWakeView(onStop: { page = 0 }).tag(4) WatchFacePreview().tag(5) } .tabViewStyle(.verticalPage) .navigationDestination(for: WatchFigmaScreen.self) { WatchFigmaDestination(screen: $0) } .haptikaScreen() .toolbar(.hidden, for: .navigationBar) .persistentSystemOverlays(.hidden) } } private var dashboard: some View { WatchFigmaCanvas { VStack(spacing: 9) { WatchEyebrow(title: "mon état actuel") ZStack { Circle().stroke(.white.opacity(0.05), lineWidth: 4) VStack(spacing: 8) { HStack(alignment: .firstTextBaseline, spacing: 0) { Text(WatchDesignMode.enabled ? "72" : "—") .font(WatchTheme.font(40, weight: .bold)).tracking(-2.4) Text("%").font(WatchTheme.font(10, weight: .bold)) } WatchBalanceAxis(compact: true, position: WatchDesignMode.enabled ? 0.72 : nil) } }.frame(width: 99, height: 99).padding(.vertical, 2) NavigationLink(value: WatchFigmaScreen.measure) { Text("Mesurer la VCF".uppercased()) }.buttonStyle(WatchPillStyle(primary: true, height: 29, fontSize: 10)) NavigationLink(value: WatchFigmaScreen.measure) { Text("Actualiser mon état".uppercased()) }.buttonStyle(WatchPillStyle(primary: true, height: 29, fontSize: 10)) }.padding(.horizontal, 24).padding(.top, 14).padding(.bottom, 2) } } } struct WatchFigmaDestination: View { let screen: WatchFigmaScreen var body: some View { switch screen { case .home: WatchHomeView() case .measure: WatchMeasurementView() case .result: WatchFigmaCanvas { WatchMeasurementResultView().padding(.horizontal, 20) } case .breathe: WatchRoutineList(category: .breathe) case .prepare: WatchRoutineView(routine: WatchRoutineCategory.breathe.routines[0]) case .breathing: WatchRoutineView(routine: WatchRoutineCategory.breathe.routines[0], initiallyRunning: true) case .anchor: WatchRoutineList(category: .anchor) case .anchoring: WatchRoutineView(routine: WatchRoutineCategory.anchor.routines[0]) case .meditate: WatchRoutineList(category: .meditate) case .meditation: WatchRoutineView(routine: WatchRoutineCategory.meditate.routines[0]) case .sleep: WatchRoutineView(routine: WatchRoutineCategory.anchor.routines[4]) case .wake: WatchWakeView() case .face: WatchFacePreview() } } } struct WatchWakeView: View { @Environment(\.dismiss) private var dismiss var onStop: (() -> Void)? var body: some View { WatchFigmaCanvas(background: WatchTheme.night) { VStack(spacing: 12) { Text("RÉVEIL").font(.system(size: 12, weight: .semibold)).tracking(1.2).foregroundStyle(WatchTheme.secondary.opacity(0.8)) Spacer(minLength: 0) Group { if WatchDesignMode.enabled { Text("7:12") } else { TimelineView(.periodic(from: .now, by: 60)) { context in Text(context.date, style: .time) } } }.font(.system(size: 44, weight: .bold)).monospacedDigit() .background { WatchGlow() } Spacer(minLength: 0) Button { if let onStop { onStop() } else { dismiss() } } label: { HStack(spacing: 8) { Image("WatchStop").resizable().scaledToFit().frame(width: 20, height: 20) Text("Arrêter") } }.buttonStyle(WatchPillStyle(destructive: true, height: 48)) }.padding(.horizontal, 16).padding(.vertical, 10) }.ignoresSafeArea(.container, edges: .bottom) } } struct WatchFacePreview: View { var body: some View { WatchFigmaCanvas { VStack(spacing: 0) { Group { if WatchDesignMode.enabled { Text("10:09") } else { TimelineView(.periodic(from: .now, by: 60)) { context in Text(context.date, style: .time) } } }.font(.system(size: 32, weight: .bold)) Text("HAPTIKA").font(.system(size: 10, weight: .light)).tracking(1).foregroundStyle(WatchTheme.accent) Spacer(minLength: 36) HStack { NavigationLink(value: WatchFigmaScreen.measure) { VStack(spacing: 4) { Text(WatchDesignMode.enabled ? "68" : "—").font(WatchTheme.font(18, weight: .bold)) .frame(width: 52, height: 52) .background(WatchTheme.background, in: Circle()) .overlay(Circle().stroke(WatchTheme.accent.opacity(0.12), lineWidth: 2).padding(4)) .shadow(color: WatchTheme.accent.opacity(0.2), radius: 4) Text("VFC").font(WatchTheme.font(9)).tracking(0.9).foregroundStyle(WatchTheme.secondary) } } Spacer() NavigationLink { WatchRoutineView(routine: .init(title: "Sos Stress", protocolID: "sosStress", category: .anchor)) } label: { VStack(spacing: 4) { Image("WatchSOS").resizable().scaledToFit().frame(width: 21, height: 21) .frame(width: 52, height: 52) .background(Color(red: 0.957, green: 0.247, blue: 0.369).opacity(0.1), in: Circle()) .overlay(Circle().stroke(Color(red: 0.957, green: 0.247, blue: 0.369), lineWidth: 2)) .shadow(color: Color(red: 0.957, green: 0.247, blue: 0.369).opacity(0.5), radius: 6) Text("SOS").font(WatchTheme.font(9)).tracking(0.9).foregroundStyle(Color(red: 0.957, green: 0.247, blue: 0.369)) } } }.buttonStyle(.plain) }.padding(.horizontal, 23).padding(.vertical, 16).background { WatchGlow() } } } }