/ BalanceKit / OnboardingView.swift
OnboardingView.swift
1 // 2 // OnboardingView.swift 3 // BalanceKit 4 // 5 // Created by Alexander Kunau on 30.10.24. 6 // 7 8 import SwiftUI 9 import HealthKit 10 11 struct OnboardingView: View { 12 @ObservedObject var userProfile: UserProfile 13 @ObservedObject var dataManager: FoodDataManager 14 @State private var currentStep = 0 15 @Environment(\.dismiss) private var dismiss 16 @State private var shouldDismiss = false 17 18 var body: some View { 19 VStack { 20 // Progress bar 21 HStack(spacing: 4) { 22 ForEach(0..<4) { step in 23 Rectangle() 24 .frame(height: 4) 25 .foregroundColor(step <= currentStep ? .blue : .gray.opacity(0.3)) 26 } 27 } 28 .padding(.horizontal) 29 .padding(.top) 30 31 ScrollView { 32 VStack(alignment: .leading, spacing: 20) { 33 // Step 1: Willkommen 34 if currentStep == 0 { 35 welcomeStep 36 } 37 // Step 2: Persönliche Daten 38 else if currentStep == 1 { 39 personalDataStep 40 } 41 // Step 3: Aktivität & Ziel 42 else if currentStep == 2 { 43 activityAndGoalStep 44 } 45 // Step 4: Zusammenfassung 46 else if currentStep == 3 { 47 summaryStep 48 } 49 } 50 .padding() 51 .frame(maxWidth: .infinity) 52 } 53 54 // Navigation buttons 55 HStack { 56 if currentStep > 0 { 57 Button(action: { 58 withAnimation { 59 currentStep -= 1 60 } 61 }) { 62 HStack { 63 Image(systemName: "chevron.left") 64 Text("Zurück") 65 } 66 .foregroundColor(.blue) 67 .padding() 68 } 69 } 70 71 Spacer() 72 73 Button(action: { 74 withAnimation { 75 if currentStep < 3 { 76 currentStep += 1 77 } else { 78 completeOnboarding() 79 } 80 } 81 }) { 82 HStack { 83 Text(currentStep == 3 ? "Fertig stellen" : "Weiter") 84 Image(systemName: "chevron.right") 85 } 86 .foregroundColor(.white) 87 .padding() 88 .background(Color.blue) 89 .cornerRadius(10) 90 } 91 } 92 .padding() 93 } 94 .navigationBarBackButtonHidden(true) 95 .onChange(of: shouldDismiss) { oldValue, newValue in 96 if newValue { 97 // Setze eine kurze Verzögerung, um sicherzustellen, dass alle Daten gespeichert sind 98 DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { 99 dismiss() 100 } 101 } 102 } 103 } 104 105 // Step 1: Willkommensbildschirm 106 var welcomeStep: some View { 107 VStack(alignment: .leading, spacing: 20) { 108 Text("Willkommen bei Foodi") 109 .font(.largeTitle) 110 .fontWeight(.bold) 111 112 Text("Wir möchten dir helfen, deine Ernährungsziele zu erreichen. Dafür brauchen wir ein paar Informationen über dich.") 113 .foregroundColor(.secondary) 114 115 Image(systemName: "fork.knife.circle.fill") 116 .resizable() 117 .aspectRatio(contentMode: .fit) 118 .frame(width: 100, height: 100) 119 .foregroundColor(.blue) 120 .frame(maxWidth: .infinity) 121 .padding(.vertical, 20) 122 123 Text("In den nächsten Schritten kannst du deine persönlichen Daten angeben, damit wir deinen täglichen Kalorienbedarf und empfohlene Makronährstoffe berechnen können.") 124 .foregroundColor(.secondary) 125 } 126 } 127 128 // Step 2: Persönliche Daten 129 var personalDataStep: some View { 130 VStack(alignment: .leading, spacing: 20) { 131 Text("Persönliche Daten") 132 .font(.title) 133 .fontWeight(.bold) 134 135 VStack(alignment: .leading) { 136 Text("Alter").fontWeight(.medium) 137 Stepper("\(userProfile.age) Jahre", value: $userProfile.age, in: 16...100, step: 1) 138 } 139 .padding(.vertical, 5) 140 141 VStack(alignment: .leading) { 142 Text("Geschlecht").fontWeight(.medium) 143 Picker("Geschlecht", selection: $userProfile.gender) { 144 ForEach(Gender.allCases) { gender in 145 Text(gender.rawValue).tag(gender) 146 } 147 } 148 .pickerStyle(.segmented) 149 } 150 .padding(.vertical, 5) 151 152 VStack(alignment: .leading) { 153 Text("Größe (cm)").fontWeight(.medium) 154 HStack { 155 Slider(value: $userProfile.height, in: 140...220, step: 1) 156 Text("\(Int(userProfile.height)) cm") 157 .frame(width: 70, alignment: .trailing) 158 } 159 } 160 .padding(.vertical, 5) 161 162 VStack(alignment: .leading) { 163 Text("Aktuelles Gewicht (kg)").fontWeight(.medium) 164 HStack { 165 Slider(value: $userProfile.weight, in: 40...180, step: 0.5) 166 Text(String(format: "%.1f kg", userProfile.weight)) 167 .frame(width: 70, alignment: .trailing) 168 } 169 } 170 .padding(.vertical, 5) 171 172 VStack(alignment: .leading) { 173 Text("Zielgewicht (kg)").fontWeight(.medium) 174 HStack { 175 Slider(value: $userProfile.targetWeight, in: 40...180, step: 0.5) 176 Text(String(format: "%.1f kg", userProfile.targetWeight)) 177 .frame(width: 70, alignment: .trailing) 178 } 179 } 180 .padding(.vertical, 5) 181 } 182 } 183 184 // Step 3: Aktivität & Ziel 185 var activityAndGoalStep: some View { 186 VStack(alignment: .leading, spacing: 20) { 187 Text("Aktivität & Ziel") 188 .font(.title) 189 .fontWeight(.bold) 190 191 VStack(alignment: .leading) { 192 Text("Wie aktiv bist du?").fontWeight(.medium) 193 Picker("Aktivitätsniveau", selection: $userProfile.activityLevel) { 194 ForEach(ActivityLevel.allCases) { level in 195 Text(level.rawValue).tag(level) 196 } 197 } 198 .pickerStyle(.navigationLink) 199 } 200 .padding(.vertical, 5) 201 202 VStack(alignment: .leading) { 203 Text("Was ist dein Ziel?").fontWeight(.medium) 204 Picker("Ziel", selection: $userProfile.weightGoal) { 205 ForEach(WeightGoal.allCases) { goal in 206 Text(goal.rawValue).tag(goal) 207 } 208 } 209 .pickerStyle(.segmented) 210 } 211 .padding(.vertical, 5) 212 } 213 } 214 215 // Step 4: Zusammenfassung 216 var summaryStep: some View { 217 VStack(alignment: .leading, spacing: 20) { 218 Text("Deine persönlichen Empfehlungen") 219 .font(.title) 220 .fontWeight(.bold) 221 222 VStack(alignment: .leading, spacing: 5) { 223 Text("Täglicher Kalorienbedarf") 224 .fontWeight(.medium) 225 Text("\(userProfile.dailyCalorieNeeds) kcal") 226 .font(.title) 227 .foregroundColor(.green) 228 } 229 .padding(.vertical, 5) 230 231 let macros = userProfile.recommendedMacros 232 VStack(alignment: .leading, spacing: 15) { 233 Text("Empfohlene Makronährstoffe") 234 .fontWeight(.medium) 235 236 HStack { 237 MacroCard( 238 title: "Protein", 239 value: Int(macros.protein), 240 unit: "g", 241 color: .blue 242 ) 243 244 MacroCard( 245 title: "Kohlenhydrate", 246 value: Int(macros.carbs), 247 unit: "g", 248 color: .orange 249 ) 250 251 MacroCard( 252 title: "Fett", 253 value: Int(macros.fat), 254 unit: "g", 255 color: .purple 256 ) 257 } 258 } 259 .padding(.vertical, 5) 260 261 Text("Diese Werte basieren auf deinen persönlichen Daten und werden verwendet, um deine Zielwerte in der App zu setzen.") 262 .foregroundColor(.secondary) 263 .font(.footnote) 264 .padding(.vertical, 5) 265 266 Divider() 267 .padding(.vertical, 10) 268 269 VStack(alignment: .leading, spacing: 10) { 270 Text("Alternative: Custom Werte") 271 .fontWeight(.semibold) 272 Text("Du kannst diese Empfehlungen übernehmen oder später in den Einstellungen unter 'Zielwerte' eigene Werte festlegen.") 273 .font(.footnote) 274 .foregroundColor(.secondary) 275 } 276 } 277 } 278 279 private func completeOnboarding() { 280 // Setze die berechneten Zielwerte in den DataManager 281 dataManager.dailyCalorieGoal = userProfile.dailyCalorieNeeds 282 283 let macros = userProfile.recommendedMacros 284 dataManager.dailyProteinGoal = macros.protein 285 dataManager.dailyCarbsGoal = macros.carbs 286 dataManager.dailyFatGoal = macros.fat 287 288 // Stelle sicher, dass der HealthManager initialisiert ist (falls auf echtem Gerät) 289 #if !targetEnvironment(simulator) 290 if HKHealthStore.isHealthDataAvailable() { 291 print("DEBUG: Onboarding - HealthKit ist verfügbar, initialisiere HealthManager") 292 if dataManager.healthManager == nil { 293 dataManager.healthManager = HealthManager() 294 } else { 295 print("DEBUG: Onboarding - HealthManager ist bereits initialisiert") 296 // Aktualisiere Daten 297 dataManager.healthManager?.requestAuthorization() 298 } 299 } else { 300 print("DEBUG: Onboarding - HealthKit ist nicht verfügbar") 301 } 302 #endif 303 304 // Markiere Onboarding als abgeschlossen 305 userProfile.isOnboardingCompleted = true 306 userProfile.save() 307 308 // Schließe die Onboarding-Ansicht 309 shouldDismiss = true 310 } 311 } 312 313 // Eine Hilfsansicht für die Makronährstoffkarten 314 struct MacroCard: View { 315 var title: String 316 var value: Int 317 var unit: String 318 var color: Color 319 320 var body: some View { 321 VStack { 322 Text(title) 323 .font(.caption) 324 .foregroundColor(.secondary) 325 .lineLimit(1) 326 .minimumScaleFactor(0.7) 327 328 Text("\(value)") 329 .font(.headline) 330 .foregroundColor(color) 331 332 Text(unit) 333 .font(.caption) 334 .foregroundColor(.secondary) 335 } 336 .frame(maxWidth: .infinity) 337 .padding() 338 .background(Color.gray.opacity(0.1)) 339 .cornerRadius(10) 340 } 341 } 342 343 #Preview { 344 OnboardingView(userProfile: UserProfile(), dataManager: FoodDataManager()) 345 }