229 lines
7.5 KiB
Swift
229 lines
7.5 KiB
Swift
//
|
|
// LoginView.swift
|
|
// medmap
|
|
//
|
|
|
|
import SwiftUI
|
|
|
|
struct LoginView: View {
|
|
|
|
@StateObject private var viewModel = LoginViewModel()
|
|
@FocusState private var focusedField: Field?
|
|
|
|
private enum Field {
|
|
case email
|
|
case password
|
|
}
|
|
|
|
var body: some View {
|
|
ZStack {
|
|
Color.appBackground.ignoresSafeArea()
|
|
|
|
GeometryReader { geometry in
|
|
let usesTwoColumns = geometry.size.width > 700
|
|
|
|
Group {
|
|
if usesTwoColumns {
|
|
HStack(spacing: 0) {
|
|
brandPanel
|
|
.frame(width: geometry.size.width * 0.44)
|
|
formPanel
|
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
|
}
|
|
} else {
|
|
formPanel
|
|
}
|
|
}
|
|
}
|
|
}
|
|
.preferredColorScheme(.light)
|
|
}
|
|
|
|
private var brandPanel: some View {
|
|
ZStack(alignment: .bottomLeading) {
|
|
Color(hex: "#315F98")
|
|
|
|
Image("splashImage")
|
|
.resizable()
|
|
.scaledToFit()
|
|
.frame(maxWidth: 360)
|
|
.padding(40)
|
|
.accessibilityHidden(true)
|
|
|
|
VStack(alignment: .leading, spacing: 10) {
|
|
Image("splashLogo")
|
|
.resizable()
|
|
.scaledToFit()
|
|
.frame(width: 146, height: 96)
|
|
.accessibilityHidden(true)
|
|
Text("MedMap")
|
|
.font(.jakartaBold24)
|
|
.foregroundStyle(.white)
|
|
Text("Acompanhamento simples da sua medicacao.")
|
|
.font(.jakartaRegular16)
|
|
.foregroundStyle(.white.opacity(0.84))
|
|
.fixedSize(horizontal: false, vertical: true)
|
|
}
|
|
.padding(44)
|
|
}
|
|
.ignoresSafeArea()
|
|
}
|
|
|
|
private var formPanel: some View {
|
|
VStack(spacing: 0) {
|
|
Spacer(minLength: 32)
|
|
|
|
VStack(alignment: .leading, spacing: 28) {
|
|
VStack(alignment: .leading, spacing: 10) {
|
|
if UIDevice.current.userInterfaceIdiom == .phone {
|
|
Image("splashLogo")
|
|
.resizable()
|
|
.scaledToFit()
|
|
.frame(width: 120, height: 76)
|
|
.accessibilityHidden(true)
|
|
}
|
|
|
|
Text("Bem-vindo de volta")
|
|
.font(.jakartaBold24)
|
|
.foregroundStyle(Color.appTextPrimary)
|
|
Text("Entre na sua conta de doente para consultar o seu plano de medicacao.")
|
|
.font(.jakartaRegular16)
|
|
.foregroundStyle(Color.appTextPrimary.opacity(0.68))
|
|
.fixedSize(horizontal: false, vertical: true)
|
|
}
|
|
|
|
VStack(spacing: 18) {
|
|
loginField(
|
|
title: "Email",
|
|
placeholder: "Email",
|
|
icon: "envelope",
|
|
text: $viewModel.email,
|
|
field: .email,
|
|
contentType: .emailAddress,
|
|
keyboardType: .emailAddress
|
|
)
|
|
|
|
passwordField
|
|
}
|
|
|
|
if let errorMessage = viewModel.errorMessage {
|
|
Text(errorMessage)
|
|
.font(.jakartaRegular14)
|
|
.foregroundStyle(.red)
|
|
.fixedSize(horizontal: false, vertical: true)
|
|
.accessibilityLabel("Erro de autenticacao: \(errorMessage)")
|
|
}
|
|
|
|
Button {
|
|
focusedField = nil
|
|
Task {
|
|
await viewModel.login()
|
|
}
|
|
} label: {
|
|
Group {
|
|
if viewModel.isLoading {
|
|
ProgressView()
|
|
.tint(.white)
|
|
} else {
|
|
Text("Entrar")
|
|
.font(.jakartaBold16)
|
|
}
|
|
}
|
|
.frame(maxWidth: .infinity)
|
|
.frame(height: 52)
|
|
}
|
|
.foregroundStyle(.white)
|
|
.background(Color.appBackground)
|
|
.clipShape(RoundedRectangle(cornerRadius: 8))
|
|
.disabled(!viewModel.canSubmit)
|
|
.opacity(viewModel.canSubmit ? 1 : 0.55)
|
|
.accessibilityHint("Inicia sessao na conta de doente")
|
|
}
|
|
.frame(maxWidth: 430)
|
|
.padding(.horizontal, 32)
|
|
|
|
Spacer()
|
|
|
|
Text("MedMap")
|
|
.font(.jakartaRegular12)
|
|
.foregroundStyle(Color.appTextPrimary.opacity(0.45))
|
|
.padding(.bottom, 24)
|
|
}
|
|
.background(Color.white)
|
|
}
|
|
|
|
private var passwordField: some View {
|
|
VStack(alignment: .leading, spacing: 8) {
|
|
Text("Palavra-passe")
|
|
.font(.jakartaMedium14)
|
|
.foregroundStyle(Color.appTextPrimary)
|
|
|
|
HStack(spacing: 12) {
|
|
Image(systemName: "lock")
|
|
.foregroundStyle(Color.appBackground)
|
|
.frame(width: 20)
|
|
SecureField("Palavra-passe", text: $viewModel.password)
|
|
.textContentType(.password)
|
|
.focused($focusedField, equals: .password)
|
|
.submitLabel(.go)
|
|
.onSubmit {
|
|
Task {
|
|
await viewModel.login()
|
|
}
|
|
}
|
|
}
|
|
.padding(.horizontal, 16)
|
|
.frame(height: 52)
|
|
.background(Color.appSecondary)
|
|
.overlay {
|
|
RoundedRectangle(cornerRadius: 8)
|
|
.stroke(Color.appBorder, lineWidth: 1)
|
|
}
|
|
.clipShape(RoundedRectangle(cornerRadius: 8))
|
|
}
|
|
}
|
|
|
|
private func loginField(
|
|
title: String,
|
|
placeholder: String,
|
|
icon: String,
|
|
text: Binding<String>,
|
|
field: Field,
|
|
contentType: UITextContentType,
|
|
keyboardType: UIKeyboardType
|
|
) -> some View {
|
|
VStack(alignment: .leading, spacing: 8) {
|
|
Text(title)
|
|
.font(.jakartaMedium14)
|
|
.foregroundStyle(Color.appTextPrimary)
|
|
|
|
HStack(spacing: 12) {
|
|
Image(systemName: icon)
|
|
.foregroundStyle(Color.appBackground)
|
|
.frame(width: 20)
|
|
TextField(placeholder, text: text)
|
|
.textContentType(contentType)
|
|
.keyboardType(keyboardType)
|
|
.textInputAutocapitalization(.never)
|
|
.autocorrectionDisabled()
|
|
.focused($focusedField, equals: field)
|
|
.submitLabel(.next)
|
|
.onSubmit {
|
|
focusedField = .password
|
|
}
|
|
}
|
|
.padding(.horizontal, 16)
|
|
.frame(height: 52)
|
|
.background(Color.appSecondary)
|
|
.overlay {
|
|
RoundedRectangle(cornerRadius: 8)
|
|
.stroke(Color.appBorder, lineWidth: 1)
|
|
}
|
|
.clipShape(RoundedRectangle(cornerRadius: 8))
|
|
}
|
|
}
|
|
}
|
|
|
|
#Preview {
|
|
LoginView()
|
|
} |