From b003e233056e04697a55b4a759d136bcd1654fe7 Mon Sep 17 00:00:00 2001 From: Alex Gabor Date: Mon, 7 Mar 2022 09:24:16 +0200 Subject: [PATCH] Add password visibility toggle --- app/build.gradle | 1 + .../test/showcase/ui/ComposeLoginRobot.kt | 1 + .../compose/screen/auth/AuthScreen.kt | 88 +++++++++++++------ .../main/res/drawable/avd_hide_password.xml | 88 +++++++++++++++++++ .../main/res/drawable/avd_show_password.xml | 87 ++++++++++++++++++ 5 files changed, 238 insertions(+), 27 deletions(-) create mode 100644 app/src/main/res/drawable/avd_hide_password.xml create mode 100644 app/src/main/res/drawable/avd_show_password.xml diff --git a/app/build.gradle b/app/build.gradle index cb29971..4655d31 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -85,6 +85,7 @@ dependencies { implementation "androidx.compose.ui:ui-tooling:$androidx_compose" implementation "androidx.compose.foundation:foundation:$androidx_compose" implementation "androidx.compose.material:material:$androidx_compose" + implementation "androidx.compose.animation:animation-graphics:$androidx_compose" implementation "com.google.accompanist:accompanist-insets:$google_accompanist" implementation "com.google.accompanist:accompanist-swiperefresh:$google_accompanist" diff --git a/app/src/androidTest/java/org/fnives/test/showcase/ui/ComposeLoginRobot.kt b/app/src/androidTest/java/org/fnives/test/showcase/ui/ComposeLoginRobot.kt index bfbf5a8..bb3db47 100644 --- a/app/src/androidTest/java/org/fnives/test/showcase/ui/ComposeLoginRobot.kt +++ b/app/src/androidTest/java/org/fnives/test/showcase/ui/ComposeLoginRobot.kt @@ -17,6 +17,7 @@ class ComposeLoginRobot( } fun assertPassword(password: String): ComposeLoginRobot = apply { + composeTestRule.onNodeWithTag(AuthScreenTag.PasswordVisibilityToggle).performClick() composeTestRule.onNodeWithTag(AuthScreenTag.PasswordInput).assertTextContains(password) } diff --git a/app/src/main/java/org/fnives/test/showcase/compose/screen/auth/AuthScreen.kt b/app/src/main/java/org/fnives/test/showcase/compose/screen/auth/AuthScreen.kt index 3cfeaae..8fc5cbb 100644 --- a/app/src/main/java/org/fnives/test/showcase/compose/screen/auth/AuthScreen.kt +++ b/app/src/main/java/org/fnives/test/showcase/compose/screen/auth/AuthScreen.kt @@ -1,12 +1,19 @@ package org.fnives.test.showcase.compose.screen.auth +import androidx.compose.animation.graphics.res.animatedVectorResource +import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter +import androidx.compose.animation.graphics.vector.AnimatedImageVector +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.* import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.getValue +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier @@ -16,6 +23,7 @@ import androidx.compose.ui.res.stringResource 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.text.input.VisualTransformation import androidx.compose.ui.unit.dp import androidx.constraintlayout.compose.ConstraintLayout import com.google.accompanist.insets.statusBarsPadding @@ -53,10 +61,8 @@ fun AuthScreen( } } -@OptIn(ExperimentalComposeUiApi::class) @Composable private fun CredentialsFields(authScreenState: AuthScreenState, modifier: Modifier = Modifier) { - val keyboardController = LocalSoftwareKeyboardController.current Column( modifier .fillMaxWidth() @@ -64,33 +70,57 @@ private fun CredentialsFields(authScreenState: AuthScreenState, modifier: Modifi verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { - OutlinedTextField( - value = authScreenState.username, - label = { Text(text = stringResource(id = R.string.username)) }, - placeholder = { Text(text = stringResource(id = R.string.username)) }, - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Email, imeAction = ImeAction.Next), - onValueChange = { authScreenState.onUsernameChanged(it) }, - modifier = Modifier.fillMaxWidth().testTag(AuthScreenTag.UsernameInput) - ) - OutlinedTextField( - value = authScreenState.password, - label = { Text(text = stringResource(id = R.string.password)) }, - placeholder = { Text(text = stringResource(id = R.string.password)) }, - onValueChange = { authScreenState.onPasswordChanged(it) }, - keyboardOptions = KeyboardOptions(autoCorrect = false, imeAction = ImeAction.Done, keyboardType = KeyboardType.Password), - keyboardActions = KeyboardActions(onDone = { - keyboardController?.hide() - authScreenState.onLogin() - }), - visualTransformation = PasswordVisualTransformation(), - modifier = Modifier - .fillMaxWidth() - .padding(top = 16.dp) - .testTag(AuthScreenTag.PasswordInput) - ) + UsernameField(authScreenState) + PasswordField(authScreenState) } } +@OptIn(ExperimentalComposeUiApi::class, androidx.compose.animation.graphics.ExperimentalAnimationGraphicsApi::class) +@Composable +private fun PasswordField(authScreenState: AuthScreenState) { + var passwordVisible by remember { mutableStateOf(false) } + val keyboardController = LocalSoftwareKeyboardController.current + OutlinedTextField( + value = authScreenState.password, + label = { Text(text = stringResource(id = R.string.password)) }, + placeholder = { Text(text = stringResource(id = R.string.password)) }, + trailingIcon = { + val image = AnimatedImageVector.animatedVectorResource(R.drawable.avd_show_password) + Icon( + painter = rememberAnimatedVectorPainter(image, passwordVisible), + contentDescription = null, + modifier = Modifier.clickable { passwordVisible = !passwordVisible } + .testTag(AuthScreenTag.PasswordVisibilityToggle) + ) + }, + onValueChange = { authScreenState.onPasswordChanged(it) }, + keyboardOptions = KeyboardOptions(autoCorrect = false, imeAction = ImeAction.Done, keyboardType = KeyboardType.Password), + keyboardActions = KeyboardActions(onDone = { + keyboardController?.hide() + authScreenState.onLogin() + }), + visualTransformation = if (passwordVisible) VisualTransformation.None else PasswordVisualTransformation(), + modifier = Modifier + .fillMaxWidth() + .padding(top = 16.dp) + .testTag(AuthScreenTag.PasswordInput) + ) +} + +@Composable +private fun UsernameField(authScreenState: AuthScreenState) { + OutlinedTextField( + value = authScreenState.username, + label = { Text(text = stringResource(id = R.string.username)) }, + placeholder = { Text(text = stringResource(id = R.string.username)) }, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Email, imeAction = ImeAction.Next), + onValueChange = { authScreenState.onUsernameChanged(it) }, + modifier = Modifier + .fillMaxWidth() + .testTag(AuthScreenTag.UsernameInput) + ) +} + @Composable private fun Snackbar(authScreenState: AuthScreenState, modifier: Modifier = Modifier) { val snackbarState = remember { SnackbarHostState() } @@ -114,7 +144,10 @@ private fun Snackbar(authScreenState: AuthScreenState, modifier: Modifier = Modi @Composable private fun LoginButton(modifier: Modifier = Modifier, onClick: () -> Unit) { Box(modifier) { - Button(onClick = onClick, Modifier.fillMaxWidth().testTag(AuthScreenTag.LoginButton)) { + Button(onClick = onClick, + Modifier + .fillMaxWidth() + .testTag(AuthScreenTag.LoginButton)) { Text(text = "Login") } } @@ -140,4 +173,5 @@ object AuthScreenTag { const val UsernameInput = "AuthScreenTag.UsernameInput" const val PasswordInput = "AuthScreenTag.PasswordInput" const val LoginButton = "AuthScreenTag.LoginButton" + const val PasswordVisibilityToggle = "AuthScreenTag.PasswordVisibilityToggle" } diff --git a/app/src/main/res/drawable/avd_hide_password.xml b/app/src/main/res/drawable/avd_hide_password.xml new file mode 100644 index 0000000..89f4cdd --- /dev/null +++ b/app/src/main/res/drawable/avd_hide_password.xml @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/avd_show_password.xml b/app/src/main/res/drawable/avd_show_password.xml new file mode 100644 index 0000000..aed3983 --- /dev/null +++ b/app/src/main/res/drawable/avd_show_password.xml @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +