main
swift 158 lines 5.68 KB
Raw
1 import SwiftUI
2
3 struct OnboardingView: View {
4 @EnvironmentObject private var guideEngine: OndeGuideEngine
5
6 let onBrowseFirst: () -> Void
7 let onComplete: () -> Void
8
9 private var privateGuideButtonTitle: String {
10 switch guideEngine.availability {
11 case .ready:
12 return "Private guide ready"
13 case .downloaded:
14 return "Finish preparing private guide"
15 case .failed:
16 return "Try private guide again"
17 case .unsupported:
18 return "Not available on this device"
19 default:
20 return "Add private guide"
21 }
22 }
23
24 var body: some View {
25 ScrollView {
26 VStack(alignment: .leading, spacing: 24) {
27 Spacer(minLength: 24)
28
29 Text("Klepon")
30 .font(KleponTypography.heroTitle)
31 .foregroundStyle(KleponColor.textPrimary)
32
33 Text("Taste of Indonesia")
34 .font(KleponTypography.screenTitle)
35 .foregroundStyle(KleponColor.accentWarm)
36
37 Text("A warm pocket guide to Indonesian dishes, ingredients, and food traditions.")
38 .font(KleponTypography.body)
39 .foregroundStyle(KleponColor.textSecondary)
40
41 KleponCard {
42 onboardingPoint(
43 title: "Curated before clever",
44 detail:
45 "Browse thoughtful dishes and food notes first. The AI layer stays in the background.",
46 systemImage: "fork.knife"
47 )
48 }
49
50 KleponCard {
51 onboardingPoint(
52 title: "Private on your device",
53 detail:
54 "Private answers use a one-time on-device guide download. You can browse first and add it later.",
55 systemImage: "lock"
56 )
57 }
58
59 KleponCard {
60 onboardingPoint(
61 title: "Made to feel calm",
62 detail:
63 "No accounts, no noisy feeds, and no generic chatbot shell. Just a clean guide you can keep close.",
64 systemImage: "sparkles"
65 )
66 }
67
68 KleponCard {
69 VStack(alignment: .leading, spacing: 10) {
70 Text("Private guide status")
71 .font(KleponTypography.cardTitle)
72 .foregroundStyle(KleponColor.textPrimary)
73
74 Text(guideEngine.availability.title)
75 .font(KleponTypography.bodySecondary)
76 .foregroundStyle(
77 guideEngine.availability.isFailure
78 ? KleponColor.highlight : KleponColor.textSecondary)
79
80 Text(guideEngine.availability.detail)
81 .font(KleponTypography.caption)
82 .foregroundStyle(KleponColor.textSecondary)
83
84 HStack(spacing: 10) {
85 KleponChip(
86 title: guideEngine.estimatedDownloadDescription,
87 icon: "arrow.down.circle")
88 KleponChip(
89 title: guideEngine.storageUsedDescription, icon: "internaldrive")
90 }
91 }
92 }
93 }
94 .padding(.horizontal, 24)
95 .padding(.top, 24)
96 .padding(.bottom, 180)
97 }
98 .background(KleponColor.background.ignoresSafeArea())
99 #if os(macOS)
100 .safeAreaInset(edge: .bottom) {
101 onboardingActions
102 .padding(.horizontal, 24)
103 .padding(.vertical, 16)
104 .background(KleponColor.background)
105 }
106 #else
107 .safeAreaInset(edge: .bottom) {
108 onboardingActions
109 .padding(.horizontal, 24)
110 .padding(.top, 12)
111 .padding(.bottom, 16)
112 .background(.ultraThinMaterial)
113 }
114 #endif
115 }
116
117 private var onboardingActions: some View {
118 VStack(spacing: 12) {
119 KleponActionButton(
120 title: privateGuideButtonTitle,
121 systemImage: "lock",
122 isLoading: guideEngine.availability.isBusy,
123 isDisabled: guideEngine.availability == .ready
124 ) {
125 Task {
126 await guideEngine.prepareIfNeeded(
127 forceReload: guideEngine.availability.isFailure)
128 if guideEngine.availability == .ready {
129 onComplete()
130 }
131 }
132 }
133
134 KleponActionButton(title: "Browse first", tone: .secondary) {
135 onBrowseFirst()
136 }
137 }
138 }
139
140 private func onboardingPoint(title: String, detail: String, systemImage: String) -> some View {
141 HStack(alignment: .top, spacing: 14) {
142 Image(systemName: systemImage)
143 .font(.title3)
144 .foregroundStyle(KleponColor.accent)
145 .frame(width: 28)
146
147 VStack(alignment: .leading, spacing: 6) {
148 Text(title)
149 .font(KleponTypography.cardTitle)
150 .foregroundStyle(KleponColor.textPrimary)
151
152 Text(detail)
153 .font(KleponTypography.bodySecondary)
154 .foregroundStyle(KleponColor.textSecondary)
155 }
156 }
157 }
158 }