diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordView.kt index d3641ea749..feb1d3d53a 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordView.kt @@ -60,6 +60,7 @@ import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.components.Button import io.element.android.libraries.designsystem.theme.components.Icon +import io.element.android.libraries.designsystem.theme.components.PasswordVisibilityToggle import io.element.android.libraries.designsystem.theme.components.Scaffold import io.element.android.libraries.designsystem.theme.components.TextField import io.element.android.libraries.designsystem.theme.components.TopAppBar @@ -249,16 +250,10 @@ private fun LoginForm( placeholder = stringResource(CommonStrings.common_password), visualTransformation = if (passwordVisible) VisualTransformation.None else PasswordVisualTransformation(), trailingIcon = { - val image = - if (passwordVisible) CompoundIcons.VisibilityOn() else CompoundIcons.VisibilityOff() - val description = - if (passwordVisible) stringResource(CommonStrings.a11y_hide_password) else stringResource(CommonStrings.a11y_show_password) - Box(Modifier.clickable { passwordVisible = !passwordVisible }) { - Icon( - imageVector = image, - contentDescription = description, - ) - } + PasswordVisibilityToggle( + visible = passwordVisible, + onToggle = { passwordVisible = !passwordVisible }, + ) }, keyboardOptions = KeyboardOptions( keyboardType = KeyboardType.Password, diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetIdentityPasswordView.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetIdentityPasswordView.kt index 8d32433ac5..3bb77ac206 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetIdentityPasswordView.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetIdentityPasswordView.kt @@ -8,8 +8,6 @@ package io.element.android.features.securebackup.impl.reset.password -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -32,7 +30,7 @@ import io.element.android.libraries.designsystem.modifiers.onTabOrEnterKeyFocusN import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.components.Button -import io.element.android.libraries.designsystem.theme.components.Icon +import io.element.android.libraries.designsystem.theme.components.PasswordVisibilityToggle import io.element.android.libraries.designsystem.theme.components.TextField import io.element.android.libraries.designsystem.theme.components.TextFieldValidity import io.element.android.libraries.ui.strings.CommonStrings @@ -92,14 +90,10 @@ private fun Content(text: String, onTextChange: (String) -> Unit, hasError: Bool singleLine = true, visualTransformation = if (showPassword) VisualTransformation.None else PasswordVisualTransformation(), trailingIcon = { - val image = - if (showPassword) CompoundIcons.VisibilityOn() else CompoundIcons.VisibilityOff() - val description = - if (showPassword) stringResource(CommonStrings.a11y_hide_password) else stringResource(CommonStrings.a11y_show_password) - - Box(Modifier.clickable { showPassword = !showPassword }) { - Icon(imageVector = image, description) - } + PasswordVisibilityToggle( + visible = showPassword, + onToggle = { showPassword = !showPassword }, + ) }, validity = if (hasError) TextFieldValidity.Invalid else TextFieldValidity.None, supportingText = if (hasError) { diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/PasswordVisibilityToggle.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/PasswordVisibilityToggle.kt new file mode 100644 index 0000000000..9154ec6292 --- /dev/null +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/PasswordVisibilityToggle.kt @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2026 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.designsystem.theme.components + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import io.element.android.compound.tokens.generated.CompoundIcons +import io.element.android.libraries.ui.strings.CommonStrings + +/** + * Show/hide toggle for a password [TextField], intended for its `trailingIcon` slot. + * Shared so every password field reveals plaintext the same way and announces the + * same accessibility labels. + */ +@Composable +fun PasswordVisibilityToggle( + visible: Boolean, + onToggle: () -> Unit, + modifier: Modifier = Modifier, +) { + Box(modifier.clickable(onClick = onToggle)) { + Icon( + imageVector = if (visible) CompoundIcons.VisibilityOn() else CompoundIcons.VisibilityOff(), + contentDescription = stringResource( + if (visible) CommonStrings.a11y_hide_password else CommonStrings.a11y_show_password + ), + ) + } +}