import SwiftUI struct SettingsView: View { @Environment(AppState.self) private var app #if os(iOS) @Environment(\.dismiss) private var dismiss #endif @AppStorage("skipDurationSeconds") private var skipSeconds: Int = 30 @AppStorage("libraryLayout") private var layoutRaw: String = LibraryLayout.grid.rawValue @AppStorage("autoRefreshOnLaunch") private var autoRefreshOnLaunch: Bool = true @State private var showLogoutConfirm: Bool = false private static let skipOptions: [Int] = [10, 15, 30, 45, 60, 90] var body: some View { #if os(iOS) NavigationStack { Form { connectionSection playbackSection appearanceSection downloadsSection aboutSection } .navigationTitle("Einstellungen") .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .topBarTrailing) { Button("Fertig") { dismiss() } } } .confirmationDialog( "Mit Server abmelden?", isPresented: $showLogoutConfirm, titleVisibility: .visible ) { Button("Abmelden", role: .destructive) { app.stopPlayback() app.auth.logout() dismiss() } Button("Abbrechen", role: .cancel) { } } message: { Text("Du wirst zurück zur Login-Maske geschickt. Heruntergeladene Inhalte bleiben.") } } #else TabView { connectionPane .tabItem { Label("Verbindung", systemImage: "server.rack") } playbackPane .tabItem { Label("Wiedergabe", systemImage: "play.circle") } appearancePane .tabItem { Label("Darstellung", systemImage: "square.grid.2x2") } aboutPane .tabItem { Label("Über", systemImage: "info.circle") } } .padding(20) .frame(width: 480, height: 320) .confirmationDialog( "Mit Server abmelden?", isPresented: $showLogoutConfirm, titleVisibility: .visible ) { Button("Abmelden", role: .destructive) { app.stopPlayback() app.auth.logout() } Button("Abbrechen", role: .cancel) { } } message: { Text("Du wirst zur Login-Maske zurückgesetzt. Heruntergeladene Hörbücher bleiben erhalten.") } #endif } // MARK: - iOS Form sections #if os(iOS) private var connectionSection: some View { Section { LabeledContent("Server") { Text(app.auth.serverURL.isEmpty ? "—" : app.auth.serverURL) .foregroundStyle(.secondary) .lineLimit(1) .truncationMode(.middle) } LabeledContent("Benutzer") { Text(app.auth.username.isEmpty ? "—" : app.auth.username) .foregroundStyle(.secondary) } HStack(spacing: 8) { Circle() .fill(app.network.isOnline ? .green : .orange) .frame(width: 8, height: 8) Text(app.network.isOnline ? "Online" : "Offline") if app.sync.queuedCount > 0 { Text("\(app.sync.queuedCount) wartend") .foregroundStyle(.secondary) .font(.subheadline) } } Button(role: .destructive) { showLogoutConfirm = true } label: { Label("Abmelden / Server wechseln", systemImage: "rectangle.portrait.and.arrow.right") } } header: { Text("Verbindung") } footer: { Text("Abmelden setzt die gespeicherten Anmeldedaten zurück. Heruntergeladene Inhalte bleiben.") } } private var playbackSection: some View { Section { Picker("Sprung-Dauer", selection: $skipSeconds) { ForEach(Self.skipOptions, id: \.self) { sec in Text("\(sec) s").tag(sec) } } } header: { Text("Wiedergabe") } footer: { Text("Gilt für die Skip-Knöpfe in der Player-Leiste und auf dem Sperrbildschirm.") } } private var appearanceSection: some View { Section { Picker("Bibliotheks-Ansicht", selection: $layoutRaw) { ForEach(LibraryLayout.allCases) { l in Label(l.label, systemImage: l.systemImage).tag(l.rawValue) } } Toggle("Beim Start automatisch aktualisieren", isOn: $autoRefreshOnLaunch) } header: { Text("Darstellung") } } private var downloadsSection: some View { Section { LabeledContent("Heruntergeladen") { Text("\(app.downloads.downloadedItems.count) Einträge") .foregroundStyle(.secondary) } } header: { Text("Downloads") } footer: { Text("Heruntergeladene Hörbücher und Folgen können einzeln über das Kontextmenü gelöscht werden.") } } private var aboutSection: some View { Section { LabeledContent("Version") { Text(appVersion).foregroundStyle(.secondary) } } header: { Text("Über") } } #endif // MARK: - macOS TabView panes #if os(macOS) private var connectionPane: some View { Form { LabeledContent("Server") { Text(app.auth.serverURL.isEmpty ? "—" : app.auth.serverURL) .foregroundStyle(.secondary) .textSelection(.enabled) } LabeledContent("Benutzer") { Text(app.auth.username.isEmpty ? "—" : app.auth.username) .foregroundStyle(.secondary) } LabeledContent("Status") { HStack(spacing: 6) { Circle() .fill(app.network.isOnline ? .green : .orange) .frame(width: 8, height: 8) Text(app.network.isOnline ? "Online" : "Offline") if app.sync.queuedCount > 0 { Text("(\(app.sync.queuedCount) wartend)") .foregroundStyle(.secondary) } } } HStack { Spacer() Button(role: .destructive) { showLogoutConfirm = true } label: { Label("Abmelden / Server wechseln", systemImage: "rectangle.portrait.and.arrow.right") } } } .formStyle(.grouped) } private var playbackPane: some View { Form { Picker("Sprung-Dauer", selection: $skipSeconds) { ForEach(Self.skipOptions, id: \.self) { sec in Text("\(sec) s").tag(sec) } } Text("Gilt für die Skip-Knöpfe in der Player-Leiste und Medientasten.") .font(.caption) .foregroundStyle(.secondary) } .formStyle(.grouped) } private var appearancePane: some View { Form { Picker("Bibliotheks-Ansicht", selection: $layoutRaw) { ForEach(LibraryLayout.allCases) { l in Label(l.label, systemImage: l.systemImage).tag(l.rawValue) } } Toggle("Beim Start automatisch aktualisieren", isOn: $autoRefreshOnLaunch) } .formStyle(.grouped) } private var aboutPane: some View { Form { LabeledContent("Version", value: appVersion) LabeledContent("Heruntergeladen", value: "\(app.downloads.downloadedItems.count) Einträge") } .formStyle(.grouped) } #endif // MARK: - Shared private var appVersion: String { let v = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? "?" let b = Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") as? String ?? "?" return "\(v) (\(b))" } }