diff --git a/medmap/Extensions/Color+Extensions.swift b/medmap/Extensions/Color+Extensions.swift index ea62737..262951d 100644 --- a/medmap/Extensions/Color+Extensions.swift +++ b/medmap/Extensions/Color+Extensions.swift @@ -56,7 +56,15 @@ extension Color { static let appAccent = Color(hex: "#4A2EC2") static let appTextPrimary = Color(hex: "#333333") static let appBorder = Color(hex: "#E5E5E5") - + + // MARK: - Login screen (matches src/main/resources/static/css/login.css) + static let loginFieldBackground = Color(hex: "#5D8CC6") + static let loginFieldBorder = Color.white.opacity(0.45) + static let loginPlaceholder = Color.white.opacity(0.6) + static let loginButtonBackground = Color(hex: "#88BDFF") + static let loginButtonText = Color(hex: "#0A2647") + static let loginErrorText = Color(hex: "#FFE08A") + // Add more as needed, e.g.: // static let appError = Color(hex: "#FF3B30") // static let appSuccess = Color(hex: "#34C759") diff --git a/medmap/Views/LoginView.swift b/medmap/Views/LoginView.swift index 2c21dc8..291bbb7 100644 --- a/medmap/Views/LoginView.swift +++ b/medmap/Views/LoginView.swift @@ -60,11 +60,10 @@ struct LoginView: View { private var formPanel: some View { VStack(spacing: 0) { - - + Spacer(minLength: 32) - VStack(alignment: .leading, spacing: 28) { + VStack(alignment: .leading, spacing: 24) { VStack(alignment: .leading, spacing: 10) { if UIDevice.current.userInterfaceIdiom == .phone { Image("splashLogo") @@ -74,15 +73,13 @@ struct LoginView: View { .accessibilityHidden(true) } - Text("Bem-vindo de volta") + Text("Aceda aqui à sua conta") .font(.jakartaBold24) - .foregroundStyle(Color.appTextPrimary) - + .foregroundStyle(.white) } - VStack(spacing: 18) { + VStack(spacing: 14) { loginField( - title: "Email", placeholder: "Email", icon: "envelope", text: $viewModel.email, @@ -97,7 +94,9 @@ struct LoginView: View { if let errorMessage = viewModel.errorMessage { Text(errorMessage) .font(.jakartaRegular14) - .foregroundStyle(.red) + .foregroundStyle(Color.loginErrorText) + .frame(maxWidth: .infinity) + .multilineTextAlignment(.center) .fixedSize(horizontal: false, vertical: true) .accessibilityLabel("Erro de autenticacao: \(errorMessage)") } @@ -111,46 +110,49 @@ struct LoginView: View { Group { if viewModel.isLoading { ProgressView() - .tint(.white) + .tint(Color.loginButtonText) } else { Text("Entrar") .font(.jakartaBold16) } } .frame(maxWidth: .infinity) - .frame(height: 52) + .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(Color.appTextPrimary.opacity(0.45)) + .foregroundStyle(.white.opacity(0.6)) .padding(.bottom, 24) } - } 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) + 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) @@ -160,19 +162,11 @@ struct LoginView: View { } } } - .padding(.horizontal, 16) - .frame(height: 52) - .background(Color.appSecondary) - .overlay { - RoundedRectangle(cornerRadius: 8) - .stroke(Color.appBorder, lineWidth: 1) - } - .clipShape(RoundedRectangle(cornerRadius: 8)) } + .accessibilityLabel("Palavra-passe") } private func loginField( - title: String, placeholder: String, icon: String, text: Binding, @@ -180,16 +174,15 @@ struct LoginView: View { 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) + 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) @@ -200,15 +193,29 @@ struct LoginView: View { focusedField = .password } } - .padding(.horizontal, 16) - .frame(height: 52) - .background(Color.appSecondary) - .overlay { - RoundedRectangle(cornerRadius: 8) - .stroke(Color.appBorder, lineWidth: 1) - } - .clipShape(RoundedRectangle(cornerRadius: 8)) } + .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)) } }