From f4f8ef7a9d75ad29e852b55650ad9dfcbdba30b7 Mon Sep 17 00:00:00 2001 From: "mohamad.jaara" Date: Thu, 7 Sep 2023 11:24:59 +0200 Subject: [PATCH] fix: app crash when clicking next on create account screen (#2183) (#2186) (cherry picked from commit 46a57b77464370086c5390de7f8ceb455228b3e4) --- .../create/common/handle/UsernameTextField.kt | 8 +- .../details/CreateAccountDetailsScreen.kt | 1 + .../create/email/CreateAccountEmailScreen.kt | 8 +- .../devices/register/RegisterDeviceScreen.kt | 3 +- .../devices/remove/RemoveDeviceDialog.kt | 3 +- .../login/email/LoginEmailScreen.kt | 1 + .../authentication/login/email/ProxyScreen.kt | 2 +- .../com/wire/android/ui/common/WireDialog.kt | 8 +- .../ui/common/textfield/AutoFillTextField.kt | 31 +++-- .../common/textfield/WirePasswordTextField.kt | 129 ++++++++++++------ .../CreatePasswordProtectedGuestLinkScreen.kt | 6 +- .../dialog/create/CreateBackupDialogs.kt | 5 +- .../dialog/restore/RestoreBackupDialogs.kt | 3 +- .../JoinConversationViaDeepLinkDialog.kt | 4 +- 14 files changed, 133 insertions(+), 79 deletions(-) diff --git a/app/src/main/kotlin/com/wire/android/ui/authentication/create/common/handle/UsernameTextField.kt b/app/src/main/kotlin/com/wire/android/ui/authentication/create/common/handle/UsernameTextField.kt index 315763e0970..7392115f00a 100644 --- a/app/src/main/kotlin/com/wire/android/ui/authentication/create/common/handle/UsernameTextField.kt +++ b/app/src/main/kotlin/com/wire/android/ui/authentication/create/common/handle/UsernameTextField.kt @@ -25,7 +25,6 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier -import androidx.compose.ui.autofill.AutofillType import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource @@ -35,9 +34,8 @@ import androidx.compose.ui.text.input.TextFieldValue import com.wire.android.R import com.wire.android.ui.common.ShakeAnimation import com.wire.android.ui.common.error.CoreFailureErrorDialog -import com.wire.android.ui.common.textfield.AutoFillTextField +import com.wire.android.ui.common.textfield.WireTextField import com.wire.android.ui.common.textfield.WireTextFieldState -import com.wire.android.ui.common.textfield.clearAutofillTree import com.wire.android.ui.theme.wireDimensions @OptIn(ExperimentalComposeUiApi::class) @@ -50,7 +48,6 @@ fun UsernameTextField( onUsernameChange: (TextFieldValue) -> Unit, onUsernameErrorAnimated: () -> Unit ) { - clearAutofillTree() if (errorState is HandleUpdateErrorState.DialogError.GenericError) { CoreFailureErrorDialog(errorState.coreFailure, onErrorDismiss) } @@ -61,8 +58,7 @@ fun UsernameTextField( animate() onUsernameErrorAnimated() } - AutoFillTextField( - autofillTypes = listOf(AutofillType.Username), + WireTextField( value = username, onValueChange = onUsernameChange, placeholderText = stringResource(R.string.create_account_username_placeholder), diff --git a/app/src/main/kotlin/com/wire/android/ui/authentication/create/details/CreateAccountDetailsScreen.kt b/app/src/main/kotlin/com/wire/android/ui/authentication/create/details/CreateAccountDetailsScreen.kt index 8d0430513ab..f452e402207 100644 --- a/app/src/main/kotlin/com/wire/android/ui/authentication/create/details/CreateAccountDetailsScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/authentication/create/details/CreateAccountDetailsScreen.kt @@ -41,6 +41,7 @@ import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.geometry.Offset import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.stringResource diff --git a/app/src/main/kotlin/com/wire/android/ui/authentication/create/email/CreateAccountEmailScreen.kt b/app/src/main/kotlin/com/wire/android/ui/authentication/create/email/CreateAccountEmailScreen.kt index 9d1747750c6..66a226503d1 100644 --- a/app/src/main/kotlin/com/wire/android/ui/authentication/create/email/CreateAccountEmailScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/authentication/create/email/CreateAccountEmailScreen.kt @@ -41,7 +41,6 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier -import androidx.compose.ui.autofill.AutofillType import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.platform.LocalContext @@ -77,9 +76,8 @@ import com.wire.android.ui.common.button.WireButtonState import com.wire.android.ui.common.button.WirePrimaryButton import com.wire.android.ui.common.button.WireSecondaryButton import com.wire.android.ui.common.error.CoreFailureErrorDialog -import com.wire.android.ui.common.textfield.AutoFillTextField +import com.wire.android.ui.common.textfield.WireTextField import com.wire.android.ui.common.textfield.WireTextFieldState -import com.wire.android.ui.common.textfield.clearAutofillTree import com.wire.android.ui.common.topappbar.WireCenterAlignedTopAppBar import com.wire.android.ui.destinations.CreateAccountDetailsScreenDestination import com.wire.android.ui.destinations.LoginScreenDestination @@ -139,7 +137,6 @@ private fun EmailContent( tosUrl: String, serverConfig: ServerConfig.Links ) { - clearAutofillTree() val focusRequester = remember { FocusRequester() } Scaffold(topBar = { @@ -174,8 +171,7 @@ private fun EmailContent( ) .testTag("createTeamText") ) - AutoFillTextField( - autofillTypes = listOf(AutofillType.EmailAddress), + WireTextField( value = state.email, onValueChange = onEmailChange, placeholderText = stringResource(R.string.create_account_email_placeholder), diff --git a/app/src/main/kotlin/com/wire/android/ui/authentication/devices/register/RegisterDeviceScreen.kt b/app/src/main/kotlin/com/wire/android/ui/authentication/devices/register/RegisterDeviceScreen.kt index 3405aee3d6f..0e620c51b7d 100644 --- a/app/src/main/kotlin/com/wire/android/ui/authentication/devices/register/RegisterDeviceScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/authentication/devices/register/RegisterDeviceScreen.kt @@ -197,7 +197,8 @@ private fun PasswordTextField(state: RegisterDeviceState, onPasswordChange: (Tex keyboardActions = KeyboardActions(onDone = { keyboardController?.hide() }), modifier = Modifier .padding(horizontal = MaterialTheme.wireDimensions.spacing16x) - .testTag("password field") + .testTag("password field"), + autofill = true ) } diff --git a/app/src/main/kotlin/com/wire/android/ui/authentication/devices/remove/RemoveDeviceDialog.kt b/app/src/main/kotlin/com/wire/android/ui/authentication/devices/remove/RemoveDeviceDialog.kt index f3f726e2137..23e28368388 100644 --- a/app/src/main/kotlin/com/wire/android/ui/authentication/devices/remove/RemoveDeviceDialog.kt +++ b/app/src/main/kotlin/com/wire/android/ui/authentication/devices/remove/RemoveDeviceDialog.kt @@ -84,7 +84,8 @@ fun RemoveDeviceDialog( modifier = Modifier .focusRequester(focusRequester) .padding(bottom = MaterialTheme.wireDimensions.spacing8x) - .testTag("remove device password field") + .testTag("remove device password field"), + autofill = true ) LaunchedEffect(Unit) { // executed only once when showing the dialog focusRequester.requestFocus() diff --git a/app/src/main/kotlin/com/wire/android/ui/authentication/login/email/LoginEmailScreen.kt b/app/src/main/kotlin/com/wire/android/ui/authentication/login/email/LoginEmailScreen.kt index 3d5d75a343e..ffaaafd77db 100644 --- a/app/src/main/kotlin/com/wire/android/ui/authentication/login/email/LoginEmailScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/authentication/login/email/LoginEmailScreen.kt @@ -245,6 +245,7 @@ private fun PasswordInput(modifier: Modifier, password: TextFieldValue, onPasswo imeAction = ImeAction.Done, keyboardActions = KeyboardActions(onDone = { keyboardController?.hide() }), modifier = modifier.testTag("passwordField"), + autofill = true, testTag = "PasswordInput" ) } diff --git a/app/src/main/kotlin/com/wire/android/ui/authentication/login/email/ProxyScreen.kt b/app/src/main/kotlin/com/wire/android/ui/authentication/login/email/ProxyScreen.kt index ea2e35b7490..8b49292885c 100644 --- a/app/src/main/kotlin/com/wire/android/ui/authentication/login/email/ProxyScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/authentication/login/email/ProxyScreen.kt @@ -152,7 +152,7 @@ private fun ProxyPasswordInput(modifier: Modifier, proxyPassword: TextFieldValue labelText = stringResource(R.string.label_proxy_password), keyboardActions = KeyboardActions(onDone = { keyboardController?.hide() }), modifier = modifier.testTag("passwordField"), - autofillTypes = listOf() + autofill = false ) } diff --git a/app/src/main/kotlin/com/wire/android/ui/common/WireDialog.kt b/app/src/main/kotlin/com/wire/android/ui/common/WireDialog.kt index 36e405afc72..8e2db34c383 100644 --- a/app/src/main/kotlin/com/wire/android/ui/common/WireDialog.kt +++ b/app/src/main/kotlin/com/wire/android/ui/common/WireDialog.kt @@ -290,7 +290,9 @@ fun PreviewWireDialog() { ) { WirePasswordTextField( value = password, - onValueChange = { password = it }) + onValueChange = { password = it }, + autofill = false + ) } } } @@ -338,7 +340,9 @@ fun PreviewWireDialogWith2OptionButtons() { ) { WirePasswordTextField( value = password, - onValueChange = { password = it }) + onValueChange = { password = it }, + autofill = true + ) } } } diff --git a/app/src/main/kotlin/com/wire/android/ui/common/textfield/AutoFillTextField.kt b/app/src/main/kotlin/com/wire/android/ui/common/textfield/AutoFillTextField.kt index bb88692c270..b45da0bd919 100644 --- a/app/src/main/kotlin/com/wire/android/ui/common/textfield/AutoFillTextField.kt +++ b/app/src/main/kotlin/com/wire/android/ui/common/textfield/AutoFillTextField.kt @@ -29,9 +29,11 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier +import androidx.compose.ui.autofill.Autofill import androidx.compose.ui.autofill.AutofillNode import androidx.compose.ui.autofill.AutofillType import androidx.compose.ui.focus.onFocusChanged +import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Shape import androidx.compose.ui.layout.boundsInWindow import androidx.compose.ui.layout.onGloballyPositioned @@ -72,6 +74,7 @@ internal fun AutoFillTextField( shape: Shape = RoundedCornerShape(MaterialTheme.wireDimensions.textFieldCornerSize), colors: WireTextFieldColors = wireTextFieldColors(), modifier: Modifier = Modifier, + onTap: (Offset) -> Unit = { }, testTag: String = String.EMPTY ) { val autofillNode = AutofillNode( @@ -105,20 +108,28 @@ internal fun AutoFillTextField( shape = shape, colors = colors, modifier = modifier - .onGloballyPositioned { autofillNode.boundingBox = it.boundsInWindow() } - .onFocusChanged { focusState -> - autofill?.run { - if (focusState.isFocused) { - requestAutofillForNode(autofillNode) - } else { - cancelAutofillForNode(autofillNode) - } - } - }, + .fillBounds(autofillNode) + .defaultOnFocusAutoFill(autofill, autofillNode), + onTap = onTap, testTag = testTag ) } +@OptIn(ExperimentalComposeUiApi::class) +fun Modifier.fillBounds(autofillNode: AutofillNode) = this.then( + Modifier.onGloballyPositioned { autofillNode.boundingBox = it.boundsInWindow() } +) + +@OptIn(ExperimentalComposeUiApi::class) +fun Modifier.defaultOnFocusAutoFill(autofill: Autofill?, autofillNode: AutofillNode): Modifier = + then(Modifier.onFocusChanged { focusState -> + if (focusState.isFocused) { + autofill?.requestAutofillForNode(autofillNode) + } else { + autofill?.cancelAutofillForNode(autofillNode) + } + }) + @OptIn(ExperimentalComposeUiApi::class) @Composable fun clearAutofillTree() { diff --git a/app/src/main/kotlin/com/wire/android/ui/common/textfield/WirePasswordTextField.kt b/app/src/main/kotlin/com/wire/android/ui/common/textfield/WirePasswordTextField.kt index 912c364e96a..623dc5e9a08 100644 --- a/app/src/main/kotlin/com/wire/android/ui/common/textfield/WirePasswordTextField.kt +++ b/app/src/main/kotlin/com/wire/android/ui/common/textfield/WirePasswordTextField.kt @@ -40,6 +40,7 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.autofill.AutofillType +import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Shape import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.stringResource @@ -77,58 +78,102 @@ fun WirePasswordTextField( shape: Shape = RoundedCornerShape(16.dp), colors: WireTextFieldColors = wireTextFieldColors(), modifier: Modifier = Modifier, + autofill: Boolean, + onTap: (Offset) -> Unit = { }, testTag: String = String.EMPTY, - autofillTypes: List = listOf(AutofillType.Password) ) { var passwordVisibility by remember { mutableStateOf(false) } - AutoFillTextField( - autofillTypes = autofillTypes, - value = value, - onValueChange = onValueChange, - readOnly = readOnly, - singleLine = true, - maxLines = 1, - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password, autoCorrect = false, imeAction = imeAction), - keyboardActions = keyboardActions, - placeholderText = placeholderText, - labelText = labelText, - labelMandatoryIcon = labelMandatoryIcon, - descriptionText = descriptionText, - state = state, - interactionSource = interactionSource, - textStyle = textStyle, - placeholderTextStyle = placeHolderTextStyle, - inputMinHeight = inputMinHeight, - shape = shape, - colors = colors, - modifier = modifier, - visualTransformation = if (passwordVisibility) VisualTransformation.None else PasswordVisualTransformation(), - trailingIcon = { - val image = if (passwordVisibility) Icons.Filled.Visibility else Icons.Filled.VisibilityOff - IconButton(onClick = { passwordVisibility = !passwordVisibility }) { - Icon( - imageVector = image, - contentDescription = stringResource( - if (!passwordVisibility) R.string.content_description_reveal_password - else R.string.content_description_hide_password - ), - modifier = Modifier - .size(20.dp) - .testTag("hidePassword") - ) - } - }, - testTag = testTag, - ) + + val keyBoardOption = remember { + KeyboardOptions(keyboardType = KeyboardType.Password, autoCorrect = false, imeAction = imeAction) + } + + val visualTransformation = remember(passwordVisibility) { + if (passwordVisibility) VisualTransformation.None else PasswordVisualTransformation() + } + + val icon = remember(passwordVisibility) { + if (passwordVisibility) Icons.Filled.Visibility else Icons.Filled.VisibilityOff + } + + val iconButton = @Composable { + IconButton(onClick = { passwordVisibility = !passwordVisibility }) { + Icon( + imageVector = icon, + contentDescription = stringResource( + if (!passwordVisibility) R.string.content_description_reveal_password + else R.string.content_description_hide_password + ), + modifier = Modifier + .size(20.dp) + .testTag("hidePassword") + ) + } + } + + if (autofill) { + AutoFillTextField( + value = value, + onValueChange = onValueChange, + readOnly = readOnly, + singleLine = true, + maxLines = 1, + keyboardOptions = keyBoardOption, + keyboardActions = keyboardActions, + placeholderText = placeholderText, + labelText = labelText, + labelMandatoryIcon = labelMandatoryIcon, + descriptionText = descriptionText, + state = state, + interactionSource = interactionSource, + textStyle = textStyle, + placeholderTextStyle = placeHolderTextStyle, + inputMinHeight = inputMinHeight, + shape = shape, + colors = colors, + modifier = modifier, + visualTransformation = visualTransformation, + trailingIcon = iconButton, + autofillTypes = listOf(AutofillType.Password), + onTap = onTap, + testTag = testTag + ) + } else { + WireTextField( + value = value, + onValueChange = onValueChange, + readOnly = readOnly, + singleLine = true, + maxLines = 1, + keyboardOptions = keyBoardOption, + keyboardActions = keyboardActions, + placeholderText = placeholderText, + labelText = labelText, + labelMandatoryIcon = labelMandatoryIcon, + descriptionText = descriptionText, + state = state, + interactionSource = interactionSource, + textStyle = textStyle, + placeholderTextStyle = placeHolderTextStyle, + inputMinHeight = inputMinHeight, + shape = shape, + colors = colors, + modifier = modifier, + visualTransformation = visualTransformation, + trailingIcon = iconButton, + onTap = onTap, + testTag = testTag + ) + } } -@OptIn(ExperimentalComposeUiApi::class) @Preview(name = "Default WirePasswordTextField") @Composable fun PreviewWirePasswordTextField() { WirePasswordTextField( value = TextFieldValue(""), onValueChange = {}, - modifier = Modifier.padding(16.dp) + modifier = Modifier.padding(16.dp), + autofill = false ) } diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/editguestaccess/createPasswordProtectedGuestLink/CreatePasswordProtectedGuestLinkScreen.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/editguestaccess/createPasswordProtectedGuestLink/CreatePasswordProtectedGuestLinkScreen.kt index a38db4c3400..095611041a2 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/editguestaccess/createPasswordProtectedGuestLink/CreatePasswordProtectedGuestLinkScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/editguestaccess/createPasswordProtectedGuestLink/CreatePasswordProtectedGuestLinkScreen.kt @@ -35,7 +35,6 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.remember -import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalClipboardManager import androidx.compose.ui.platform.LocalContext @@ -60,7 +59,6 @@ import com.wire.android.ui.theme.wireColorScheme import com.wire.android.ui.theme.wireDimensions import com.wire.android.ui.theme.wireTypography -@OptIn(ExperimentalComposeUiApi::class) @RootNavGraph @Destination( navArgsDelegate = CreatePasswordGuestLinkNavArgs::class @@ -161,7 +159,7 @@ fun CreatePasswordProtectedGuestLinkScreen( id = R.string.conversation_options_create_password_protected_guest_link_button_placeholder_text ), onValueChange = viewModel::onPasswordUpdated, - autofillTypes = emptyList() + autofill = false ) Spacer(modifier = Modifier.height(dimensions().spacing8x)) } @@ -185,7 +183,7 @@ fun CreatePasswordProtectedGuestLinkScreen( ), value = viewModel.state.passwordConfirm, onValueChange = viewModel::onPasswordConfirmUpdated, - autofillTypes = emptyList() + autofill = false ) Spacer(modifier = Modifier.height(dimensions().spacing24x)) } diff --git a/app/src/main/kotlin/com/wire/android/ui/home/settings/backup/dialog/create/CreateBackupDialogs.kt b/app/src/main/kotlin/com/wire/android/ui/home/settings/backup/dialog/create/CreateBackupDialogs.kt index 04ac81e9cbc..655666c7558 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/settings/backup/dialog/create/CreateBackupDialogs.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/settings/backup/dialog/create/CreateBackupDialogs.kt @@ -33,7 +33,6 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue -import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.TextFieldValue @@ -52,7 +51,6 @@ import com.wire.android.util.permission.rememberCreateFileFlow import java.util.Locale import kotlin.math.roundToInt -@OptIn(ExperimentalComposeUiApi::class) @Composable fun SetBackupPasswordDialog( isBackupPasswordValid: Boolean, @@ -85,7 +83,8 @@ fun SetBackupPasswordDialog( onValueChange = { backupPassword = it onBackupPasswordChanged(it) - } + }, + autofill = false ) } } diff --git a/app/src/main/kotlin/com/wire/android/ui/home/settings/backup/dialog/restore/RestoreBackupDialogs.kt b/app/src/main/kotlin/com/wire/android/ui/home/settings/backup/dialog/restore/RestoreBackupDialogs.kt index 13a0754d7bb..ffd633ca6e9 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/settings/backup/dialog/restore/RestoreBackupDialogs.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/settings/backup/dialog/restore/RestoreBackupDialogs.kt @@ -102,7 +102,8 @@ fun EnterRestorePasswordDialog( ) { WirePasswordTextField( value = restorePassword, - onValueChange = { restorePassword = it } + onValueChange = { restorePassword = it }, + autofill = false ) } } else { diff --git a/app/src/main/kotlin/com/wire/android/ui/joinConversation/JoinConversationViaDeepLinkDialog.kt b/app/src/main/kotlin/com/wire/android/ui/joinConversation/JoinConversationViaDeepLinkDialog.kt index 2460d23da2b..ff62d2ea445 100644 --- a/app/src/main/kotlin/com/wire/android/ui/joinConversation/JoinConversationViaDeepLinkDialog.kt +++ b/app/src/main/kotlin/com/wire/android/ui/joinConversation/JoinConversationViaDeepLinkDialog.kt @@ -144,11 +144,11 @@ fun JoinConversationViaDeepLinkDialog( }, imeAction = ImeAction.Done, keyboardActions = KeyboardActions(onDone = { keyboardController?.hide() }), - autofillTypes = emptyList(), modifier = Modifier .focusRequester(focusRequester) .padding(bottom = MaterialTheme.wireDimensions.spacing8x) - .testTag("remove device password field") + .testTag("remove device password field"), + autofill = false ) LaunchedEffect(Unit) { // executed only once when showing the dialog focusRequester.requestFocus()