Files
ABS-Client/ABS Client Mac/Audiobookshelf swift/Views/SettingsView.swift
2026-05-17 08:45:37 +02:00

120 lines
4.2 KiB
Swift

import SwiftUI
struct SettingsView: View {
@Environment(AppState.self) private var app
@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 {
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.")
}
}
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)
}
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))"
}
}