feat: watch design from figma
This commit is contained in:
@@ -5,7 +5,7 @@ struct WatchRootView: View {
|
||||
|
||||
var body: some View {
|
||||
Group {
|
||||
if model.hasCompletedOnboarding {
|
||||
if WatchDesignMode.enabled || model.hasCompletedOnboarding {
|
||||
WatchHomeView()
|
||||
} else {
|
||||
WatchOnboardingView()
|
||||
@@ -47,68 +47,160 @@ struct WatchOnboardingView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
.buttonStyle(.borderedProminent)
|
||||
.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 {
|
||||
VStack(spacing: 10) {
|
||||
ScrollView { VStack(spacing: 10) {
|
||||
Image(systemName: icon).font(.system(size: 34)).foregroundStyle(WatchTheme.accent)
|
||||
Text(title).font(.headline).multilineTextAlignment(.center)
|
||||
Text(text).font(.caption).foregroundStyle(WatchTheme.secondary).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 {
|
||||
@EnvironmentObject private var model: WatchAppModel
|
||||
@State private var page = 0
|
||||
@State private var path: [WatchFigmaScreen] = WatchDesignMode.initialScreen.flatMap { $0 == .home ? nil : [$0] } ?? []
|
||||
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
List {
|
||||
Section {
|
||||
NavigationLink(value: WatchHomeDestination.measure) { homeRow("Mesurer", "waveform.path.ecg", WatchTheme.mint) }
|
||||
NavigationLink(value: WatchHomeDestination.coherence) { homeRow("Ma Cohérence", "circle.dotted", WatchTheme.accent) }
|
||||
NavigationLink(value: WatchHomeDestination.exercises) { homeRow("Exercices", "wind", .cyan) }
|
||||
NavigationLink(value: WatchHomeDestination.cardiacMirror) { homeRow("Miroir cardiaque", "heart.circle", .pink) }
|
||||
NavigationLink(value: WatchHomeDestination.history) { homeRow("Historique", "chart.xyaxis.line", .indigo) }
|
||||
} header: {
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
Text("HAPTIKA").font(.caption2).foregroundStyle(WatchTheme.accent)
|
||||
Text("Prendre un temps").font(.headline).foregroundStyle(.white)
|
||||
}
|
||||
.textCase(nil)
|
||||
}
|
||||
|
||||
Section {
|
||||
HStack {
|
||||
Circle().fill(model.connectivity.isReachable ? WatchTheme.mint : .gray).frame(width: 7, height: 7)
|
||||
Text(model.connectivity.isReachable ? "iPhone joignable" : "Mode autonome")
|
||||
}
|
||||
.font(.caption2)
|
||||
.foregroundStyle(WatchTheme.secondary)
|
||||
}
|
||||
}
|
||||
.navigationDestination(for: WatchHomeDestination.self) { destination in
|
||||
switch destination {
|
||||
case .measure: WatchMeasurementView()
|
||||
case .coherence: WatchCoherenceView()
|
||||
case .exercises: WatchExerciseListView()
|
||||
case .cardiacMirror: WatchCardiacMirrorView()
|
||||
case .history: WatchHistoryView()
|
||||
}
|
||||
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 func homeRow(_ title: String, _ icon: String, _ color: Color) -> some View {
|
||||
Label {
|
||||
Text(title).font(.subheadline.weight(.semibold))
|
||||
} icon: {
|
||||
Image(systemName: icon).foregroundStyle(color)
|
||||
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() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user