feat: add iOS support and harden receipt scanning
This commit is contained in:
109
app/ios/ScanReceipts/Features/Auth/LoginView.swift
Normal file
109
app/ios/ScanReceipts/Features/Auth/LoginView.swift
Normal file
@@ -0,0 +1,109 @@
|
||||
import SwiftUI
|
||||
|
||||
/// "Anmelden" screen — the default screen of `AuthFlowView`. Talks to auth
|
||||
/// state exclusively through `AppState.login(email:password:)`; never calls
|
||||
/// `AuthAPI` directly (see `AppState`'s own doc comment for why).
|
||||
struct LoginView: View {
|
||||
@EnvironmentObject private var appState: AppState
|
||||
|
||||
/// Switches `AuthFlowView` to the signup screen.
|
||||
var onSwitchToSignup: () -> Void
|
||||
|
||||
@State private var email = ""
|
||||
@State private var password = ""
|
||||
@State private var isSubmitting = false
|
||||
|
||||
private var canSubmit: Bool {
|
||||
!email.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
|
||||
&& !password.isEmpty
|
||||
&& !isSubmitting
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
ScrollView {
|
||||
VStack(spacing: ZenithSpacing.md) {
|
||||
VStack(spacing: ZenithSpacing.xs) {
|
||||
Image(systemName: "doc.text.viewfinder")
|
||||
.font(.system(size: 40))
|
||||
.foregroundStyle(Color.zenithBlack)
|
||||
Text("ScanReceipts")
|
||||
.zenithHeadlineLgStyle()
|
||||
}
|
||||
.padding(.top, ZenithSpacing.lg)
|
||||
|
||||
VStack(spacing: ZenithSpacing.sm) {
|
||||
ZenithTextField(
|
||||
label: "E-Mail",
|
||||
text: $email,
|
||||
keyboardType: .emailAddress,
|
||||
textContentType: .username,
|
||||
autocapitalization: .never,
|
||||
autocorrectionDisabled: true
|
||||
)
|
||||
|
||||
ZenithTextField(
|
||||
label: "Passwort",
|
||||
text: $password,
|
||||
isSecure: true,
|
||||
textContentType: .password
|
||||
)
|
||||
}
|
||||
|
||||
if let error = appState.lastError {
|
||||
Text(error)
|
||||
.zenithBodySmStyle(color: .zenithError)
|
||||
.multilineTextAlignment(.center)
|
||||
}
|
||||
|
||||
Button {
|
||||
Task { await submit() }
|
||||
} label: {
|
||||
Group {
|
||||
if isSubmitting {
|
||||
ProgressView()
|
||||
.tint(.white)
|
||||
} else {
|
||||
Text("Anmelden")
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
.buttonStyle(.zenithPrimary)
|
||||
.disabled(!canSubmit)
|
||||
|
||||
HStack(spacing: ZenithSpacing.sm) {
|
||||
ZenithDivider()
|
||||
Text("oder")
|
||||
.zenithBodySmStyle()
|
||||
ZenithDivider()
|
||||
}
|
||||
|
||||
// No outer `.frame(height:)` here — AppleSignInButton sizes
|
||||
// its own button (minHeight: 50) and needs the flexibility
|
||||
// to grow when it shows its own inline error text below it.
|
||||
AppleSignInButton()
|
||||
|
||||
Button("Noch kein Konto? Registrieren") {
|
||||
appState.lastError = nil
|
||||
onSwitchToSignup()
|
||||
}
|
||||
.buttonStyle(.zenithPlain)
|
||||
.padding(.top, ZenithSpacing.xs)
|
||||
}
|
||||
.padding(.horizontal, ZenithSpacing.md)
|
||||
.padding(.bottom, ZenithSpacing.lg)
|
||||
}
|
||||
.scrollDismissesKeyboard(.interactively)
|
||||
.background(Color.zenithBg)
|
||||
}
|
||||
|
||||
private func submit() async {
|
||||
guard canSubmit else { return }
|
||||
isSubmitting = true
|
||||
defer { isSubmitting = false }
|
||||
await appState.login(
|
||||
email: email.trimmingCharacters(in: .whitespacesAndNewlines),
|
||||
password: password
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user