mirror of
https://github.com/Novaloop-AG/element-x-android.git
synced 2026-11-04 10:32:34 +00:00
Add shared PasswordVisibilityToggle design-system component
Extract the show/hide password trailing-icon into a reusable PasswordVisibilityToggle so every password field reveals plaintext the same way and announces the same a11y labels. Reuse it from the login and reset-identity password fields. Pure refactor, no behaviour change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+4
-9
@@ -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.preview.PreviewsDayNight
|
||||||
import io.element.android.libraries.designsystem.theme.components.Button
|
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.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.Scaffold
|
||||||
import io.element.android.libraries.designsystem.theme.components.TextField
|
import io.element.android.libraries.designsystem.theme.components.TextField
|
||||||
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
||||||
@@ -249,16 +250,10 @@ private fun LoginForm(
|
|||||||
placeholder = stringResource(CommonStrings.common_password),
|
placeholder = stringResource(CommonStrings.common_password),
|
||||||
visualTransformation = if (passwordVisible) VisualTransformation.None else PasswordVisualTransformation(),
|
visualTransformation = if (passwordVisible) VisualTransformation.None else PasswordVisualTransformation(),
|
||||||
trailingIcon = {
|
trailingIcon = {
|
||||||
val image =
|
PasswordVisibilityToggle(
|
||||||
if (passwordVisible) CompoundIcons.VisibilityOn() else CompoundIcons.VisibilityOff()
|
visible = passwordVisible,
|
||||||
val description =
|
onToggle = { passwordVisible = !passwordVisible },
|
||||||
if (passwordVisible) stringResource(CommonStrings.a11y_hide_password) else stringResource(CommonStrings.a11y_show_password)
|
|
||||||
Box(Modifier.clickable { passwordVisible = !passwordVisible }) {
|
|
||||||
Icon(
|
|
||||||
imageVector = image,
|
|
||||||
contentDescription = description,
|
|
||||||
)
|
)
|
||||||
}
|
|
||||||
},
|
},
|
||||||
keyboardOptions = KeyboardOptions(
|
keyboardOptions = KeyboardOptions(
|
||||||
keyboardType = KeyboardType.Password,
|
keyboardType = KeyboardType.Password,
|
||||||
|
|||||||
+5
-11
@@ -8,8 +8,6 @@
|
|||||||
|
|
||||||
package io.element.android.features.securebackup.impl.reset.password
|
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.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
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.ElementPreview
|
||||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
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.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.TextField
|
||||||
import io.element.android.libraries.designsystem.theme.components.TextFieldValidity
|
import io.element.android.libraries.designsystem.theme.components.TextFieldValidity
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
@@ -92,14 +90,10 @@ private fun Content(text: String, onTextChange: (String) -> Unit, hasError: Bool
|
|||||||
singleLine = true,
|
singleLine = true,
|
||||||
visualTransformation = if (showPassword) VisualTransformation.None else PasswordVisualTransformation(),
|
visualTransformation = if (showPassword) VisualTransformation.None else PasswordVisualTransformation(),
|
||||||
trailingIcon = {
|
trailingIcon = {
|
||||||
val image =
|
PasswordVisibilityToggle(
|
||||||
if (showPassword) CompoundIcons.VisibilityOn() else CompoundIcons.VisibilityOff()
|
visible = showPassword,
|
||||||
val description =
|
onToggle = { showPassword = !showPassword },
|
||||||
if (showPassword) stringResource(CommonStrings.a11y_hide_password) else stringResource(CommonStrings.a11y_show_password)
|
)
|
||||||
|
|
||||||
Box(Modifier.clickable { showPassword = !showPassword }) {
|
|
||||||
Icon(imageVector = image, description)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
validity = if (hasError) TextFieldValidity.Invalid else TextFieldValidity.None,
|
validity = if (hasError) TextFieldValidity.Invalid else TextFieldValidity.None,
|
||||||
supportingText = if (hasError) {
|
supportingText = if (hasError) {
|
||||||
|
|||||||
+37
@@ -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
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user