Merge iOS and Mac app into one
This commit is contained in:
250
ABS Client/Audiobookshelf swift/Views/SettingsView.swift
Normal file
250
ABS Client/Audiobookshelf swift/Views/SettingsView.swift
Normal file
@@ -0,0 +1,250 @@
|
||||
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))"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user