Files
ABS-Client/ABS Client/Audiobookshelf swift/Views/LoginView.swift
2026-05-17 21:06:59 +02:00

201 lines
6.7 KiB
Swift

import SwiftUI
struct LoginView: View {
@Environment(AppState.self) private var app
@State private var serverURL: String = ""
@State private var username: String = ""
@State private var password: String = ""
@State private var remember: Bool = true
@State private var isLoading: Bool = false
var body: some View {
#if os(iOS)
iOSBody
#else
macOSBody
#endif
}
// MARK: - iOS
#if os(iOS)
private var iOSBody: some View {
VStack(spacing: 0) {
// Header with green gradient background
ZStack {
LinearGradient(
colors: [Color.accentColor.opacity(0.85), Color.accentColor.opacity(0.55)],
startPoint: .topLeading,
endPoint: .bottomTrailing
)
.ignoresSafeArea(edges: .top)
VStack(spacing: 10) {
Image(systemName: "books.vertical.fill")
.font(.system(size: 52, weight: .regular))
.foregroundStyle(.white)
Text("ABS Client")
.font(.title.bold())
.foregroundStyle(.white)
Text("Verbinde dich mit deinem Audiobookshelf-Server")
.font(.subheadline)
.foregroundStyle(.white.opacity(0.85))
.multilineTextAlignment(.center)
}
.padding(.top, 56)
.padding(.bottom, 32)
.padding(.horizontal, 24)
}
.fixedSize(horizontal: false, vertical: true)
// Form fields uses native iOS Form appearance
Form {
Section {
TextField("https://abs.example.com", text: $serverURL)
.textContentType(.URL)
.keyboardType(.URL)
.textInputAutocapitalization(.never)
.autocorrectionDisabled(true)
.submitLabel(.next)
} header: {
Text("Server-URL")
}
Section {
TextField("Benutzername", text: $username)
.textContentType(.username)
.textInputAutocapitalization(.never)
.autocorrectionDisabled(true)
.submitLabel(.next)
SecureField("Passwort", text: $password)
.textContentType(.password)
.submitLabel(.go)
.onSubmit { if canLogin { doLogin() } }
} header: {
Text("Anmeldedaten")
}
Section {
Toggle("Anmeldung merken", isOn: $remember)
}
if let err = app.auth.errorMessage {
Section {
Label(err, systemImage: "exclamationmark.triangle.fill")
.foregroundStyle(.red)
.font(.callout)
}
}
Section {
Button(action: doLogin) {
HStack {
Spacer()
if isLoading {
ProgressView()
} else {
Text("Einloggen").bold()
}
Spacer()
}
}
.disabled(!canLogin)
}
}
.scrollContentBackground(.hidden)
.background(Color(.systemGroupedBackground))
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color(.systemGroupedBackground).ignoresSafeArea())
}
private var canLogin: Bool {
!isLoading && !serverURL.isEmpty && !username.isEmpty && !password.isEmpty
}
#endif
// MARK: - macOS
#if os(macOS)
private var macOSBody: some View {
VStack(spacing: 16) {
Spacer()
Image(systemName: "books.vertical.fill")
.font(.system(size: 48))
.foregroundStyle(.tint)
Text("ABS Client")
.font(.largeTitle).bold()
Text("Verbinde dich mit deinem Audiobookshelf-Server")
.foregroundStyle(.secondary)
VStack(alignment: .leading, spacing: 12) {
labeledField(label: "Server-URL") {
TextField("", text: $serverURL)
.textFieldStyle(.roundedBorder)
.disableAutocorrection(true)
}
labeledField(label: "Benutzername") {
TextField("", text: $username)
.textFieldStyle(.roundedBorder)
.disableAutocorrection(true)
}
labeledField(label: "Passwort") {
SecureField("", text: $password)
.textFieldStyle(.roundedBorder)
}
Toggle("Anmeldung merken", isOn: $remember)
.toggleStyle(.checkbox)
}
.frame(maxWidth: 380)
if let err = app.auth.errorMessage {
Text(err)
.foregroundStyle(.red)
.font(.callout)
.multilineTextAlignment(.center)
.frame(maxWidth: 380)
}
Button(action: doLogin) {
if isLoading {
ProgressView().controlSize(.small)
} else {
Text("Einloggen").frame(maxWidth: 200)
}
}
.buttonStyle(.borderedProminent)
.controlSize(.large)
.disabled(isLoading || serverURL.isEmpty || username.isEmpty || password.isEmpty)
.keyboardShortcut(.defaultAction)
Spacer()
}
.padding(32)
}
@ViewBuilder
private func labeledField<C: View>(label: String, @ViewBuilder content: () -> C) -> some View {
VStack(alignment: .leading, spacing: 4) {
Text(label).font(.subheadline).foregroundStyle(.secondary)
content()
}
}
#endif
// MARK: - Shared
private func doLogin() {
isLoading = true
Task {
await app.auth.login(
serverURL: serverURL,
username: username,
password: password,
remember: remember
)
isLoading = false
}
}
}