// // 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() formPanel } //.preferredColorScheme(.light) } private var brandPanel: some View { ZStack(alignment: .bottomLeading) { Color.appBackground.ignoresSafeArea() 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: 24) { VStack(alignment: .leading, spacing: 10) { if UIDevice.current.userInterfaceIdiom == .phone { Image("splashLogo") .resizable() .scaledToFit() .frame(width: 120, height: 76) .accessibilityHidden(true) } Text("Aceda aqui à sua conta") .font(.jakartaBold24) .foregroundStyle(.white) } VStack(spacing: 14) { loginField( placeholder: "Email", icon: "envelope", text: $viewModel.email, field: .email, contentType: .emailAddress, keyboardType: .emailAddress ) passwordField } if let errorMessage = viewModel.errorMessage { Text(errorMessage) .font(.jakartaRegular14) .foregroundStyle(Color.loginErrorText) .frame(maxWidth: .infinity) .multilineTextAlignment(.center) .fixedSize(horizontal: false, vertical: true) .accessibilityLabel("Erro de autenticacao: \(errorMessage)") } Button { focusedField = nil Task { await viewModel.login() } } label: { Group { if viewModel.isLoading { ProgressView() .tint(Color.loginButtonText) } else { Text("Entrar") .font(.jakartaBold16) } } .frame(maxWidth: .infinity) .frame(height: 46) } .foregroundStyle(Color.loginButtonText) .background(Color.loginButtonBackground) .clipShape(RoundedRectangle(cornerRadius: 8)) .disabled(!viewModel.canSubmit) .opacity(viewModel.canSubmit ? 1 : 0.55) .accessibilityHint("Inicia sessao na conta de doente") Text("Não tem conta? Criar conta") .font(.jakartaRegular14) .foregroundStyle(.white.opacity(0.88)) .frame(maxWidth: .infinity) .multilineTextAlignment(.center) } .padding(.horizontal, 32) Spacer() Text("MedMap") .font(.jakartaRegular12) .foregroundStyle(.white.opacity(0.6)) .padding(.bottom, 24) } } private var passwordField: some View { loginFieldContainer(icon: "lock") { ZStack(alignment: .leading) { if viewModel.password.isEmpty { Text("Palavra-passe") .foregroundStyle(Color.loginPlaceholder) } SecureField("", text: $viewModel.password) .foregroundStyle(.white) .tint(.white) .textContentType(.password) .focused($focusedField, equals: .password) .submitLabel(.go) .onSubmit { Task { await viewModel.login() } } } } .accessibilityLabel("Palavra-passe") } private func loginField( placeholder: String, icon: String, text: Binding, field: Field, contentType: UITextContentType, keyboardType: UIKeyboardType ) -> some View { loginFieldContainer(icon: icon) { ZStack(alignment: .leading) { if text.wrappedValue.isEmpty { Text(placeholder) .foregroundStyle(Color.loginPlaceholder) } TextField("", text: text) .foregroundStyle(.white) .tint(.white) .textContentType(contentType) .keyboardType(keyboardType) .textInputAutocapitalization(.never) .autocorrectionDisabled() .focused($focusedField, equals: field) .submitLabel(.next) .onSubmit { focusedField = .password } } } .accessibilityLabel(placeholder) } private func loginFieldContainer( icon: String, @ViewBuilder content: () -> Content ) -> some View { HStack(spacing: 10) { Image(systemName: icon) .foregroundStyle(.white) .frame(width: 18) content() .font(.jakartaRegular16) } .padding(.horizontal, 14) .frame(height: 46) .background(Color.loginFieldBackground) .overlay { RoundedRectangle(cornerRadius: 8) .stroke(Color.loginFieldBorder, lineWidth: 1) } .clipShape(RoundedRectangle(cornerRadius: 8)) } } #Preview { LoginView() }