1→// 2→// SettingsView.swift 3→// Planner2026-iOS 4→// 5→// App settings and configuration 6→// 7→ 8→import SwiftUI 9→ 10→struct SettingsView: View { 11→ @EnvironmentObject var authService: AuthenticationService 12→ @EnvironmentObject var dataManager: DataManager 13→ @EnvironmentObject var locationService: LocationService 14→ @EnvironmentObject var claudeService: ClaudeService 15→ @StateObject private var geofenceService = GeofenceService.shared 16→ @State private var showingSignOutConfirm = false 17→ @State private var showingClearDataConfirm = false 18→ 19→ // Notification settings 20→ @AppStorage("wakeReminderEnabled") private var wakeReminderEnabled = true 21→ @AppStorage("walkingReminderEnabled") private var walkingReminderEnabled = true 22→ @AppStorage("eveningReminderEnabled") private var eveningReminderEnabled = true 23→ @AppStorage("streakWarningEnabled") private var streakWarningEnabled = true 24→ @AppStorage("wakeHour") private var wakeHour = 6 25→ @AppStorage("wakeMinute") private var wakeMinute = 0 26→ 27→ var body: some View { 28→ NavigationStack { 29→ List { 30→ // Account 31→ Section { 32→ if let user = authService.currentUser { 33→ HStack(spacing: 16) { 34→ // Avatar 35→ Text(user.initials) 36→ .font(.title2.bold()) 37→ .foregroundColor(.white) 38→ .frame(width: 50, height: 50) 39→ .background(Color.blue) 40→ .clipShape(Circle()) 41→ 42→ VStack(alignment: .leading) { 43→ Text(user.displayName) 44→ .font(.headline) 45→ 46→ if let email = user.email { 47→ Text(email) 48→ .font(.subheadline) 49→ .foregroundColor(.secondary) 50→ } 51→ } 52→ } 53→ .padding(.vertical, 8) 54→ } 55→ } 56→ 57→ // Location 58→ Section("Location") { 59→ HStack { 60→ Text("Location Access") 61→ Spacer() 62→ Text(locationStatusText) 63→ .foregroundColor(.secondary) 64→ } 65→ 66→ if locationService.isDenied { 67→ Button("Open Settings") { 68→ locationService.openSettings() 69→ } 70→ } else if locationService.needsPermission { 71→ Button("Enable Location") { 72→ locationService.requestPermission() 73→ } 74→ } else if locationService.needsAlwaysAuth { 75→ Button("Enable Background Location") { 76→ locationService.requestAlwaysPermission() 77→ } 78→ .font(.subheadline) 79→ } 80→ 81→ NavigationLink { 82→ LocationsListView() 83→ } label: { 84→ HStack { 85→ Text("Saved Locations") 86→ Spacer() 87→ Text("\(dataManager.locations.count)") 88→ .foregroundColor(.secondary) 89→ } 90→ } 91→ } 92→ 93→ // AI 94→ Section("AI Assistant") { 95→ HStack { 96→ Text("API Status") 97→ Spacer() 98→ if claudeService.hasAPIKey() { 99→ Label("Connected", systemImage: "checkmark.circle.fill") 100→ .foregroundColor(.green) 101→ .font(.subheadline) 102→ } else { 103→ Label("Not Configured", systemImage: "exclamationmark.circle.fill") 104→ .foregroundColor(.orange) 105→ .font(.subheadline) 106→ } 107→ } 108→ 109→ Picker("Model", selection: $claudeService.preferredModel) { 110→ Text("Sonnet (Fast)").tag(ClaudeService.ClaudeModel.sonnet) 111→ Text("Opus (Powerful)").tag(ClaudeService.ClaudeModel.opus) 112→ } 113→ } 114→ 115→ // Haptics 116→ Section("Feedback") { 117→ Toggle("Haptic Feedback", isOn: Binding( 118→ get: { HapticManager.isEnabled }, 119→ set: { HapticManager.isEnabled = $0 } 120→ )) 121→ } 122→ 123→ // Notifications 124→ Section("Daily Reminders") { 125→ Toggle("Wake Reminder (6:00 AM)", isOn: $wakeReminderEnabled) 126→ .onChange(of: wakeReminderEnabled) { _, newValue in 127→ if newValue { 128→ Task { 129→ await NotificationService.shared.requestAuthorization() 130→ NotificationService.shared.scheduleWakeReminder(at: wakeHour, minute: wakeMinute) 131→ } 132→ } else { 133→ NotificationService.shared.cancelWakeReminder() 134→ } 135→ } 136→ 137→ Toggle("Walking Reminder (6:30 AM)", isOn: $walkingReminderEnabled) 138→ .onChange(of: walkingReminderEnabled) { _, newValue in 139→ if newValue { 140→ NotificationService.shared.scheduleWalkingReminder(at: wakeHour, minute: wakeMinute + 30) 141→ } else { 142→ NotificationService.shared.cancelWalkingReminder() 143→ } 144→ } 145→ 146→ Toggle("Evening Routine (9:00 PM)", isOn: $eveningReminderEnabled) 147→ .onChange(of: eveningReminderEnabled) { _, newValue in 148→ if newValue { 149→ NotificationService.shared.scheduleEveningRoutineReminder() 150→ } else { 151→ NotificationService.shared.cancelEveningRoutineReminder() 152→ } 153→ } 154→ 155→ Toggle("Streak Warning (8:00 PM)", isOn: $streakWarningEnabled) 156→ .onChange(of: streakWarningEnabled) { _, newValue in 157→ if newValue { 158→ NotificationService.shared.scheduleStreakWarning() 159→ } else { 160→ NotificationService.shared.cancelStreakWarning() 161→ } 162→ } 163→ } 164→ 165→ // Data 166→ Section("Data") { 167→ HStack { 168→ Text("Tasks") 169→ Spacer() 170→ Text("\(dataManager.tasks.count)") 171→ .foregroundColor(.secondary) 172→ } 173→ 174→ HStack { 175→ Text("Projects") 176→ Spacer() 177→ Text("\(dataManager.projects.count)") 178→ .foregroundColor(.secondary) 179→ } 180→ 181→ HStack { 182→ Text("AI Conversations") 183→ Spacer() 184→ Text("\(dataManager.conversations.count)") 185→ .foregroundColor(.secondary) 186→ } 187→ } 188→ 189→ // Danger Zone 190→ Section { 191→ Button(role: .destructive) { 192→ showingClearDataConfirm = true 193→ } label: { 194→ Label("Clear All Data", systemImage: "trash") 195→ } 196→ 197→ Button(role: .destructive) { 198→ showingSignOutConfirm = true 199→ } label: { 200→ Label("Sign Out", systemImage: "rectangle.portrait.and.arrow.right") 201→ } 202→ } 203→ 204→ // About 205→ Section { 206→ HStack { 207→ Text("Version") 208→ Spacer() 209→ Text(Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "1.0") 210→ .foregroundColor(.secondary) 211→ } 212→ 213→ HStack { 214→ Text("Build") 215→ Spacer() 216→ Text(Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? "1") 217→ .foregroundColor(.secondary) 218→ } 219→ } 220→ } 221→ .navigationTitle("Settings") 222→ .confirmationDialog("Sign Out", isPresented: $showingSignOutConfirm) { 223→ Button("Sign Out", role: .destructive) { 224→ authService.signOut() 225→ } 226→ } message: { 227→ Text("Are you sure you want to sign out?") 228→ } 229→ .confirmationDialog("Clear All Data", isPresented: $showingClearDataConfirm) { 230→ Button("Clear All Data", role: .destructive) { 231→ clearAllData() 232→ } 233→ } message: { 234→ Text("This will permanently delete all your tasks, projects, and settings. This cannot be undone.") 235→ } 236→ } 237→ } 238→ 239→ private var locationStatusText: String { 240→ if locationService.isDenied { 241→ return "Denied" 242→ } else if locationService.needsPermission { 243→ return "Not Set" 244→ } else if locationService.needsAlwaysAuth { 245→ return "When In Use" 246→ } else { 247→ return "Always" 248→ } 249→ } 250→ 251→ private func clearAllData() { 252→ // Clear UserDefaults 253→ if let bundleID = Bundle.main.bundleIdentifier { 254→ UserDefaults.standard.removePersistentDomain(forName: bundleID) 255→ } 256→ 257→ // Remove all active geofences to prevent orphaned monitoring 258→ geofenceService.removeAllGeofences() 259→ 260→ // Reload empty data 261→ dataManager.tasks.removeAll() 262→ dataManager.projects.removeAll() 263→ dataManager.locations.removeAll() 264→ dataManager.conversations.removeAll() 265→ dataManager.insights.removeAll() 266→ dataManager.schedules.removeAll() 267→ dataManager.tags = Tag.systemTags 268→ 269→ HapticManager.notification(.warning) 270→ } 271→} 272→ 273→// MARK: - Locations List View 274→ 275→struct LocationsListView: View { 276→ @EnvironmentObject var dataManager: DataManager 277→ @State private var showingAddLocation = false 278→ @State private var locationToDelete: TaskLocation? 279→ @State private var showDeleteConfirmation = false 280→ 281→ var body: some View { 282→ List { 283→ ForEach(dataManager.locations) { location in 284→ HStack(spacing: 12) { 285→ Image(systemName: location.icon) 286→ .foregroundColor(Color(hex: location.colorHex)) 287→ .frame(width: 30) 288→ 289→ VStack(alignment: .leading) { 290→ Text(location.name) 291→ .font(.headline) 292→ 293→ if !location.address.isEmpty { 294→ Text(location.address) 295→ .font(.caption) 296→ .foregroundColor(.secondary) 297→ } 298→ } 299→ 300→ Spacer() 301→ 302→ if location.isFavorite { 303→ Image(systemName: "star.fill") 304→ .foregroundColor(.yellow) 305→ } 306→ } 307→ } 308→ .onDelete { indexSet in 309→ if let index = indexSet.first { 310→ locationToDelete = dataManager.locations[index] 311→ showDeleteConfirmation = true 312→ } 313→ } 314→ } 315→ .navigationTitle("Saved Locations") 316→ .alert("Delete Location?", isPresented: $showDeleteConfirmation) { 317→ Button("Cancel", role: .cancel) { 318→ locationToDelete = nil 319→ } 320→ Button("Delete", role: .destructive) { 321→ if let location = locationToDelete { 322→ dataManager.deleteLocation(location) 323→ locationToDelete = nil 324→ } 325→ } 326→ } message: { 327→ Text("Are you sure you want to delete \"\(locationToDelete?.name ?? "this location")\"? Tasks using this location will lose their location reference.") 328→ } 329→ .toolbar { 330→ ToolbarItem(placement: .primaryAction) { 331→ Button { 332→ showingAddLocation = true 333→ } label: { 334→ Image(systemName: "plus") 335→ } 336→ } 337→ } 338→ .sheet(isPresented: $showingAddLocation) { 339→ AddLocationView() 340→ } 341→ } 342→} 343→ 344→// MARK: - Add Location View 345→ 346→struct AddLocationView: View { 347→ @EnvironmentObject var dataManager: DataManager 348→ @EnvironmentObject var locationService: LocationService 349→ @Environment(\.dismiss) var dismiss 350→ 351→ @State private var name = "" 352→ @State private var address = "" 353→ @State private var selectedIcon = "mappin" 354→ @State private var selectedColor = "#007AFF" 355→ 356→ let icons = ["mappin", "house.fill", "building.2.fill", "dumbbell.fill", "cart.fill", "cross.fill"] 357→ let colors = ["#007AFF", "#34C759", "#FF9500", "#FF3B30", "#5856D6", "#FF2D55"] 358→ 359→ var body: some View { 360→ NavigationStack { 361→ Form { 362→ Section { 363→ TextField("Location name", text: $name) 364→ 365→ TextField("Address (optional)", text: $address) 366→ } 367→ 368→ Section("Appearance") { 369→ HStack { 370→ ForEach(icons, id: \.self) { icon in 371→ Button { 372→ selectedIcon = icon 373→ } label: { 374→ Image(systemName: icon) 375→ .font(.title2) 376→ .foregroundColor(selectedIcon == icon ? Color(hex: selectedColor) : .gray) 377→ } 378→ .buttonStyle(.plain) 379→ } 380→ } 381→ 382→ HStack { 383→ ForEach(colors, id: \.self) { color in 384→ Button { 385→ selectedColor = color 386→ } label: { 387→ Circle() 388→ .fill(Color(hex: color)) 389→ .frame(width: 30, height: 30) 390→ .overlay { 391→ if selectedColor == color { 392→ Image(systemName: "checkmark") 393→ .foregroundColor(.white) 394→ } 395→ } 396→ } 397→ .buttonStyle(.plain) 398→ } 399→ } 400→ } 401→ 402→ if let location = locationService.currentLocation { 403→ Section("Current Location") { 404→ Text("Lat: \(location.coordinate.latitude, specifier: "%.4f")") 405→ Text("Lon: \(location.coordinate.longitude, specifier: "%.4f")") 406→ } 407→ } 408→ } 409→ .navigationTitle("Add Location") 410→ .navigationBarTitleDisplayMode(.inline) 411→ .toolbar { 412→ ToolbarItem(placement: .cancellationAction) { 413→ Button("Cancel") { dismiss() } 414→ } 415→ 416→ ToolbarItem(placement: .confirmationAction) { 417→ Button("Save") { 418→ saveLocation() 419→ } 420→ .disabled(name.isEmpty || locationService.currentLocation == nil) 421→ } 422→ } 423→ .onAppear { 424→ locationService.requestCurrentLocation() 425→ } 426→ } 427→ } 428→ 429→ private func saveLocation() { 430→ guard let current = locationService.currentLocation else { return } 431→ 432→ let location = TaskLocation( 433→ name: name, 434→ address: address, 435→ latitude: current.coordinate.latitude, 436→ longitude: current.coordinate.longitude, 437→ icon: selectedIcon, 438→ colorHex: selectedColor 439→ ) 440→ 441→ dataManager.addLocation(location) 442→ dismiss() 443→ } 444→} 445→ 446→// MARK: - Preview 447→ 448→#if DEBUG 449→#Preview { 450→ SettingsView() 451→ .environmentObject(AuthenticationService.preview) 452→ .environmentObject(DataManager.shared) 453→ .environmentObject(LocationService()) 454→ .environmentObject(ClaudeService.shared) 455→} 456→#endif 457→ Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.