From 7d2ac309aba056964ae7f2a9bb6fb01181f99997 Mon Sep 17 00:00:00 2001 From: Trey t Date: Wed, 17 Dec 2025 13:35:21 -0600 Subject: [PATCH] Fix password visibility toggle position in LoginView MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add SecureIconTextField component that includes the eye toggle button inside the text field (matching RegisterView's OrganicSecureField). Update LoginView to use SecureIconTextField instead of IconTextField with an external button, ensuring consistent UI across auth screens. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- iosApp/iosApp/Login/LoginView.swift | 33 ++++------- .../Shared/Components/FormComponents.swift | 56 +++++++++++++++++++ 2 files changed, 67 insertions(+), 22 deletions(-) diff --git a/iosApp/iosApp/Login/LoginView.swift b/iosApp/iosApp/Login/LoginView.swift index dda1206..0d527ca 100644 --- a/iosApp/iosApp/Login/LoginView.swift +++ b/iosApp/iosApp/Login/LoginView.swift @@ -97,28 +97,17 @@ struct LoginView: View { VStack(alignment: .leading, spacing: 8) { FieldLabel(text: L10n.Auth.loginPasswordLabel) - HStack(spacing: 12) { - IconTextField( - icon: "lock.fill", - placeholder: L10n.Auth.enterPassword, - text: $viewModel.password, - isSecure: !isPasswordVisible, - textContentType: .password, - onSubmit: { viewModel.login() } - ) - .onChange(of: viewModel.password) { _, _ in - viewModel.clearError() - } - .accessibilityIdentifier(AccessibilityIdentifiers.Authentication.passwordField) - - Button(action: { - isPasswordVisible.toggle() - }) { - Image(systemName: isPasswordVisible ? "eye.slash.fill" : "eye.fill") - .font(.system(size: 16, weight: .medium)) - .foregroundColor(Color.appTextSecondary) - } - .accessibilityIdentifier(AccessibilityIdentifiers.Authentication.passwordVisibilityToggle) + SecureIconTextField( + icon: "lock.fill", + placeholder: L10n.Auth.enterPassword, + text: $viewModel.password, + isVisible: $isPasswordVisible, + textContentType: .password, + onSubmit: { viewModel.login() }, + accessibilityId: AccessibilityIdentifiers.Authentication.passwordField + ) + .onChange(of: viewModel.password) { _, _ in + viewModel.clearError() } } diff --git a/iosApp/iosApp/Shared/Components/FormComponents.swift b/iosApp/iosApp/Shared/Components/FormComponents.swift index 2ff77ed..2165030 100644 --- a/iosApp/iosApp/Shared/Components/FormComponents.swift +++ b/iosApp/iosApp/Shared/Components/FormComponents.swift @@ -288,6 +288,62 @@ struct IconTextField: View { } } +// MARK: - Secure TextField with Icon and Visibility Toggle + +struct SecureIconTextField: View { + let icon: String + let placeholder: String + @Binding var text: String + @Binding var isVisible: Bool + var textContentType: UITextContentType? = nil + var onSubmit: (() -> Void)? = nil + var accessibilityId: String? = nil + + @FocusState private var isFocused: Bool + + var body: some View { + HStack(spacing: 12) { + ZStack { + Circle() + .fill(Color.appPrimary.opacity(0.1)) + .frame(width: 32, height: 32) + Image(systemName: icon) + .font(.system(size: 14, weight: .medium)) + .foregroundColor(Color.appPrimary) + } + + Group { + if isVisible { + TextField(placeholder, text: $text) + .accessibilityIdentifier(accessibilityId ?? "") + } else { + SecureField(placeholder, text: $text) + .accessibilityIdentifier(accessibilityId ?? "") + } + } + .font(.system(size: 16, weight: .medium)) + .textContentType(textContentType) + .focused($isFocused) + .submitLabel(.go) + .onSubmit { onSubmit?() } + + Button(action: { isVisible.toggle() }) { + Image(systemName: isVisible ? "eye.slash.fill" : "eye.fill") + .font(.system(size: 16, weight: .medium)) + .foregroundColor(Color.appTextSecondary) + } + } + .padding(16) + .background(Color.appBackgroundPrimary.opacity(0.5)) + .clipShape(RoundedRectangle(cornerRadius: 16, style: .continuous)) + .overlay( + RoundedRectangle(cornerRadius: 16, style: .continuous) + .stroke(isFocused ? Color.appPrimary : Color.appTextSecondary.opacity(0.15), lineWidth: 1.5) + ) + .animation(.easeInOut(duration: 0.2), value: isFocused) + } +} + // MARK: - Field Label struct FieldLabel: View {