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

95 lines
3.0 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 {
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", placeholder: "https://abs.example.com") {
TextField("", text: $serverURL)
.textFieldStyle(.roundedBorder)
.disableAutocorrection(true)
}
LabeledField(label: "Benutzername", placeholder: "user") {
TextField("", text: $username)
.textFieldStyle(.roundedBorder)
.disableAutocorrection(true)
}
LabeledField(label: "Passwort", placeholder: "••••••") {
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)
}
private func doLogin() {
isLoading = true
Task {
await app.auth.login(
serverURL: serverURL,
username: username,
password: password,
remember: remember
)
isLoading = false
}
}
}
private struct LabeledField<Content: View>: View {
let label: String
let placeholder: String
@ViewBuilder let content: Content
var body: some View {
VStack(alignment: .leading, spacing: 4) {
Text(label).font(.subheadline).foregroundStyle(.secondary)
content
}
}
}