2022-12-02 12:46:48 +01:00
|
|
|
//
|
|
|
|
// LoginView.swift
|
2022-12-02 20:55:13 +01:00
|
|
|
// Login
|
2022-12-02 12:46:48 +01:00
|
|
|
//
|
|
|
|
// Created by Javier Cicchelli on 30/11/2022.
|
|
|
|
// Copyright © 2022 Röck+Cöde. All rights reserved.
|
|
|
|
//
|
|
|
|
|
2022-12-11 21:33:33 +01:00
|
|
|
import APIService
|
2022-12-02 12:46:48 +01:00
|
|
|
import SwiftUI
|
|
|
|
|
2022-12-02 20:23:10 +01:00
|
|
|
public struct LoginView: View {
|
2022-12-02 12:46:48 +01:00
|
|
|
|
|
|
|
// MARK: States
|
|
|
|
|
2022-12-02 19:27:21 +01:00
|
|
|
@State private var containerTopPadding: CGFloat = 0
|
2022-12-02 12:46:48 +01:00
|
|
|
|
2022-12-12 00:55:37 +01:00
|
|
|
// MARK: Properties
|
|
|
|
|
2022-12-12 01:30:49 +01:00
|
|
|
private let authenticated: AuthenticatedClosure
|
2022-12-12 00:55:37 +01:00
|
|
|
|
2022-12-02 20:23:10 +01:00
|
|
|
// MARK: Initialisers
|
|
|
|
|
2022-12-12 01:30:49 +01:00
|
|
|
public init(authenticated: @escaping AuthenticatedClosure) {
|
|
|
|
self.authenticated = authenticated
|
2022-12-12 00:55:37 +01:00
|
|
|
}
|
2022-12-02 20:23:10 +01:00
|
|
|
|
2022-12-02 12:46:48 +01:00
|
|
|
// MARK: Body
|
|
|
|
|
2022-12-02 20:23:10 +01:00
|
|
|
public var body: some View {
|
2022-12-02 12:46:48 +01:00
|
|
|
ScrollView(
|
|
|
|
.vertical,
|
|
|
|
showsIndicators: false
|
|
|
|
) {
|
2022-12-12 01:30:49 +01:00
|
|
|
LoginContainer(authenticated: authenticated)
|
2022-12-02 12:46:48 +01:00
|
|
|
.padding(.horizontal, 24)
|
2022-12-02 19:27:21 +01:00
|
|
|
.padding(.top, containerTopPadding)
|
2022-12-02 12:46:48 +01:00
|
|
|
}
|
|
|
|
.background(Color.red)
|
|
|
|
.overlay(ViewHeightGeometry())
|
|
|
|
.onPreferenceChange(ViewHeightPreferenceKey.self) { height in
|
2022-12-02 19:27:21 +01:00
|
|
|
containerTopPadding = height * 0.1
|
2022-12-02 12:46:48 +01:00
|
|
|
}
|
2022-12-12 00:55:37 +01:00
|
|
|
.interactiveDismissDisabled()
|
2022-12-02 12:46:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// MARK: - Views
|
|
|
|
|
|
|
|
fileprivate extension LoginView {
|
2022-12-02 19:27:21 +01:00
|
|
|
struct LoginContainer: View {
|
2022-12-12 00:55:37 +01:00
|
|
|
|
2022-12-02 19:27:21 +01:00
|
|
|
// MARK: States
|
|
|
|
|
|
|
|
@State private var isAuthenticating: Bool = false
|
2022-12-02 20:23:10 +01:00
|
|
|
@State private var username: String = ""
|
|
|
|
@State private var password: String = ""
|
2022-12-02 12:46:48 +01:00
|
|
|
@State private var errorMessage: String?
|
2022-12-12 00:55:37 +01:00
|
|
|
|
|
|
|
// MARK: Properties
|
|
|
|
|
|
|
|
private let getUser: GetUserUseCase
|
|
|
|
|
|
|
|
// MARK: Initialisers
|
|
|
|
|
2022-12-12 01:30:49 +01:00
|
|
|
init(authenticated: @escaping AuthenticatedClosure) {
|
|
|
|
self.getUser = .init(authenticated: authenticated)
|
2022-12-12 00:55:37 +01:00
|
|
|
}
|
2022-12-02 12:46:48 +01:00
|
|
|
|
2022-12-02 19:27:21 +01:00
|
|
|
// MARK: Body
|
|
|
|
|
2022-12-02 12:46:48 +01:00
|
|
|
var body: some View {
|
2022-12-02 19:27:21 +01:00
|
|
|
VStack(spacing: 32) {
|
2022-12-02 20:55:13 +01:00
|
|
|
Text(
|
|
|
|
"login.title.text",
|
|
|
|
bundle: .module,
|
|
|
|
comment: "Login view title text."
|
|
|
|
)
|
|
|
|
.font(.largeTitle)
|
|
|
|
.fontWeight(.bold)
|
|
|
|
.foregroundColor(.primary)
|
2022-12-02 12:46:48 +01:00
|
|
|
|
|
|
|
LoginForm(
|
|
|
|
username: $username,
|
|
|
|
password: $password,
|
|
|
|
errorMessage: $errorMessage
|
2022-12-02 19:27:21 +01:00
|
|
|
) {
|
2022-12-11 21:33:33 +01:00
|
|
|
isAuthenticating = true
|
2022-12-02 19:27:21 +01:00
|
|
|
}
|
2022-12-02 12:46:48 +01:00
|
|
|
|
|
|
|
Button {
|
2022-12-11 21:33:33 +01:00
|
|
|
isAuthenticating = true
|
2022-12-02 12:46:48 +01:00
|
|
|
} label: {
|
2022-12-02 19:27:21 +01:00
|
|
|
Label {
|
2022-12-02 20:55:13 +01:00
|
|
|
Text(
|
|
|
|
"login.button.log_in.text",
|
|
|
|
bundle: .module,
|
|
|
|
comment: "Log in button text."
|
|
|
|
)
|
|
|
|
.fontWeight(.semibold)
|
2022-12-02 19:27:21 +01:00
|
|
|
} icon: {
|
|
|
|
if isAuthenticating {
|
|
|
|
ProgressView()
|
2022-12-11 22:13:56 +01:00
|
|
|
.controlSize(.regular)
|
2022-12-02 19:27:21 +01:00
|
|
|
} else {
|
|
|
|
EmptyView()
|
|
|
|
}
|
|
|
|
}
|
2022-12-02 20:55:13 +01:00
|
|
|
.labelStyle(.logIn)
|
2022-12-02 12:46:48 +01:00
|
|
|
}
|
2022-12-02 19:27:21 +01:00
|
|
|
.tint(.orange)
|
|
|
|
.buttonStyle(.borderedProminent)
|
|
|
|
.buttonBorderShape(.roundedRectangle(radius: 8))
|
|
|
|
.controlSize(.large)
|
|
|
|
.disabled(isLoginDisabled)
|
2022-12-11 21:33:33 +01:00
|
|
|
.task(id: isAuthenticating) {
|
|
|
|
await authenticate()
|
|
|
|
}
|
2022-12-02 12:46:48 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-12-02 19:27:21 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// MARK: - Helpers
|
|
|
|
|
|
|
|
private extension LoginView.LoginContainer {
|
2022-12-11 21:33:33 +01:00
|
|
|
|
|
|
|
// MARK: Computed
|
|
|
|
|
2022-12-02 19:27:21 +01:00
|
|
|
var isLoginDisabled: Bool {
|
|
|
|
username.isEmpty || password.isEmpty
|
|
|
|
}
|
2022-12-11 21:33:33 +01:00
|
|
|
|
|
|
|
// MARK: Functions
|
|
|
|
|
|
|
|
func authenticate() async {
|
|
|
|
guard isAuthenticating else { return }
|
|
|
|
|
|
|
|
do {
|
2022-12-12 00:55:37 +01:00
|
|
|
try await getUser(
|
2022-12-11 21:33:33 +01:00
|
|
|
username: username,
|
|
|
|
password: password
|
|
|
|
)
|
|
|
|
} catch APIClientError.authenticationFailed {
|
2022-12-11 22:09:39 +01:00
|
|
|
errorMessage = "login.error.authentication_failed.text"
|
2022-12-11 21:33:33 +01:00
|
|
|
isAuthenticating = false
|
|
|
|
} catch {
|
2022-12-11 22:09:39 +01:00
|
|
|
errorMessage = "login.error.authentication_unknown.text"
|
2022-12-11 21:33:33 +01:00
|
|
|
isAuthenticating = false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-02 19:27:21 +01:00
|
|
|
}
|
|
|
|
|
2022-12-02 12:46:48 +01:00
|
|
|
// MARK: - Previews
|
|
|
|
|
|
|
|
struct LoginView_Previews: PreviewProvider {
|
|
|
|
static var previews: some View {
|
2022-12-12 01:30:49 +01:00
|
|
|
LoginView { _, _ in
|
|
|
|
// closure for authenticated action.
|
|
|
|
}
|
2022-12-02 12:46:48 +01:00
|
|
|
}
|
|
|
|
}
|