import SwiftUI import SwiftData import EventKit import UserNotifications struct SettingsView: View { @Environment(\.modelContext) private var context @Query private var allSettings: [AppSettings] @StateObject private var viewModel = SettingsViewModel() @State private var showResetAllDataAlert = false @State private var notificationAuthStatus: UNAuthorizationStatus = .notDetermined @State private var remindersEnabled = NotificationService.shared.remindersEnabled private var settings: AppSettings? { allSettings.first } private func refreshNotificationStatus() async { notificationAuthStatus = await NotificationService.shared.authorizationStatus() remindersEnabled = NotificationService.shared.remindersEnabled } private func handleReminderToggle(_ enabled: Bool, settings: AppSettings) { Task { if enabled { let granted = await NotificationService.shared.requestPermission() NotificationService.shared.remindersEnabled = true remindersEnabled = true notificationAuthStatus = await NotificationService.shared.authorizationStatus() AnalyticsService.logNotificationsToggled(enabled: granted) if granted { NotificationService.shared.schedulePlanningReminderIfNeeded( nextWeekPlan: nil, language: settings.languageEnum.resolved() ) } } else { NotificationService.shared.remindersEnabled = false remindersEnabled = false AnalyticsService.logNotificationsToggled(enabled: false) } } } var body: some View { ZStack { Color.mealMoodBackground.ignoresSafeArea() if let settings = settings { settingsContent(settings: settings) } } .navigationTitle("settings_title") .navigationBarTitleDisplayMode(.inline) .toolbarBackground(Color.mealMoodBackground, for: .navigationBar) .toolbarBackground(.visible, for: .navigationBar) .environment(\.isEnabled, true) .toast(isShowing: $viewModel.showToast, message: viewModel.toastMessage) } @ViewBuilder private func settingsContent(settings: AppSettings) -> some View { List { // Planning section Section { Picker("settings_meal_windows", selection: Binding( get: { settings.mealWindowsEnum }, set: { settings.mealWindowsEnum = $0 } )) { Text("meal_windows_dinner_only").tag(MealWindows.dinnerOnly) Text("meal_windows_lunch_only").tag(MealWindows.lunchOnly) Text("meal_windows_both").tag(MealWindows.both) } Toggle("settings_include_weekends", isOn: Binding( get: { settings.includeWeekends }, set: { settings.includeWeekends = $0 } )) .tint(.mealMoodCoral) Picker("settings_export_style", selection: Binding( get: { settings.weekExportStyleEnum }, set: { settings.weekExportStyleEnum = $0 } )) { ForEach(WeekExportStyle.allCases, id: \.self) { style in Text(LocalizedStringKey(style.localizedKey)).tag(style) } } } header: { Label("settings_planning", systemImage: "fork.knife") } .listRowBackground(Color.mealMoodSurface) // Calendar section Section { Toggle("settings_icloud_sync", isOn: Binding( get: { settings.iCloudSyncEnabledResolved }, set: { settings.iCloudSyncEnabledResolved = $0 } )) .tint(.mealMoodCoral) Toggle("settings_sync", isOn: Binding( get: { settings.syncEnabled }, set: { newValue in if newValue { Task { let granted = await viewModel.requestCalendarAccess() if granted { let hasCalendar = viewModel.ensureValidCalendarSelection(settings: settings) settings.syncEnabled = hasCalendar } } } else { settings.syncEnabled = false } } )) .tint(.mealMoodCoral) if settings.syncEnabled { Picker("settings_sync_mode", selection: Binding( get: { settings.syncModeEnum }, set: { settings.syncModeEnum = $0 } )) { ForEach(CalendarSyncMode.allCases, id: \.self) { mode in Text(LocalizedStringKey(mode.localizedKey)).tag(mode) } } if !viewModel.availableCalendars.isEmpty { Picker("calendar_select", selection: Binding( get: { settings.calendarId }, set: { settings.calendarId = $0 } )) { Text("calendar_select").tag(nil as String?) ForEach(viewModel.availableCalendars, id: \.calendarIdentifier) { cal in Text(cal.title).tag(cal.calendarIdentifier as String?) } } } DatePicker("settings_lunch_time", selection: Binding( get: { settings.lunchTime }, set: { settings.lunchTime = $0 } ), displayedComponents: .hourAndMinute) DatePicker("settings_dinner_time", selection: Binding( get: { settings.dinnerTime }, set: { settings.dinnerTime = $0 } ), displayedComponents: .hourAndMinute) Picker("settings_event_duration", selection: Binding( get: { settings.eventDuration }, set: { settings.eventDuration = $0 } )) { Text("duration_30").tag(30) Text("duration_60").tag(60) Text("duration_90").tag(90) Text("duration_120").tag(120) } TextField("settings_event_prefix", text: Binding( get: { settings.eventPrefix }, set: { settings.eventPrefix = $0 } )) Picker("settings_reminder", selection: Binding( get: { settings.reminderMinutesBefore }, set: { settings.reminderMinutesBefore = $0 } )) { Text("settings_reminder_none").tag(nil as Int?) Text("reminder_30").tag(30 as Int?) Text("reminder_60").tag(60 as Int?) Text("reminder_120").tag(120 as Int?) } } } header: { Label("settings_calendar", systemImage: "calendar") } .listRowBackground(Color.mealMoodSurface) // Notifications section Section { Toggle("settings_weekly_reminder", isOn: Binding( get: { remindersEnabled && notificationAuthStatus != .denied }, set: { newValue in handleReminderToggle(newValue, settings: settings) } )) .tint(.mealMoodCoral) .disabled(notificationAuthStatus == .denied) if notificationAuthStatus == .denied { VStack(alignment: .leading, spacing: 8) { Text("settings_notifications_denied_hint") .font(.mealMoodSmall) .foregroundColor(.mealMoodTextSecondary) Button { if let url = URL(string: UIApplication.openSettingsURLString) { UIApplication.shared.open(url) } } label: { Text("settings_open_system_settings") .font(.mealMoodSmall) .foregroundColor(.mealMoodCoral) } } } } header: { Label("settings_notifications_header", systemImage: "bell") } .listRowBackground(Color.mealMoodSurface) .task { await refreshNotificationStatus() } // Tags section Section { NavigationLink(destination: TagListView()) { Text("settings_manage_tags") } } header: { Label("settings_tags", systemImage: "tag") } .listRowBackground(Color.mealMoodSurface) // Language section Section { Picker("settings_language", selection: Binding( get: { settings.languageEnum }, set: { settings.languageEnum = $0 } )) { ForEach(AppLanguage.allCases, id: \.self) { lang in Text(lang.displayName).tag(lang) } } } header: { Label("settings_language", systemImage: "globe") } .listRowBackground(Color.mealMoodSurface) // Premium section Section { HStack { Text("settings_premium_status") Spacer() Text(settings.isPremium ? "Premium" : "Free") .foregroundColor(.mealMoodTextSecondary) } NavigationLink(destination: PremiumView(settings: settings, source: "settings")) { Text("settings_remove_ads") } } header: { Label("settings_premium", systemImage: "star") } .listRowBackground(Color.mealMoodSurface) Section { Button { ReviewPromptService.shared.requestFromSettings() } label: { Label("settings_rate_app", systemImage: "star.bubble") } Link(destination: URL(string: "https://mealmood.app")!) { Label("settings_website", systemImage: "safari") } Link(destination: URL(string: "mailto:support@mealmood.app")!) { Label("settings_support", systemImage: "envelope") } HStack { Text("settings_app_version") Spacer() Text(viewModel.appVersion) .foregroundColor(.mealMoodTextSecondary) } HStack { Text("settings_app_build") Spacer() Text(viewModel.appBuild) .foregroundColor(.mealMoodTextSecondary) } } header: { Label("settings_about", systemImage: "info.circle") } .listRowBackground(Color.mealMoodSurface) Section { Button(role: .destructive) { showResetAllDataAlert = true } label: { Label("settings_reset_all_data", systemImage: "trash") } } header: { Label("settings_danger_zone", systemImage: "exclamationmark.triangle") } .listRowBackground(Color.mealMoodSurface) } .scrollContentBackground(.hidden) .listStyle(.insetGrouped) .foregroundColor(.mealMoodTextPrimary) .tint(.mealMoodCoral) .onAppear { AnalyticsService.logScreenView("Settings") if settings.syncEnabled { let hasCalendar = viewModel.ensureValidCalendarSelection(settings: settings) settings.syncEnabled = hasCalendar } } .alert("calendar_permission_title", isPresented: $viewModel.showCalendarPermissionAlert) { Button("calendar_permission_settings") { viewModel.openSystemSettings() } Button("reset_cancel", role: .cancel) {} } message: { Text("calendar_permission_message") } .alert("settings_reset_all_data_title", isPresented: $showResetAllDataAlert) { Button("reset_cancel", role: .cancel) {} Button("settings_reset_all_data_confirm", role: .destructive) { viewModel.resetAllData(context: context) } } message: { Text("settings_reset_all_data_message") } } }