import SwiftUI struct SettingsView: View { @Environment(AuthSession.self) private var auth @AppStorage("autoPauseEnabled") private var autoPauseEnabled = true @AppStorage("healthKitSyncEnabled") private var healthKitSyncEnabled = false @State private var showHealthPermissionAlert = false @State private var showDeleteConfirm1 = false @State private var showDeleteConfirm2 = false @State private var isDeleting = false @State private var deleteError: String? var body: some View { NavigationStack { Form { Section { HStack(spacing: 14) { avatarView .frame(width: 60, height: 60) .clipShape(Circle()) VStack(alignment: .leading, spacing: 2) { Text(displayName) .font(.headline) if let email = auth.profile?.email { Text(email) .font(.footnote) .foregroundStyle(.secondary) } } } .padding(.vertical, 4) } Section("Hund & Alltag") { NavigationLink { GassiView() } label: { Label("Gassi-Treffen", systemImage: "pawprint.fill") } NavigationLink { GiftkoederView() } label: { Label("Giftköder", systemImage: "exclamationmark.octagon.fill") } NavigationLink { VerloreneHundeView() } label: { Label("Verlorene Hunde", systemImage: "magnifyingglass.circle.fill") } NavigationLink { AusgabenView() } label: { Label("Ausgaben", systemImage: "eurosign.circle.fill") } } Section("Account") { LabeledContent("Rolle", value: rolleLabel) if auth.profile?.isFounderFlag == true { LabeledContent("Founder", value: founderLabel) } if auth.profile?.isPartnerFlag == true { LabeledContent("Partner", value: "Ja") } if let tier = auth.profile?.subscriptionTier, !tier.isEmpty { LabeledContent("Abo", value: tier.capitalized) } LabeledContent("Premium", value: premiumValue ? "Ja" : "Nein") } if let wohnort = auth.profile?.wohnort, !wohnort.isEmpty { Section("Ort") { Text(wohnort) } } Section { Toggle(isOn: $autoPauseEnabled) { Label("Auto-Pause", systemImage: "pause.circle") } Toggle(isOn: $healthKitSyncEnabled) { Label("Apple Health Sync", systemImage: "heart.fill") } .onChange(of: healthKitSyncEnabled) { _, newValue in if newValue { Task { let granted = await WalkHealthSync.shared.requestAuthorization() if !granted { healthKitSyncEnabled = false showHealthPermissionAlert = true } } } } } header: { Text("Aufnahme") } footer: { Text("Auto-Pause: pausiert die Aufnahme, wenn du 2 Minuten lang stehen bleibst.\nApple Health: schreibt jede gespeicherte Tour als Spaziergang-Workout mit Route in Health.") } Section { if let url = URL(string: "https://banyaro.app") { Link(destination: url) { HStack { Label("banyaro.app öffnen", systemImage: "safari.fill") .foregroundStyle(.primary) Spacer() Image(systemName: "arrow.up.right.square") .font(.caption) .foregroundStyle(.tertiary) } } } } header: { Text("banyaro.app") } footer: { VStack(alignment: .leading, spacing: 8) { Text("Du kannst banyaro.app zusätzlich als Web-App auf deinem Home-Bildschirm ablegen — praktisch für alle Features, die diese App nicht abbildet.") HStack(alignment: .firstTextBaseline, spacing: 4) { Text("**1.**") Text("Oben „banyaro.app öffnen“ tippen") } HStack(alignment: .firstTextBaseline, spacing: 4) { Text("**2.**") HStack(spacing: 4) { Text("In Safari unten das Teilen-Icon") Image(systemName: "square.and.arrow.up") Text("antippen") } } HStack(alignment: .firstTextBaseline, spacing: 4) { Text("**3.**") HStack(spacing: 4) { Text("„Zum Home-Bildschirm“") Image(systemName: "plus.square") Text("auswählen") } } Text("Eine native App kann eine Web-App leider nicht selbst installieren — Apple lässt das nur über Safari zu.") .padding(.top, 4) .foregroundStyle(.tertiary) } } Section { Button("Abmelden", role: .destructive) { auth.logout() } } Section { Button(role: .destructive) { showDeleteConfirm1 = true } label: { if isDeleting { HStack { ProgressView(); Text("Wird gelöscht…") } } else { Label("Konto unwiderruflich löschen", systemImage: "trash") } } .disabled(isDeleting) if let deleteError { Text(deleteError).font(.footnote).foregroundStyle(.red) } } header: { Text("Konto löschen") } footer: { Text("Löscht dein banyaro-Konto, alle Hunde, Touren, Tagebuch-Einträge, Ausgaben und Fotos endgültig. Das gilt App-übergreifend (auch für banyaro.app im Browser). Die Aktion kann nicht rückgängig gemacht werden.") } Section("Über") { Text("Ban Yaro Go ist die native iOS-Ergänzung zur banyaro.app — fürs Gassi-Tracking, Wetter, Tagebuch und mehr unterwegs.") .font(.footnote) .foregroundStyle(.secondary) } } .navigationTitle("Mehr") .refreshable { await auth.loadProfile() } .alert("Apple Health hat den Zugriff verweigert", isPresented: $showHealthPermissionAlert) { Button("OK", role: .cancel) {} } message: { Text("Du kannst die Berechtigung in den iOS-Einstellungen unter Datenschutz & Sicherheit → Health → Ban Yaro Go nachträglich ändern.") } .alert("Konto wirklich löschen?", isPresented: $showDeleteConfirm1) { Button("Abbrechen", role: .cancel) {} Button("Weiter", role: .destructive) { showDeleteConfirm2 = true } } message: { Text("Alle Hunde, Touren, Tagebuch, Ausgaben und Fotos werden endgültig gelöscht — App und banyaro.app gleichermaßen. Diese Aktion kann nicht rückgängig gemacht werden.") } .alert("Letzte Bestätigung", isPresented: $showDeleteConfirm2) { Button("Abbrechen", role: .cancel) {} Button("Endgültig löschen", role: .destructive) { Task { await deleteAccount() } } } message: { Text("Bist du dir sicher? Dein Konto und alle Daten werden jetzt sofort und unwiderruflich entfernt.") } } } private func deleteAccount() async { isDeleting = true deleteError = nil defer { isDeleting = false } do { try await APIClient.shared.delete("/api/profile/account") auth.logout() } catch { deleteError = error.localizedDescription } } private var displayName: String { auth.profile?.name ?? auth.userName ?? "—" } private var premiumValue: Bool { auth.profile?.isPremium ?? auth.isPremium } private var rolleLabel: String { switch auth.profile?.rolle?.lowercased() { case "admin": return "Admin" case "moderator": return "Moderator" case nil: return "—" default: return "Mitglied" } } private var founderLabel: String { if let n = auth.profile?.founderNumber { return "#\(n)" } return "Ja" } @ViewBuilder private var avatarView: some View { if let path = auth.profile?.avatarUrl, !path.isEmpty, let url = avatarURL(path) { AsyncImage(url: url) { phase in switch phase { case .success(let img): img.resizable().scaledToFill() default: avatarPlaceholder } } } else { avatarPlaceholder } } private var avatarPlaceholder: some View { ZStack { Color.accentColor.opacity(0.2) Image(systemName: "person.crop.circle.fill") .font(.system(size: 36)) .foregroundStyle(Color.accentColor) } } private func avatarURL(_ path: String) -> URL? { if path.hasPrefix("http") { return URL(string: path) } return URL(string: "https://banyaro.app\(path)") } }