diff --git a/changelog.d/6620.feature b/changelog.d/6620.feature new file mode 100644 index 0000000000..ad192edd5c --- /dev/null +++ b/changelog.d/6620.feature @@ -0,0 +1 @@ +FTUE - Test session feedback diff --git a/changelog.d/6621.feature b/changelog.d/6621.feature new file mode 100644 index 0000000000..b893c968b4 --- /dev/null +++ b/changelog.d/6621.feature @@ -0,0 +1 @@ +FTUE - Improved reset password error message diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Extensions.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Extensions.kt index d3cc8fc8e4..6e198fb98c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Extensions.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Extensions.kt @@ -86,6 +86,10 @@ fun Throwable.isInvalidUIAAuth() = this is Failure.ServerError && fun Throwable.isHomeserverUnavailable() = this is Failure.NetworkConnection && this.ioException is UnknownHostException +fun Throwable.isMissingEmailVerification() = this is Failure.ServerError && + error.code == MatrixError.M_UNAUTHORIZED && + error.message == "Unable to get validated threepid" + /** * Try to convert to a RegistrationFlowResponse. Return null in the cases it's not possible */ diff --git a/vector/src/main/java/im/vector/app/core/error/ErrorFormatter.kt b/vector/src/main/java/im/vector/app/core/error/ErrorFormatter.kt index 8fbd89a6c1..d9a08bd81a 100644 --- a/vector/src/main/java/im/vector/app/core/error/ErrorFormatter.kt +++ b/vector/src/main/java/im/vector/app/core/error/ErrorFormatter.kt @@ -25,6 +25,7 @@ import org.matrix.android.sdk.api.failure.MatrixError import org.matrix.android.sdk.api.failure.MatrixIdFailure import org.matrix.android.sdk.api.failure.isInvalidPassword import org.matrix.android.sdk.api.failure.isLimitExceededError +import org.matrix.android.sdk.api.failure.isMissingEmailVerification import org.matrix.android.sdk.api.session.identity.IdentityServiceError import java.net.HttpURLConnection import java.net.SocketTimeoutException @@ -105,6 +106,9 @@ class DefaultErrorFormatter @Inject constructor( throwable.error.message == "Not allowed to join this room" -> { stringProvider.getString(R.string.room_error_access_unauthorized) } + throwable.isMissingEmailVerification() -> { + stringProvider.getString(R.string.auth_reset_password_error_unverified) + } else -> { throwable.error.message.takeIf { it.isNotEmpty() } ?: throwable.error.code.takeIf { it.isNotEmpty() } diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedRegisterFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedRegisterFragment.kt index 639045b5c0..c69706a17b 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedRegisterFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedRegisterFragment.kt @@ -75,7 +75,11 @@ class FtueAuthCombinedRegisterFragment @Inject constructor() : AbstractSSOFtueAu setupSubmitButton() views.createAccountRoot.realignPercentagesToParent() views.editServerButton.debouncedClicks { viewModel.handle(OnboardingAction.PostViewEvent(OnboardingViewEvents.EditServerSelection)) } - views.createAccountPasswordInput.setOnImeDoneListener { submit() } + views.createAccountPasswordInput.setOnImeDoneListener { + if (canSubmit(views.createAccountInput.content(), views.createAccountPasswordInput.content())) { + submit() + } + } views.createAccountInput.onTextChange(viewLifecycleOwner) { viewModel.handle(OnboardingAction.ResetSelectedRegistrationUserName) @@ -87,15 +91,19 @@ class FtueAuthCombinedRegisterFragment @Inject constructor() : AbstractSSOFtueAu } } + private fun canSubmit(account: CharSequence, password: CharSequence): Boolean { + val accountIsValid = account.isNotEmpty() + val passwordIsValid = password.length >= MINIMUM_PASSWORD_LENGTH + return accountIsValid && passwordIsValid + } + private fun setupSubmitButton() { views.createAccountSubmit.setOnClickListener { submit() } views.createAccountInput.clearErrorOnChange(viewLifecycleOwner) views.createAccountPasswordInput.clearErrorOnChange(viewLifecycleOwner) combine(views.createAccountInput.editText().textChanges(), views.createAccountPasswordInput.editText().textChanges()) { account, password -> - val accountIsValid = account.isNotEmpty() - val passwordIsValid = password.length >= MINIMUM_PASSWORD_LENGTH - views.createAccountSubmit.isEnabled = accountIsValid && passwordIsValid + views.createAccountSubmit.isEnabled = canSubmit(account, password) }.launchIn(viewLifecycleOwner.lifecycleScope) } diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedServerSelectionFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedServerSelectionFragment.kt index bc44a7dbdb..749aac2898 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedServerSelectionFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedServerSelectionFragment.kt @@ -20,18 +20,20 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.view.inputmethod.EditorInfo import im.vector.app.R +import im.vector.app.core.extensions.associateContentStateWith import im.vector.app.core.extensions.clearErrorOnChange import im.vector.app.core.extensions.content import im.vector.app.core.extensions.editText import im.vector.app.core.extensions.realignPercentagesToParent +import im.vector.app.core.extensions.setOnImeDoneListener import im.vector.app.core.extensions.toReducedUrl import im.vector.app.core.utils.ensureProtocol import im.vector.app.core.utils.ensureTrailingSlash import im.vector.app.core.utils.openUrlInExternalBrowser import im.vector.app.databinding.FragmentFtueServerSelectionCombinedBinding import im.vector.app.features.onboarding.OnboardingAction +import im.vector.app.features.onboarding.OnboardingFlow import im.vector.app.features.onboarding.OnboardingViewEvents import im.vector.app.features.onboarding.OnboardingViewState import org.matrix.android.sdk.api.failure.isHomeserverUnavailable @@ -53,19 +55,19 @@ class FtueAuthCombinedServerSelectionFragment @Inject constructor() : AbstractFt views.chooseServerToolbar.setNavigationOnClickListener { viewModel.handle(OnboardingAction.PostViewEvent(OnboardingViewEvents.OnBack)) } - views.chooseServerInput.editText?.setOnEditorActionListener { _, actionId, _ -> - when (actionId) { - EditorInfo.IME_ACTION_DONE -> { - updateServerUrl() - } + views.chooseServerInput.associateContentStateWith(button = views.chooseServerSubmit, enabledPredicate = { canSubmit(it) }) + views.chooseServerInput.setOnImeDoneListener { + if (canSubmit(views.chooseServerInput.content())) { + updateServerUrl() } - false } views.chooseServerGetInTouch.debouncedClicks { openUrlInExternalBrowser(requireContext(), getString(R.string.ftue_ems_url)) } views.chooseServerSubmit.debouncedClicks { updateServerUrl() } views.chooseServerInput.clearErrorOnChange(viewLifecycleOwner) } + private fun canSubmit(url: String) = url.isNotEmpty() + private fun updateServerUrl() { viewModel.handle(OnboardingAction.HomeServerChange.EditHomeServer(views.chooseServerInput.content().ensureProtocol().ensureTrailingSlash())) } @@ -75,6 +77,14 @@ class FtueAuthCombinedServerSelectionFragment @Inject constructor() : AbstractFt } override fun updateWithState(state: OnboardingViewState) { + views.chooseServerHeaderSubtitle.setText( + when (state.onboardingFlow) { + OnboardingFlow.SignIn -> R.string.ftue_auth_choose_server_sign_in_subtitle + OnboardingFlow.SignUp -> R.string.ftue_auth_choose_server_subtitle + else -> throw IllegalStateException("Invalid flow state") + } + ) + if (views.chooseServerInput.content().isEmpty()) { val userUrlInput = state.selectedHomeserver.userFacingUrl?.toReducedUrlKeepingSchemaIfInsecure() views.chooseServerInput.editText().setText(userUrlInput) diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthResetPasswordEntryFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthResetPasswordEntryFragment.kt index 6282fded61..61826352bf 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthResetPasswordEntryFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthResetPasswordEntryFragment.kt @@ -31,6 +31,7 @@ import im.vector.app.core.extensions.setOnImeDoneListener import im.vector.app.databinding.FragmentFtueResetPasswordInputBinding import im.vector.app.features.onboarding.OnboardingAction import im.vector.app.features.onboarding.OnboardingViewState +import org.matrix.android.sdk.api.failure.isMissingEmailVerification @AndroidEntryPoint class FtueAuthResetPasswordEntryFragment : AbstractFtueAuthFragment() { @@ -61,7 +62,12 @@ class FtueAuthResetPasswordEntryFragment : AbstractFtueAuthFragment super.onError(throwable) + else -> { + views.newPasswordInput.error = errorFormatter.toHumanReadable(throwable) + } + } } override fun updateWithState(state: OnboardingViewState) { diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthWaitForEmailFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthWaitForEmailFragment.kt index 4649c7c799..b1a3258cae 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthWaitForEmailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthWaitForEmailFragment.kt @@ -21,7 +21,7 @@ import android.os.Parcelable import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import androidx.core.view.isVisible +import androidx.core.view.isInvisible import com.airbnb.mvrx.args import im.vector.app.R import im.vector.app.core.utils.colorTerminatingFullStop @@ -63,6 +63,7 @@ class FtueAuthWaitForEmailFragment @Inject constructor( .colorTerminatingFullStop(ThemeUtils.getColor(requireContext(), R.attr.colorSecondary)) views.emailVerificationSubtitle.text = getString(R.string.ftue_auth_email_verification_subtitle, params.email) views.emailVerificationResendEmail.debouncedClicks { + hideWaitingForVerificationLoading() viewModel.handle(OnboardingAction.PostRegisterAction(RegisterAction.SendAgainThreePid)) } } @@ -75,13 +76,21 @@ class FtueAuthWaitForEmailFragment @Inject constructor( private fun showLoadingIfReturningToScreen() { when (inferHasLeftAndReturnedToScreen) { - true -> views.emailVerificationWaiting.isVisible = true + true -> showWaitingForVerificationLoading() false -> { inferHasLeftAndReturnedToScreen = true } } } + private fun hideWaitingForVerificationLoading() { + views.emailVerificationWaiting.isInvisible = true + } + + private fun showWaitingForVerificationLoading() { + views.emailVerificationWaiting.isInvisible = false + } + override fun onPause() { super.onPause() viewModel.handle(OnboardingAction.StopEmailValidationCheck) diff --git a/vector/src/main/res/drawable/ic_robot.xml b/vector/src/main/res/drawable/ic_robot.xml new file mode 100644 index 0000000000..cdc358f7eb --- /dev/null +++ b/vector/src/main/res/drawable/ic_robot.xml @@ -0,0 +1,10 @@ + + + diff --git a/vector/src/main/res/layout/fragment_ftue_combined_login.xml b/vector/src/main/res/layout/fragment_ftue_combined_login.xml index b23ea9d7cc..9533ab29fc 100644 --- a/vector/src/main/res/layout/fragment_ftue_combined_login.xml +++ b/vector/src/main/res/layout/fragment_ftue_combined_login.xml @@ -66,7 +66,7 @@ android:layout_height="wrap_content" android:layout_marginStart="12dp" android:layout_marginTop="4dp" - android:text="@string/ftue_auth_create_account_choose_server_header" + android:text="@string/ftue_auth_sign_in_choose_server_header" android:textColor="?vctr_content_secondary" app:layout_constraintBottom_toTopOf="@id/selectedServerName" app:layout_constraintEnd_toStartOf="@id/editServerButton" diff --git a/vector/src/main/res/layout/fragment_ftue_login_captcha.xml b/vector/src/main/res/layout/fragment_ftue_login_captcha.xml index becb745305..2f6970c785 100644 --- a/vector/src/main/res/layout/fragment_ftue_login_captcha.xml +++ b/vector/src/main/res/layout/fragment_ftue_login_captcha.xml @@ -38,7 +38,7 @@ android:background="@drawable/circle" android:backgroundTint="?colorSecondary" android:contentDescription="@null" - android:src="@drawable/ic_user_fg" + android:src="@drawable/ic_robot" app:layout_constraintBottom_toTopOf="@id/captchaHeaderTitle" app:layout_constraintEnd_toEndOf="@id/captchaGutterEnd" app:layout_constraintHeight_percent="0.10" diff --git a/vector/src/main/res/layout/fragment_ftue_server_selection_combined.xml b/vector/src/main/res/layout/fragment_ftue_server_selection_combined.xml index afe7a06183..f1944e25ad 100644 --- a/vector/src/main/res/layout/fragment_ftue_server_selection_combined.xml +++ b/vector/src/main/res/layout/fragment_ftue_server_selection_combined.xml @@ -1,6 +1,7 @@ + app:layout_constraintTop_toBottomOf="@id/chooseServerHeaderTitle" + tools:text="@string/ftue_auth_choose_server_subtitle" /> This homeserver would like to make sure you are not a robot The email address linked to your account must be entered. Failed to verify email address: make sure you clicked the link in the email + Email not verified, check your inbox "Please review and accept the policies of this homeserver:" @@ -1929,6 +1930,7 @@ Others can discover you %s Must be 8 characters or more Where your conversations will live + Where your conversations live Or Edit @@ -1936,6 +1938,7 @@ Select your server What is the address of your server? This is like a home for all your data + What is the address of your server? Server URL Want to host your own server? @@ -1970,9 +1973,9 @@ A code was sent to %s Resend code - Check your email to verify. + Verify your email - To confirm your email, tap the button in the email we just sent to %s + Follow the instructions sent to %s Did not receive an email? Resend email Forgot password