/ BalanceKit / SettingsView.swift
SettingsView.swift
  1  //
  2  //  SettingsView.swift
  3  //  BalanceKit
  4  //
  5  //  Created by Alexander Kunau on 12.07.25.
  6  //
  7  
  8  import SwiftUI
  9  import UniformTypeIdentifiers
 10  
 11  // MARK: - Backup Data Structure
 12  struct BalanceKitBackup: Codable {
 13      let version: Int
 14      let exportDate: Date
 15      let foodItems: [FoodItem]
 16      let mealPresets: [MealPreset]
 17      let workouts: [Workout]
 18      let profile: UserProfileData
 19      let goals: PersistedGoalsData
 20      let appearance: String
 21      
 22      static let currentVersion = 1
 23  }
 24  
 25  struct SettingsView: View {
 26      @EnvironmentObject var appearanceSettings: AppearanceSettings
 27      @ObservedObject var dataManager: FoodDataManager
 28      @ObservedObject var workoutManager: WorkoutManager
 29      @ObservedObject var userProfile: UserProfile
 30      @State private var showingOnboarding = false
 31      
 32      // Export/Import States
 33      @State private var showingExportSheet = false
 34      @State private var showingImportPicker = false
 35      @State private var exportURL: URL?
 36      @State private var showingAlert = false
 37      @State private var alertTitle = ""
 38      @State private var alertMessage = ""
 39      
 40      var body: some View {
 41          List {
 42              Section(header: Text("Erscheinungsbild")) {
 43                  ForEach(AppearanceMode.allCases) { mode in
 44                      Button(action: {
 45                          appearanceSettings.appearanceMode = mode
 46                      }) {
 47                          HStack {
 48                              Image(systemName: mode.icon)
 49                                  .foregroundColor(.blue)
 50                                  .frame(width: 30)
 51                              
 52                              Text(mode.rawValue)
 53                              
 54                              Spacer()
 55                              
 56                              if appearanceSettings.appearanceMode == mode {
 57                                  Image(systemName: "checkmark")
 58                                      .foregroundColor(.blue)
 59                              }
 60                          }
 61                          .contentShape(Rectangle())
 62                      }
 63                      .buttonStyle(PlainButtonStyle())
 64                  }
 65              }
 66              
 67              Section(header: Text("Persönliche Einstellungen")) {
 68                  Button {
 69                      showingOnboarding = true
 70                  } label: {
 71                      HStack {
 72                          Image(systemName: "person.crop.circle.fill")
 73                              .foregroundColor(.blue)
 74                              .frame(width: 30)
 75                          Text("Persönliche Daten bearbeiten")
 76                      }
 77                  }
 78                  
 79                  NavigationLink(destination: GoalSettingsView(dataManager: dataManager)) {
 80                      HStack {
 81                          Image(systemName: "target")
 82                              .foregroundColor(.blue)
 83                              .frame(width: 30)
 84                          Text("Ernährungsziele anpassen")
 85                      }
 86                  }
 87              }
 88              
 89              Section(header: Text("Datensicherung")) {
 90                  Button {
 91                      exportData()
 92                  } label: {
 93                      HStack {
 94                          Image(systemName: "square.and.arrow.up")
 95                              .foregroundColor(.blue)
 96                              .frame(width: 30)
 97                          Text("Daten exportieren")
 98                      }
 99                  }
100                  
101                  Button {
102                      showingImportPicker = true
103                  } label: {
104                      HStack {
105                          Image(systemName: "square.and.arrow.down")
106                              .foregroundColor(.blue)
107                              .frame(width: 30)
108                          Text("Daten importieren")
109                      }
110                  }
111              }
112              
113              Section(header: Text("Über die App")) {
114                  HStack {
115                      Text("Version")
116                      Spacer()
117                      Text("1.0")
118                          .foregroundColor(.secondary)
119                  }
120                  
121                  Link(destination: URL(string: "https://foodi.neocities.org/")!) {
122                      HStack {
123                          Text("Datenschutz")
124                          Spacer()
125                          Image(systemName: "arrow.up.right.square")
126                              .foregroundColor(.blue)
127                      }
128                  }
129                  
130                  NavigationLink(destination: GoalSettingsView(dataManager: dataManager)) {
131                      Text("Ernährungsziele")
132                  }
133              }
134              
135              Section {
136                  Button(action: resetAppData) {
137                      HStack {
138                          Spacer()
139                          Text("Alle Daten zurücksetzen")
140                              .foregroundColor(.red)
141                          Spacer()
142                      }
143                  }
144              }
145          }
146          .navigationTitle("Einstellungen")
147          .sheet(isPresented: $showingOnboarding) {
148              OnboardingView(userProfile: userProfile, dataManager: dataManager)
149          }
150          .sheet(isPresented: $showingExportSheet) {
151              if let url = exportURL {
152                  ShareSheet(items: [url])
153              }
154          }
155          .fileImporter(
156              isPresented: $showingImportPicker,
157              allowedContentTypes: [UTType.json],
158              allowsMultipleSelection: false
159          ) { result in
160              handleImport(result: result)
161          }
162          .alert(alertTitle, isPresented: $showingAlert) {
163              Button("OK", role: .cancel) { }
164          } message: {
165              Text(alertMessage)
166          }
167      }
168      
169      // MARK: - Export/Import Functions
170      private func exportData() {
171          do {
172              // Sammle alle Daten
173              let backup = BalanceKitBackup(
174                  version: BalanceKitBackup.currentVersion,
175                  exportDate: Date(),
176                  foodItems: dataManager.foodItems,
177                  mealPresets: dataManager.mealPresets,
178                  workouts: workoutManager.workouts,
179                  profile: UserProfileData(
180                      isOnboardingCompleted: userProfile.isOnboardingCompleted,
181                      age: userProfile.age,
182                      gender: userProfile.gender,
183                      height: userProfile.height,
184                      weight: userProfile.weight,
185                      targetWeight: userProfile.targetWeight,
186                      activityLevel: userProfile.activityLevel,
187                      weightGoal: userProfile.weightGoal
188                  ),
189                  goals: PersistedGoalsData(
190                      version: PersistedGoalsData.currentVersion,
191                      calorieGoal: dataManager.dailyCalorieGoal,
192                      proteinGoal: dataManager.dailyProteinGoal,
193                      carbsGoal: dataManager.dailyCarbsGoal,
194                      fatGoal: dataManager.dailyFatGoal
195                  ),
196                  appearance: appearanceSettings.appearanceMode.rawValue
197              )
198              
199              // Encode zu JSON
200              let encoder = JSONEncoder()
201              encoder.dateEncodingStrategy = .iso8601
202              encoder.outputFormatting = [.prettyPrinted, .sortedKeys]
203              let jsonData = try encoder.encode(backup)
204              
205              // Speichere temporär
206              let dateFormatter = DateFormatter()
207              dateFormatter.dateFormat = "yyyy-MM-dd_HHmm"
208              let dateString = dateFormatter.string(from: Date())
209              let filename = "Foodi_Backup_\(dateString).json"
210              
211              let tempURL = FileManager.default.temporaryDirectory.appendingPathComponent(filename)
212              try jsonData.write(to: tempURL)
213              
214              exportURL = tempURL
215              showingExportSheet = true
216              
217              print("✅ Export erfolgreich: \(filename)")
218              
219          } catch {
220              alertTitle = "Export fehlgeschlagen"
221              alertMessage = "Fehler beim Exportieren der Daten: \(error.localizedDescription)"
222              showingAlert = true
223              print("❌ Export Fehler: \(error)")
224          }
225      }
226      
227      private func handleImport(result: Result<[URL], Error>) {
228          switch result {
229          case .success(let urls):
230              guard let url = urls.first else { return }
231              
232              do {
233                  // Lese JSON Datei
234                  let jsonData = try Data(contentsOf: url)
235                  
236                  // Decode Backup
237                  let decoder = JSONDecoder()
238                  decoder.dateDecodingStrategy = .iso8601
239                  let backup = try decoder.decode(BalanceKitBackup.self, from: jsonData)
240                  
241                  // Validiere Version
242                  if backup.version > BalanceKitBackup.currentVersion {
243                      alertTitle = "Inkompatible Version"
244                      alertMessage = "Diese Backup-Datei wurde mit einer neueren App-Version erstellt und kann nicht importiert werden."
245                      showingAlert = true
246                      return
247                  }
248                  
249                  // Bestätige Import (überschreibt alle Daten)
250                  confirmImport(backup: backup)
251                  
252              } catch {
253                  alertTitle = "Import fehlgeschlagen"
254                  alertMessage = "Fehler beim Lesen der Backup-Datei: \(error.localizedDescription)"
255                  showingAlert = true
256                  print("❌ Import Fehler: \(error)")
257              }
258              
259          case .failure(let error):
260              alertTitle = "Dateiauswahl fehlgeschlagen"
261              alertMessage = error.localizedDescription
262              showingAlert = true
263          }
264      }
265      
266      private func confirmImport(backup: BalanceKitBackup) {
267          let alert = UIAlertController(
268              title: "Daten importieren",
269              message: "Möchtest du wirklich alle aktuellen Daten durch das Backup vom \(formatDate(backup.exportDate)) ersetzen?\n\nDies überschreibt:\n• \(backup.foodItems.count) Nahrungsmittel\n• \(backup.mealPresets.count) Presets\n• \(backup.workouts.count) Workouts\n• Dein Profil und Ziele",
270              preferredStyle: .alert
271          )
272          
273          alert.addAction(UIAlertAction(title: "Abbrechen", style: .cancel))
274          alert.addAction(UIAlertAction(title: "Importieren", style: .default) { _ in
275              executeImport(backup: backup)
276          })
277          
278          if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
279             let rootViewController = windowScene.windows.first?.rootViewController {
280              rootViewController.present(alert, animated: true)
281          }
282      }
283      
284      private func executeImport(backup: BalanceKitBackup) {
285          // Importiere alle Daten
286          dataManager.foodItems = backup.foodItems
287          dataManager.mealPresets = backup.mealPresets
288          workoutManager.workouts = backup.workouts
289          
290          // Importiere Goals
291          dataManager.dailyCalorieGoal = backup.goals.calorieGoal
292          dataManager.dailyProteinGoal = backup.goals.proteinGoal
293          dataManager.dailyCarbsGoal = backup.goals.carbsGoal
294          dataManager.dailyFatGoal = backup.goals.fatGoal
295          
296          // Importiere Profil
297          userProfile.isOnboardingCompleted = backup.profile.isOnboardingCompleted
298          userProfile.age = backup.profile.age
299          userProfile.gender = backup.profile.gender
300          userProfile.height = backup.profile.height
301          userProfile.weight = backup.profile.weight
302          userProfile.targetWeight = backup.profile.targetWeight
303          userProfile.activityLevel = backup.profile.activityLevel
304          userProfile.weightGoal = backup.profile.weightGoal
305          userProfile.save()
306          
307          // Importiere Appearance
308          if let mode = AppearanceMode(rawValue: backup.appearance) {
309              appearanceSettings.appearanceMode = mode
310          }
311          
312          alertTitle = "Import erfolgreich"
313          alertMessage = "Alle Daten wurden erfolgreich wiederhergestellt."
314          showingAlert = true
315          
316          print("✅ Import erfolgreich: \(backup.foodItems.count) Items, \(backup.workouts.count) Workouts")
317      }
318      
319      private func formatDate(_ date: Date) -> String {
320          let formatter = DateFormatter()
321          formatter.dateStyle = .medium
322          formatter.timeStyle = .short
323          return formatter.string(from: date)
324      }
325      
326      // MARK: - Reset Function (komplett überarbeitet)
327      // MARK: - Reset Function (komplett überarbeitet)
328      private func resetAppData() {
329          let alert = UIAlertController(
330              title: "Daten zurücksetzen",
331              message: "Bist du sicher, dass du alle deine eingetragenen Daten löschen möchtest? Diese Aktion kann nicht rückgängig gemacht werden.\n\nGelöscht werden:\n• Alle Nahrungsmittel\n• Alle Presets\n• Alle Workouts\n• Ernährungsziele\n• Profil (Onboarding wird erneut angezeigt)",
332              preferredStyle: .alert
333          )
334          
335          alert.addAction(UIAlertAction(title: "Abbrechen", style: .cancel))
336          alert.addAction(UIAlertAction(title: "Alles löschen", style: .destructive) { _ in
337              // Lösche alle FoodItems
338              dataManager.foodItems = []
339              
340              // Lösche alle Presets
341              dataManager.mealPresets = []
342              
343              // Lösche alle Workouts
344              workoutManager.workouts = []
345              
346              // Setze Goals zurück auf Defaults
347              dataManager.dailyCalorieGoal = 2000
348              dataManager.dailyProteinGoal = 100.0
349              dataManager.dailyCarbsGoal = 250.0
350              dataManager.dailyFatGoal = 70.0
351              
352              // Setze Profil zurück
353              let freshProfile = UserProfile()
354              freshProfile.isOnboardingCompleted = false
355              freshProfile.save()
356              
357              // Setze Appearance zurück auf System
358              appearanceSettings.appearanceMode = .system
359              
360              print("✅ Alle Daten wurden zurückgesetzt")
361              
362              // Zeige Bestätigung
363              alertTitle = "Daten gelöscht"
364              alertMessage = "Alle Daten wurden erfolgreich zurückgesetzt. Die App wird beim nächsten Start das Onboarding anzeigen."
365              showingAlert = true
366          })
367          
368          if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
369             let rootViewController = windowScene.windows.first?.rootViewController {
370              rootViewController.present(alert, animated: true)
371          }
372      }
373  }
374  
375  // MARK: - ShareSheet für Export
376  struct ShareSheet: UIViewControllerRepresentable {
377      let items: [Any]
378      
379      func makeUIViewController(context: Context) -> UIActivityViewController {
380          let controller = UIActivityViewController(activityItems: items, applicationActivities: nil)
381          return controller
382      }
383      
384      func updateUIViewController(_ uiViewController: UIActivityViewController, context: Context) {}
385  }
386  
387  #Preview {
388      NavigationStack {
389          SettingsView(dataManager: FoodDataManager(), workoutManager: WorkoutManager(), userProfile: UserProfile())
390              .environmentObject(AppearanceSettings())
391      }
392  }