From a055af5280e66fdf1e2e5d79156a4093d7138f1e Mon Sep 17 00:00:00 2001 From: Altynbek Nurtaza <altynbek.nurtaza@nu.edu.kz> Date: Mon, 21 Feb 2022 14:43:07 +0000 Subject: [PATCH] Add reveal password to registration screen --- .cla-accepted | 1 + .../compose/material/OutlinedTextFieldExt.kt | 103 ++++++++++++++++++ .../briar/desktop/login/LoginScreen.kt | 6 +- .../briar/desktop/login/RegistrationScreen.kt | 10 +- 4 files changed, 110 insertions(+), 10 deletions(-) diff --git a/.cla-accepted b/.cla-accepted index 722b0d023d..1669034493 100644 --- a/.cla-accepted +++ b/.cla-accepted @@ -4,3 +4,4 @@ @akwizgran (Michael Rogers) @paul-lorenc (Paul Lorenc) @ialokim (Mikolai Gütschow) +@altynbek.nurtaza (Altynbek Nurtaza) diff --git a/src/main/kotlin/androidx/compose/material/OutlinedTextFieldExt.kt b/src/main/kotlin/androidx/compose/material/OutlinedTextFieldExt.kt index e5ad6c096a..d2defb118f 100644 --- a/src/main/kotlin/androidx/compose/material/OutlinedTextFieldExt.kt +++ b/src/main/kotlin/androidx/compose/material/OutlinedTextFieldExt.kt @@ -27,6 +27,9 @@ import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.InitialFocusState.AFTER_FIRST_FOCUSSED import androidx.compose.material.InitialFocusState.AFTER_FOCUS_LOST_ONCE import androidx.compose.material.InitialFocusState.FROM_START +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Visibility +import androidx.compose.material.icons.filled.VisibilityOff import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue @@ -43,9 +46,11 @@ import androidx.compose.ui.input.key.key import androidx.compose.ui.input.key.onPreviewKeyEvent import androidx.compose.ui.input.key.type import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.input.PasswordVisualTransformation import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import org.briarproject.briar.desktop.utils.InternationalizationUtils.i18n /** * Material Design outlined text field with extended support for error and helper messages, @@ -142,3 +147,101 @@ fun OutlinedTextField( } enum class InitialFocusState { FROM_START, AFTER_FIRST_FOCUSSED, AFTER_FOCUS_LOST_ONCE } + +/** + * Material Design outlined text field with support for error and helper messages, + * as well as for handling the Enter key. The difference with [OutlinedTextField] is + * that it shows the visibility icons instead of error icons in the error state. + * All parameters not specified here are the same as on the original [OutlinedTextField]. + * + * @param visualTransformation Visual transformation is only applied when password is hidden + */ +@OptIn(ExperimentalComposeUiApi::class) +@Composable +fun OutlinedPasswordTextField( + value: String, + onValueChange: (String) -> Unit, + modifier: Modifier = Modifier, + onEnter: () -> Unit = {}, + enabled: Boolean = true, + readOnly: Boolean = false, + textStyle: TextStyle = LocalTextStyle.current, + label: @Composable (() -> Unit)? = null, + placeholder: @Composable (() -> Unit)? = null, + leadingIcon: @Composable (() -> Unit)? = null, + helperMessage: String? = null, + errorMessage: String? = null, + isError: Boolean = false, + showErrorWhen: InitialFocusState = FROM_START, + visualTransformation: VisualTransformation = PasswordVisualTransformation(), + keyboardOptions: KeyboardOptions = KeyboardOptions.Default, + singleLine: Boolean = false, + maxLines: Int = Int.MAX_VALUE, + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, + shape: Shape = MaterialTheme.shapes.small, + colors: TextFieldColors = TextFieldDefaults.outlinedTextFieldColors() +) { + var isPasswordVisible by remember { mutableStateOf(false) } + + OutlinedTextField( + value = value, + onValueChange = onValueChange, + modifier = modifier, + onEnter = onEnter, + enabled = enabled, + readOnly = readOnly, + textStyle = textStyle, + label = label, + placeholder = placeholder, + leadingIcon = leadingIcon, + trailingIcon = { + ShowHidePasswordIcon( + isVisible = isPasswordVisible, + toggleIsVisible = { + isPasswordVisible = !isPasswordVisible + }, + ) + }, + errorIcon = { + ShowHidePasswordIcon( + isVisible = isPasswordVisible, + toggleIsVisible = { + isPasswordVisible = !isPasswordVisible + }, + ) + }, + helperMessage = helperMessage, + errorMessage = errorMessage, + isError = isError, + showErrorWhen = showErrorWhen, + visualTransformation = if (!isPasswordVisible) visualTransformation else VisualTransformation.None, + keyboardOptions = keyboardOptions, + singleLine = singleLine, + maxLines = maxLines, + interactionSource = interactionSource, + shape = shape, + colors = colors, + ) +} + +@Composable +private fun ShowHidePasswordIcon( + isVisible: Boolean, + toggleIsVisible: () -> Unit, +) { + IconButton( + onClick = toggleIsVisible + ) { + if (isVisible) { + Icon( + imageVector = Icons.Filled.VisibilityOff, + contentDescription = i18n("access.password.show"), + ) + } else { + Icon( + imageVector = Icons.Filled.Visibility, + contentDescription = i18n("access.password.hide"), + ) + } + } +} diff --git a/src/main/kotlin/org/briarproject/briar/desktop/login/LoginScreen.kt b/src/main/kotlin/org/briarproject/briar/desktop/login/LoginScreen.kt index f1413a50ad..abdb0fd68a 100644 --- a/src/main/kotlin/org/briarproject/briar/desktop/login/LoginScreen.kt +++ b/src/main/kotlin/org/briarproject/briar/desktop/login/LoginScreen.kt @@ -26,7 +26,7 @@ import androidx.compose.material.ButtonType.DESTRUCTIVE import androidx.compose.material.ButtonType.NEUTRAL import androidx.compose.material.DialogButton import androidx.compose.material.ExperimentalMaterialApi -import androidx.compose.material.OutlinedTextField +import androidx.compose.material.OutlinedPasswordTextField import androidx.compose.material.Text import androidx.compose.material.TextButton import androidx.compose.runtime.Composable @@ -38,7 +38,6 @@ import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardType -import androidx.compose.ui.text.input.PasswordVisualTransformation import androidx.compose.ui.unit.dp import org.briarproject.briar.desktop.login.LoginSubViewModel.State.COMPACTING import org.briarproject.briar.desktop.login.LoginSubViewModel.State.MIGRATING @@ -110,14 +109,13 @@ fun LoginForm( val initialFocusRequester = remember { FocusRequester() } val passwordForgotten = remember { mutableStateOf(false) } - OutlinedTextField( + OutlinedPasswordTextField( value = password, onValueChange = setPassword, label = { Text(i18n("startup.field.password")) }, singleLine = true, isError = passwordInvalidError, errorMessage = i18n("startup.error.password_wrong"), - visualTransformation = PasswordVisualTransformation(), keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password, imeAction = ImeAction.Next), modifier = Modifier.fillMaxWidth().focusRequester(initialFocusRequester), onEnter = onEnter diff --git a/src/main/kotlin/org/briarproject/briar/desktop/login/RegistrationScreen.kt b/src/main/kotlin/org/briarproject/briar/desktop/login/RegistrationScreen.kt index d223cb9b17..0397637deb 100644 --- a/src/main/kotlin/org/briarproject/briar/desktop/login/RegistrationScreen.kt +++ b/src/main/kotlin/org/briarproject/briar/desktop/login/RegistrationScreen.kt @@ -24,6 +24,7 @@ import androidx.compose.foundation.layout.requiredHeight import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.InitialFocusState.AFTER_FIRST_FOCUSSED import androidx.compose.material.InitialFocusState.AFTER_FOCUS_LOST_ONCE +import androidx.compose.material.OutlinedPasswordTextField import androidx.compose.material.OutlinedTextField import androidx.compose.material.Text import androidx.compose.runtime.Composable @@ -37,7 +38,6 @@ import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardType -import androidx.compose.ui.text.input.PasswordVisualTransformation import androidx.compose.ui.unit.dp import org.briarproject.briar.desktop.login.RegistrationSubViewModel.State.CREATED import org.briarproject.briar.desktop.login.RegistrationSubViewModel.State.CREATING @@ -140,7 +140,7 @@ fun PasswordForm( if (password.isNotEmpty()) StrengthMeter(passwordStrength, Modifier.fillMaxWidth()) } - OutlinedTextField( + OutlinedPasswordTextField( value = password, onValueChange = setPassword, label = { Text(i18n("startup.field.password")) }, @@ -148,12 +148,11 @@ fun PasswordForm( isError = passwordTooWeakError, showErrorWhen = AFTER_FOCUS_LOST_ONCE, errorMessage = i18n("startup.error.password_too_weak"), - visualTransformation = PasswordVisualTransformation(), keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password, imeAction = ImeAction.Next), modifier = Modifier.fillMaxWidth().focusRequester(initialFocusRequester), - onEnter = { focusManager.moveFocus(FocusDirection.Next) } + onEnter = { focusManager.moveFocus(FocusDirection.Next) }, ) - OutlinedTextField( + OutlinedPasswordTextField( value = passwordConfirmation, onValueChange = setPasswordConfirmation, label = { Text(i18n("startup.field.password_confirmation")) }, @@ -161,7 +160,6 @@ fun PasswordForm( isError = passwordsDontMatchError, showErrorWhen = AFTER_FIRST_FOCUSSED, errorMessage = i18n("startup.error.passwords_not_match"), - visualTransformation = PasswordVisualTransformation(), keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password, imeAction = ImeAction.Done), modifier = Modifier.fillMaxWidth(), onEnter = onEnter, -- GitLab