From 2a3e90acf3dc8f0ff0ba3a8053332d53b9581f8a Mon Sep 17 00:00:00 2001 From: "omer.habib" Date: Mon, 24 Jun 2024 22:55:27 +0500 Subject: [PATCH 1/3] fix: update keyboard visibility and imeAction - Hide keyboard on logistration screens - update imeAction for long InputEditFields -LEARNER-10032 --- .../auth/presentation/restore/RestorePasswordFragment.kt | 4 ++++ .../openedx/auth/presentation/signin/compose/SignInView.kt | 5 +++++ .../openedx/auth/presentation/signup/compose/SignUpView.kt | 7 ++++++- .../presentation/threads/DiscussionAddThreadFragment.kt | 2 +- .../profile/presentation/edit/EditProfileFragment.kt | 2 +- 5 files changed, 17 insertions(+), 3 deletions(-) diff --git a/auth/src/main/java/org/openedx/auth/presentation/restore/RestorePasswordFragment.kt b/auth/src/main/java/org/openedx/auth/presentation/restore/RestorePasswordFragment.kt index 18cf169bc..41fe23230 100644 --- a/auth/src/main/java/org/openedx/auth/presentation/restore/RestorePasswordFragment.kt +++ b/auth/src/main/java/org/openedx/auth/presentation/restore/RestorePasswordFragment.kt @@ -40,6 +40,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.platform.ViewCompositionStrategy import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.painterResource @@ -130,6 +131,7 @@ private fun RestorePasswordScreen( var email by rememberSaveable { mutableStateOf("") } + val keyboardController = LocalSoftwareKeyboardController.current Scaffold( scaffoldState = scaffoldState, @@ -272,6 +274,7 @@ private fun RestorePasswordScreen( }, imeAction = ImeAction.Done, keyboardActions = { + keyboardController?.hide() it.clearFocus() onRestoreButtonClick(email) } @@ -292,6 +295,7 @@ private fun RestorePasswordScreen( modifier = buttonWidth.testTag("btn_reset_password"), text = stringResource(id = authR.string.auth_reset_password), onClick = { + keyboardController?.hide() onRestoreButtonClick(email) } ) diff --git a/auth/src/main/java/org/openedx/auth/presentation/signin/compose/SignInView.kt b/auth/src/main/java/org/openedx/auth/presentation/signin/compose/SignInView.kt index 783a60a99..fc7fb5ae6 100644 --- a/auth/src/main/java/org/openedx/auth/presentation/signin/compose/SignInView.kt +++ b/auth/src/main/java/org/openedx/auth/presentation/signin/compose/SignInView.kt @@ -40,6 +40,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource @@ -220,6 +221,7 @@ private fun AuthForm( ) { var login by rememberSaveable { mutableStateOf("") } var password by rememberSaveable { mutableStateOf("") } + val keyboardController = LocalSoftwareKeyboardController.current Column(horizontalAlignment = Alignment.CenterHorizontally) { LoginTextField( @@ -239,6 +241,7 @@ private fun AuthForm( password = it }, onPressDone = { + keyboardController?.hide() onEvent(AuthEvent.SignIn(login = login, password = password)) } ) @@ -282,6 +285,7 @@ private fun AuthForm( textColor = MaterialTheme.appColors.primaryButtonText, backgroundColor = MaterialTheme.appColors.secondaryButtonBackground, onClick = { + keyboardController?.hide() onEvent(AuthEvent.SignIn(login = login, password = password)) } ) @@ -294,6 +298,7 @@ private fun AuthForm( isMicrosoftAuthEnabled = state.isMicrosoftAuthEnabled, isSignIn = true, ) { + keyboardController?.hide() onEvent(AuthEvent.SocialSignIn(it)) } } diff --git a/auth/src/main/java/org/openedx/auth/presentation/signup/compose/SignUpView.kt b/auth/src/main/java/org/openedx/auth/presentation/signup/compose/SignUpView.kt index 2872c579b..e1e31c7b8 100644 --- a/auth/src/main/java/org/openedx/auth/presentation/signup/compose/SignUpView.kt +++ b/auth/src/main/java/org/openedx/auth/presentation/signup/compose/SignUpView.kt @@ -442,6 +442,7 @@ internal fun SignUpView( textColor = MaterialTheme.appColors.primaryButtonText, backgroundColor = MaterialTheme.appColors.secondaryButtonBackground, onClick = { + keyboardController?.hide() showErrorMap.clear() onRegisterClick(AuthType.PASSWORD) } @@ -455,6 +456,7 @@ internal fun SignUpView( isMicrosoftAuthEnabled = uiState.isMicrosoftAuthEnabled, isSignIn = false, ) { + keyboardController?.hide() onRegisterClick(it) } } @@ -478,7 +480,10 @@ private fun RegistrationScreenPreview() { SignUpView( windowSize = WindowSize(WindowType.Compact, WindowType.Compact), uiState = SignUpUIState( - allFields = listOf(field, field, field.copy(required = false)), + allFields = listOf(field), + requiredFields = listOf(field, field), + optionalFields = listOf(field, field), + agreementFields = listOf(field), ), uiMessage = null, onBackClick = {}, diff --git a/discussion/src/main/java/org/openedx/discussion/presentation/threads/DiscussionAddThreadFragment.kt b/discussion/src/main/java/org/openedx/discussion/presentation/threads/DiscussionAddThreadFragment.kt index 416140f1e..a211e10b3 100644 --- a/discussion/src/main/java/org/openedx/discussion/presentation/threads/DiscussionAddThreadFragment.kt +++ b/discussion/src/main/java/org/openedx/discussion/presentation/threads/DiscussionAddThreadFragment.kt @@ -395,7 +395,7 @@ private fun DiscussionAddThreadScreen( ), isSingleLine = false, withRequiredMark = true, - imeAction = ImeAction.Done, + imeAction = ImeAction.Default, keyboardActions = { focusManager -> focusManager.clearFocus() keyboardController?.hide() diff --git a/profile/src/main/java/org/openedx/profile/presentation/edit/EditProfileFragment.kt b/profile/src/main/java/org/openedx/profile/presentation/edit/EditProfileFragment.kt index 5fc9e9a78..3800d23ac 100644 --- a/profile/src/main/java/org/openedx/profile/presentation/edit/EditProfileFragment.kt +++ b/profile/src/main/java/org/openedx/profile/presentation/edit/EditProfileFragment.kt @@ -1047,7 +1047,7 @@ private fun InputEditField( }, keyboardOptions = KeyboardOptions.Default.copy( keyboardType = keyboardType, - imeAction = ImeAction.Done + imeAction = ImeAction.Default ), keyboardActions = KeyboardActions { keyboardController?.hide() From 01a154d425ebd37e53801785c13cce3e8e371f3f Mon Sep 17 00:00:00 2001 From: "omer.habib" Date: Sat, 29 Jun 2024 03:20:31 +0500 Subject: [PATCH 2/3] fix: Added error Text for editable fields on SignIn Screen - Add error text in case of empty fields on sign in Screen fix: LEARNER-10032 --- .../restore/RestorePasswordFragment.kt | 24 +++++++---- .../presentation/signin/compose/SignInView.kt | 42 ++++++++++++++++--- .../openedx/auth/presentation/ui/AuthUI.kt | 32 ++++++++++---- auth/src/main/res/values/strings.xml | 3 ++ 4 files changed, 81 insertions(+), 20 deletions(-) diff --git a/auth/src/main/java/org/openedx/auth/presentation/restore/RestorePasswordFragment.kt b/auth/src/main/java/org/openedx/auth/presentation/restore/RestorePasswordFragment.kt index 41fe23230..fa459d756 100644 --- a/auth/src/main/java/org/openedx/auth/presentation/restore/RestorePasswordFragment.kt +++ b/auth/src/main/java/org/openedx/auth/presentation/restore/RestorePasswordFragment.kt @@ -128,9 +128,8 @@ private fun RestorePasswordScreen( ) { val scaffoldState = rememberScaffoldState() val scrollState = rememberScrollState() - var email by rememberSaveable { - mutableStateOf("") - } + var email by rememberSaveable { mutableStateOf("") } + var isEmailError by rememberSaveable { mutableStateOf(false) } val keyboardController = LocalSoftwareKeyboardController.current Scaffold( @@ -271,13 +270,20 @@ private fun RestorePasswordScreen( description = stringResource(id = authR.string.auth_example_email), onValueChanged = { email = it + isEmailError = email.isEmpty() }, imeAction = ImeAction.Done, keyboardActions = { keyboardController?.hide() - it.clearFocus() - onRestoreButtonClick(email) - } + if (email.isNotEmpty()) { + it.clearFocus() + onRestoreButtonClick(email) + } else { + isEmailError = email.isEmpty() + } + }, + isError = isEmailError, + errorMessages = stringResource(id = authR.string.auth_error_empty_email) ) Spacer(Modifier.height(50.dp)) if (uiState == RestorePasswordUIState.Loading) { @@ -296,7 +302,11 @@ private fun RestorePasswordScreen( text = stringResource(id = authR.string.auth_reset_password), onClick = { keyboardController?.hide() - onRestoreButtonClick(email) + if (email.isNotEmpty()) { + onRestoreButtonClick(email) + } else { + isEmailError = email.isEmpty() + } } ) } diff --git a/auth/src/main/java/org/openedx/auth/presentation/signin/compose/SignInView.kt b/auth/src/main/java/org/openedx/auth/presentation/signin/compose/SignInView.kt index fc7fb5ae6..bce426ff4 100644 --- a/auth/src/main/java/org/openedx/auth/presentation/signin/compose/SignInView.kt +++ b/auth/src/main/java/org/openedx/auth/presentation/signin/compose/SignInView.kt @@ -222,6 +222,9 @@ private fun AuthForm( var login by rememberSaveable { mutableStateOf("") } var password by rememberSaveable { mutableStateOf("") } val keyboardController = LocalSoftwareKeyboardController.current + var isEmailError by rememberSaveable { mutableStateOf(false) } + var isPasswordError by rememberSaveable { mutableStateOf(false) } + Column(horizontalAlignment = Alignment.CenterHorizontally) { LoginTextField( @@ -231,7 +234,11 @@ private fun AuthForm( description = stringResource(id = R.string.auth_enter_email_username), onValueChanged = { login = it - }) + isEmailError = login.isEmpty() + }, + isError = isEmailError, + errorMessages = stringResource(id = R.string.auth_error_empty_username_email) + ) Spacer(modifier = Modifier.height(18.dp)) PasswordTextField( @@ -239,11 +246,18 @@ private fun AuthForm( .fillMaxWidth(), onValueChanged = { password = it + isPasswordError = password.isEmpty() }, onPressDone = { keyboardController?.hide() - onEvent(AuthEvent.SignIn(login = login, password = password)) - } + if (password.isNotEmpty()) { + onEvent(AuthEvent.SignIn(login = login, password = password)) + } else { + isEmailError = login.isEmpty() + isPasswordError = password.isEmpty() + } + }, + isError = isPasswordError, ) Row( @@ -286,7 +300,12 @@ private fun AuthForm( backgroundColor = MaterialTheme.appColors.secondaryButtonBackground, onClick = { keyboardController?.hide() - onEvent(AuthEvent.SignIn(login = login, password = password)) + if (login.isNotEmpty() && password.isNotEmpty()) { + onEvent(AuthEvent.SignIn(login = login, password = password)) + } else { + isEmailError = login.isEmpty() + isPasswordError = password.isEmpty() + } } ) } @@ -308,6 +327,7 @@ private fun AuthForm( @Composable private fun PasswordTextField( modifier: Modifier = Modifier, + isError: Boolean, onValueChanged: (String) -> Unit, onPressDone: () -> Unit, ) { @@ -366,9 +386,21 @@ private fun PasswordTextField( focusManager.clearFocus() onPressDone() }, + isError = isError, textStyle = MaterialTheme.appTypography.bodyMedium, - singleLine = true + singleLine = true, ) + if (isError) { + Text( + modifier = Modifier + .testTag("txt_password_error") + .fillMaxWidth() + .padding(top = 4.dp), + text = stringResource(id = R.string.auth_error_empty_password), + style = MaterialTheme.appTypography.bodySmall, + color = MaterialTheme.appColors.error, + ) + } } @Preview(uiMode = UI_MODE_NIGHT_NO) diff --git a/auth/src/main/java/org/openedx/auth/presentation/ui/AuthUI.kt b/auth/src/main/java/org/openedx/auth/presentation/ui/AuthUI.kt index 16d75492b..9f75a2478 100644 --- a/auth/src/main/java/org/openedx/auth/presentation/ui/AuthUI.kt +++ b/auth/src/main/java/org/openedx/auth/presentation/ui/AuthUI.kt @@ -12,6 +12,7 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions @@ -69,14 +70,15 @@ fun RequiredFields( showErrorMap: MutableMap, selectableNamesMap: MutableMap, onFieldUpdated: (String, String) -> Unit, - onSelectClick: (String, RegistrationField, List) -> Unit + onSelectClick: (String, RegistrationField, List) -> Unit, ) { fields.forEach { field -> when (field.type) { RegistrationFieldType.TEXT, RegistrationFieldType.EMAIL, RegistrationFieldType.CONFIRM_EMAIL, - RegistrationFieldType.PASSWORD -> { + RegistrationFieldType.PASSWORD, + -> { InputRegistrationField( modifier = Modifier.fillMaxWidth(), isErrorShown = showErrorMap[field.name] ?: true, @@ -232,9 +234,11 @@ fun LoginTextField( modifier: Modifier = Modifier, title: String, description: String, + isError: Boolean = false, + errorMessages: String = "", onValueChanged: (String) -> Unit, imeAction: ImeAction = ImeAction.Next, - keyboardActions: (FocusManager) -> Unit = { it.moveFocus(FocusDirection.Down) } + keyboardActions: (FocusManager) -> Unit = { it.moveFocus(FocusDirection.Down) }, ) { var loginTextFieldValue by rememberSaveable(stateSaver = TextFieldValue.Saver) { mutableStateOf( @@ -281,8 +285,20 @@ fun LoginTextField( }, textStyle = MaterialTheme.appTypography.bodyMedium, singleLine = true, - modifier = modifier.testTag("tf_email") + modifier = modifier.testTag("tf_email"), + isError = isError ) + if (isError) { + Text( + modifier = Modifier + .testTag("txt_email_error") + .fillMaxWidth() + .padding(top = 4.dp), + text = errorMessages, + style = MaterialTheme.appTypography.bodySmall, + color = MaterialTheme.appColors.error, + ) + } } @Composable @@ -290,7 +306,7 @@ fun InputRegistrationField( modifier: Modifier, isErrorShown: Boolean, registrationField: RegistrationField, - onValueChanged: (String, String, Boolean) -> Unit + onValueChanged: (String, String, Boolean) -> Unit, ) { var inputRegistrationFieldValue by rememberSaveable { mutableStateOf(registrationField.placeholder) @@ -401,7 +417,7 @@ fun SelectableRegisterField( registrationField: RegistrationField, isErrorShown: Boolean, initialValue: String, - onClick: (String, List) -> Unit + onClick: (String, List) -> Unit, ) { val helperTextColor = if (registrationField.errorInstructions.isEmpty()) { MaterialTheme.appColors.textSecondary @@ -489,7 +505,7 @@ fun SelectableRegisterField( fun ExpandableText( modifier: Modifier = Modifier, isExpanded: Boolean, - onClick: (Boolean) -> Unit + onClick: (Boolean) -> Unit, ) { val transitionState = remember { MutableTransitionState(isExpanded).apply { @@ -537,7 +553,7 @@ fun ExpandableText( @Composable internal fun PasswordVisibilityIcon( isPasswordVisible: Boolean, - onClick: () -> Unit + onClick: () -> Unit, ) { val (image, description) = if (isPasswordVisible) { Icons.Filled.VisibilityOff to stringResource(R.string.auth_accessibility_hide_password) diff --git a/auth/src/main/res/values/strings.xml b/auth/src/main/res/values/strings.xml index 642185915..49a8fb68e 100644 --- a/auth/src/main/res/values/strings.xml +++ b/auth/src/main/res/values/strings.xml @@ -22,7 +22,10 @@ We have sent a password recover instructions to your email %s username@domain.com Enter email or username + Please enter your username or e-mail address and try again. + Please enter your e-mail address and try again. Enter password + Please enter your password and try again. Create an account to start learning today! Complete your registration Sign in with Google From 0f80bf67536c33f6a0b6470a8013a6299f6b0d18 Mon Sep 17 00:00:00 2001 From: "omer.habib" Date: Wed, 3 Jul 2024 23:42:43 +0500 Subject: [PATCH 3/3] fix: Address PR comments --- .../auth/presentation/restore/RestorePasswordFragment.kt | 2 +- .../openedx/auth/presentation/signin/compose/SignInView.kt | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/auth/src/main/java/org/openedx/auth/presentation/restore/RestorePasswordFragment.kt b/auth/src/main/java/org/openedx/auth/presentation/restore/RestorePasswordFragment.kt index fa459d756..84d2d584e 100644 --- a/auth/src/main/java/org/openedx/auth/presentation/restore/RestorePasswordFragment.kt +++ b/auth/src/main/java/org/openedx/auth/presentation/restore/RestorePasswordFragment.kt @@ -270,7 +270,7 @@ private fun RestorePasswordScreen( description = stringResource(id = authR.string.auth_example_email), onValueChanged = { email = it - isEmailError = email.isEmpty() + isEmailError = false }, imeAction = ImeAction.Done, keyboardActions = { diff --git a/auth/src/main/java/org/openedx/auth/presentation/signin/compose/SignInView.kt b/auth/src/main/java/org/openedx/auth/presentation/signin/compose/SignInView.kt index bce426ff4..cb77faa37 100644 --- a/auth/src/main/java/org/openedx/auth/presentation/signin/compose/SignInView.kt +++ b/auth/src/main/java/org/openedx/auth/presentation/signin/compose/SignInView.kt @@ -225,7 +225,6 @@ private fun AuthForm( var isEmailError by rememberSaveable { mutableStateOf(false) } var isPasswordError by rememberSaveable { mutableStateOf(false) } - Column(horizontalAlignment = Alignment.CenterHorizontally) { LoginTextField( modifier = Modifier @@ -234,7 +233,7 @@ private fun AuthForm( description = stringResource(id = R.string.auth_enter_email_username), onValueChanged = { login = it - isEmailError = login.isEmpty() + isEmailError = false }, isError = isEmailError, errorMessages = stringResource(id = R.string.auth_error_empty_username_email) @@ -246,7 +245,7 @@ private fun AuthForm( .fillMaxWidth(), onValueChanged = { password = it - isPasswordError = password.isEmpty() + isPasswordError = false }, onPressDone = { keyboardController?.hide()