201 lines
6.7 KiB
Swift
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
|
|
}
|
|
}
|
|
}
|