Merge pull request #6649 from vector-im/feature/adm/auth-testing-results

FTUE - Test session feedback
This commit is contained in:
Adam Brown 2022-07-27 12:09:35 +01:00 committed by GitHub
commit 067a030f19
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 78 additions and 21 deletions

1
changelog.d/6620.feature Normal file
View file

@ -0,0 +1 @@
FTUE - Test session feedback

1
changelog.d/6621.feature Normal file
View file

@ -0,0 +1 @@
FTUE - Improved reset password error message

View file

@ -86,6 +86,10 @@ fun Throwable.isInvalidUIAAuth() = this is Failure.ServerError &&
fun Throwable.isHomeserverUnavailable() = this is Failure.NetworkConnection && fun Throwable.isHomeserverUnavailable() = this is Failure.NetworkConnection &&
this.ioException is UnknownHostException 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 * Try to convert to a RegistrationFlowResponse. Return null in the cases it's not possible
*/ */

View file

@ -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.MatrixIdFailure
import org.matrix.android.sdk.api.failure.isInvalidPassword import org.matrix.android.sdk.api.failure.isInvalidPassword
import org.matrix.android.sdk.api.failure.isLimitExceededError 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 org.matrix.android.sdk.api.session.identity.IdentityServiceError
import java.net.HttpURLConnection import java.net.HttpURLConnection
import java.net.SocketTimeoutException import java.net.SocketTimeoutException
@ -105,6 +106,9 @@ class DefaultErrorFormatter @Inject constructor(
throwable.error.message == "Not allowed to join this room" -> { throwable.error.message == "Not allowed to join this room" -> {
stringProvider.getString(R.string.room_error_access_unauthorized) stringProvider.getString(R.string.room_error_access_unauthorized)
} }
throwable.isMissingEmailVerification() -> {
stringProvider.getString(R.string.auth_reset_password_error_unverified)
}
else -> { else -> {
throwable.error.message.takeIf { it.isNotEmpty() } throwable.error.message.takeIf { it.isNotEmpty() }
?: throwable.error.code.takeIf { it.isNotEmpty() } ?: throwable.error.code.takeIf { it.isNotEmpty() }

View file

@ -75,7 +75,11 @@ class FtueAuthCombinedRegisterFragment @Inject constructor() : AbstractSSOFtueAu
setupSubmitButton() setupSubmitButton()
views.createAccountRoot.realignPercentagesToParent() views.createAccountRoot.realignPercentagesToParent()
views.editServerButton.debouncedClicks { viewModel.handle(OnboardingAction.PostViewEvent(OnboardingViewEvents.EditServerSelection)) } 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) { views.createAccountInput.onTextChange(viewLifecycleOwner) {
viewModel.handle(OnboardingAction.ResetSelectedRegistrationUserName) 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() { private fun setupSubmitButton() {
views.createAccountSubmit.setOnClickListener { submit() } views.createAccountSubmit.setOnClickListener { submit() }
views.createAccountInput.clearErrorOnChange(viewLifecycleOwner) views.createAccountInput.clearErrorOnChange(viewLifecycleOwner)
views.createAccountPasswordInput.clearErrorOnChange(viewLifecycleOwner) views.createAccountPasswordInput.clearErrorOnChange(viewLifecycleOwner)
combine(views.createAccountInput.editText().textChanges(), views.createAccountPasswordInput.editText().textChanges()) { account, password -> combine(views.createAccountInput.editText().textChanges(), views.createAccountPasswordInput.editText().textChanges()) { account, password ->
val accountIsValid = account.isNotEmpty() views.createAccountSubmit.isEnabled = canSubmit(account, password)
val passwordIsValid = password.length >= MINIMUM_PASSWORD_LENGTH
views.createAccountSubmit.isEnabled = accountIsValid && passwordIsValid
}.launchIn(viewLifecycleOwner.lifecycleScope) }.launchIn(viewLifecycleOwner.lifecycleScope)
} }

View file

@ -20,18 +20,20 @@ import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.inputmethod.EditorInfo
import im.vector.app.R 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.clearErrorOnChange
import im.vector.app.core.extensions.content import im.vector.app.core.extensions.content
import im.vector.app.core.extensions.editText import im.vector.app.core.extensions.editText
import im.vector.app.core.extensions.realignPercentagesToParent 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.extensions.toReducedUrl
import im.vector.app.core.utils.ensureProtocol import im.vector.app.core.utils.ensureProtocol
import im.vector.app.core.utils.ensureTrailingSlash import im.vector.app.core.utils.ensureTrailingSlash
import im.vector.app.core.utils.openUrlInExternalBrowser import im.vector.app.core.utils.openUrlInExternalBrowser
import im.vector.app.databinding.FragmentFtueServerSelectionCombinedBinding import im.vector.app.databinding.FragmentFtueServerSelectionCombinedBinding
import im.vector.app.features.onboarding.OnboardingAction 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.OnboardingViewEvents
import im.vector.app.features.onboarding.OnboardingViewState import im.vector.app.features.onboarding.OnboardingViewState
import org.matrix.android.sdk.api.failure.isHomeserverUnavailable import org.matrix.android.sdk.api.failure.isHomeserverUnavailable
@ -53,19 +55,19 @@ class FtueAuthCombinedServerSelectionFragment @Inject constructor() : AbstractFt
views.chooseServerToolbar.setNavigationOnClickListener { views.chooseServerToolbar.setNavigationOnClickListener {
viewModel.handle(OnboardingAction.PostViewEvent(OnboardingViewEvents.OnBack)) viewModel.handle(OnboardingAction.PostViewEvent(OnboardingViewEvents.OnBack))
} }
views.chooseServerInput.editText?.setOnEditorActionListener { _, actionId, _ -> views.chooseServerInput.associateContentStateWith(button = views.chooseServerSubmit, enabledPredicate = { canSubmit(it) })
when (actionId) { views.chooseServerInput.setOnImeDoneListener {
EditorInfo.IME_ACTION_DONE -> { if (canSubmit(views.chooseServerInput.content())) {
updateServerUrl() updateServerUrl()
}
} }
false
} }
views.chooseServerGetInTouch.debouncedClicks { openUrlInExternalBrowser(requireContext(), getString(R.string.ftue_ems_url)) } views.chooseServerGetInTouch.debouncedClicks { openUrlInExternalBrowser(requireContext(), getString(R.string.ftue_ems_url)) }
views.chooseServerSubmit.debouncedClicks { updateServerUrl() } views.chooseServerSubmit.debouncedClicks { updateServerUrl() }
views.chooseServerInput.clearErrorOnChange(viewLifecycleOwner) views.chooseServerInput.clearErrorOnChange(viewLifecycleOwner)
} }
private fun canSubmit(url: String) = url.isNotEmpty()
private fun updateServerUrl() { private fun updateServerUrl() {
viewModel.handle(OnboardingAction.HomeServerChange.EditHomeServer(views.chooseServerInput.content().ensureProtocol().ensureTrailingSlash())) viewModel.handle(OnboardingAction.HomeServerChange.EditHomeServer(views.chooseServerInput.content().ensureProtocol().ensureTrailingSlash()))
} }
@ -75,6 +77,14 @@ class FtueAuthCombinedServerSelectionFragment @Inject constructor() : AbstractFt
} }
override fun updateWithState(state: OnboardingViewState) { 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()) { if (views.chooseServerInput.content().isEmpty()) {
val userUrlInput = state.selectedHomeserver.userFacingUrl?.toReducedUrlKeepingSchemaIfInsecure() val userUrlInput = state.selectedHomeserver.userFacingUrl?.toReducedUrlKeepingSchemaIfInsecure()
views.chooseServerInput.editText().setText(userUrlInput) views.chooseServerInput.editText().setText(userUrlInput)

View file

@ -31,6 +31,7 @@ import im.vector.app.core.extensions.setOnImeDoneListener
import im.vector.app.databinding.FragmentFtueResetPasswordInputBinding import im.vector.app.databinding.FragmentFtueResetPasswordInputBinding
import im.vector.app.features.onboarding.OnboardingAction import im.vector.app.features.onboarding.OnboardingAction
import im.vector.app.features.onboarding.OnboardingViewState import im.vector.app.features.onboarding.OnboardingViewState
import org.matrix.android.sdk.api.failure.isMissingEmailVerification
@AndroidEntryPoint @AndroidEntryPoint
class FtueAuthResetPasswordEntryFragment : AbstractFtueAuthFragment<FragmentFtueResetPasswordInputBinding>() { class FtueAuthResetPasswordEntryFragment : AbstractFtueAuthFragment<FragmentFtueResetPasswordInputBinding>() {
@ -61,7 +62,12 @@ class FtueAuthResetPasswordEntryFragment : AbstractFtueAuthFragment<FragmentFtue
} }
override fun onError(throwable: Throwable) { override fun onError(throwable: Throwable) {
views.newPasswordInput.error = errorFormatter.toHumanReadable(throwable) when {
throwable.isMissingEmailVerification() -> super.onError(throwable)
else -> {
views.newPasswordInput.error = errorFormatter.toHumanReadable(throwable)
}
}
} }
override fun updateWithState(state: OnboardingViewState) { override fun updateWithState(state: OnboardingViewState) {

View file

@ -21,7 +21,7 @@ import android.os.Parcelable
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.view.isVisible import androidx.core.view.isInvisible
import com.airbnb.mvrx.args import com.airbnb.mvrx.args
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.utils.colorTerminatingFullStop import im.vector.app.core.utils.colorTerminatingFullStop
@ -63,6 +63,7 @@ class FtueAuthWaitForEmailFragment @Inject constructor(
.colorTerminatingFullStop(ThemeUtils.getColor(requireContext(), R.attr.colorSecondary)) .colorTerminatingFullStop(ThemeUtils.getColor(requireContext(), R.attr.colorSecondary))
views.emailVerificationSubtitle.text = getString(R.string.ftue_auth_email_verification_subtitle, params.email) views.emailVerificationSubtitle.text = getString(R.string.ftue_auth_email_verification_subtitle, params.email)
views.emailVerificationResendEmail.debouncedClicks { views.emailVerificationResendEmail.debouncedClicks {
hideWaitingForVerificationLoading()
viewModel.handle(OnboardingAction.PostRegisterAction(RegisterAction.SendAgainThreePid)) viewModel.handle(OnboardingAction.PostRegisterAction(RegisterAction.SendAgainThreePid))
} }
} }
@ -75,13 +76,21 @@ class FtueAuthWaitForEmailFragment @Inject constructor(
private fun showLoadingIfReturningToScreen() { private fun showLoadingIfReturningToScreen() {
when (inferHasLeftAndReturnedToScreen) { when (inferHasLeftAndReturnedToScreen) {
true -> views.emailVerificationWaiting.isVisible = true true -> showWaitingForVerificationLoading()
false -> { false -> {
inferHasLeftAndReturnedToScreen = true inferHasLeftAndReturnedToScreen = true
} }
} }
} }
private fun hideWaitingForVerificationLoading() {
views.emailVerificationWaiting.isInvisible = true
}
private fun showWaitingForVerificationLoading() {
views.emailVerificationWaiting.isInvisible = false
}
override fun onPause() { override fun onPause() {
super.onPause() super.onPause()
viewModel.handle(OnboardingAction.StopEmailValidationCheck) viewModel.handle(OnboardingAction.StopEmailValidationCheck)

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="71dp"
android:height="70dp"
android:viewportWidth="71"
android:viewportHeight="70">
<path
android:pathData="M39,18C39,19.306 38.165,20.417 37,20.829V25H48C52.418,25 56,28.582 56,33V47C56,51.418 52.418,55 48,55H24C19.582,55 16,51.418 16,47V33C16,28.582 19.582,25 24,25H35V20.829C33.835,20.417 33,19.306 33,18C33,16.343 34.343,15 36,15C37.657,15 39,16.343 39,18ZM47,40C49.209,40 51,38.209 51,36C51,33.791 49.209,32 47,32C44.791,32 43,33.791 43,36C43,38.209 44.791,40 47,40ZM29,36C29,38.209 27.209,40 25,40C22.791,40 21,38.209 21,36C21,33.791 22.791,32 25,32C27.209,32 29,33.791 29,36ZM30.5,47C29.672,47 29,47.672 29,48.5C29,49.328 29.672,50 30.5,50H41.5C42.328,50 43,49.328 43,48.5C43,47.672 42.328,47 41.5,47H30.5Z"
android:fillColor="#ff0000"
android:fillType="evenOdd"/>
</vector>

View file

@ -66,7 +66,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="12dp" android:layout_marginStart="12dp"
android:layout_marginTop="4dp" 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" android:textColor="?vctr_content_secondary"
app:layout_constraintBottom_toTopOf="@id/selectedServerName" app:layout_constraintBottom_toTopOf="@id/selectedServerName"
app:layout_constraintEnd_toStartOf="@id/editServerButton" app:layout_constraintEnd_toStartOf="@id/editServerButton"

View file

@ -38,7 +38,7 @@
android:background="@drawable/circle" android:background="@drawable/circle"
android:backgroundTint="?colorSecondary" android:backgroundTint="?colorSecondary"
android:contentDescription="@null" android:contentDescription="@null"
android:src="@drawable/ic_user_fg" android:src="@drawable/ic_robot"
app:layout_constraintBottom_toTopOf="@id/captchaHeaderTitle" app:layout_constraintBottom_toTopOf="@id/captchaHeaderTitle"
app:layout_constraintEnd_toEndOf="@id/captchaGutterEnd" app:layout_constraintEnd_toEndOf="@id/captchaGutterEnd"
app:layout_constraintHeight_percent="0.10" app:layout_constraintHeight_percent="0.10"

View file

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android" <androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
style="@style/LoginFormScrollView" style="@style/LoginFormScrollView"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="?android:colorBackground" android:background="?android:colorBackground"
@ -76,12 +77,12 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
android:gravity="center" android:gravity="center"
android:text="@string/ftue_auth_choose_server_subtitle"
android:textColor="?vctr_content_secondary" android:textColor="?vctr_content_secondary"
app:layout_constraintBottom_toTopOf="@id/titleContentSpacing" app:layout_constraintBottom_toTopOf="@id/titleContentSpacing"
app:layout_constraintEnd_toEndOf="@id/chooseServerGutterEnd" app:layout_constraintEnd_toEndOf="@id/chooseServerGutterEnd"
app:layout_constraintStart_toStartOf="@id/chooseServerGutterStart" app:layout_constraintStart_toStartOf="@id/chooseServerGutterStart"
app:layout_constraintTop_toBottomOf="@id/chooseServerHeaderTitle" /> app:layout_constraintTop_toBottomOf="@id/chooseServerHeaderTitle"
tools:text="@string/ftue_auth_choose_server_subtitle" />
<Space <Space
android:id="@+id/titleContentSpacing" android:id="@+id/titleContentSpacing"

View file

@ -96,7 +96,7 @@
android:id="@+id/emailVerificationWaiting" android:id="@+id/emailVerificationWaiting"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:visibility="gone" android:visibility="invisible"
app:layout_constraintBottom_toBottomOf="@id/emailVerificationSpace4" app:layout_constraintBottom_toBottomOf="@id/emailVerificationSpace4"
app:layout_constraintEnd_toEndOf="@id/ftueAuthGutterEnd" app:layout_constraintEnd_toEndOf="@id/ftueAuthGutterEnd"
app:layout_constraintStart_toStartOf="@id/ftueAuthGutterStart" app:layout_constraintStart_toStartOf="@id/ftueAuthGutterStart"

View file

@ -524,6 +524,7 @@
<string name="auth_recaptcha_message">This homeserver would like to make sure you are not a robot</string> <string name="auth_recaptcha_message">This homeserver would like to make sure you are not a robot</string>
<string name="auth_reset_password_missing_email">The email address linked to your account must be entered.</string> <string name="auth_reset_password_missing_email">The email address linked to your account must be entered.</string>
<string name="auth_reset_password_error_unauthorized">Failed to verify email address: make sure you clicked the link in the email</string> <string name="auth_reset_password_error_unauthorized">Failed to verify email address: make sure you clicked the link in the email</string>
<string name="auth_reset_password_error_unverified">Email not verified, check your inbox</string>
<string name="auth_accept_policies">"Please review and accept the policies of this homeserver:"</string> <string name="auth_accept_policies">"Please review and accept the policies of this homeserver:"</string>
<!-- Login Screen --> <!-- Login Screen -->
@ -1929,6 +1930,7 @@
<string name="ftue_auth_create_account_username_entry_footer">Others can discover you %s</string> <string name="ftue_auth_create_account_username_entry_footer">Others can discover you %s</string>
<string name="ftue_auth_create_account_password_entry_footer">Must be 8 characters or more</string> <string name="ftue_auth_create_account_password_entry_footer">Must be 8 characters or more</string>
<string name="ftue_auth_create_account_choose_server_header">Where your conversations will live</string> <string name="ftue_auth_create_account_choose_server_header">Where your conversations will live</string>
<string name="ftue_auth_sign_in_choose_server_header">Where your conversations live</string>
<string name="ftue_auth_create_account_sso_section_header">Or</string> <string name="ftue_auth_create_account_sso_section_header">Or</string>
<string name="ftue_auth_create_account_edit_server_selection">Edit</string> <string name="ftue_auth_create_account_edit_server_selection">Edit</string>
@ -1936,6 +1938,7 @@
<string name="ftue_auth_choose_server_title">Select your server</string> <string name="ftue_auth_choose_server_title">Select your server</string>
<string name="ftue_auth_choose_server_subtitle">What is the address of your server? This is like a home for all your data</string> <string name="ftue_auth_choose_server_subtitle">What is the address of your server? This is like a home for all your data</string>
<string name="ftue_auth_choose_server_sign_in_subtitle">What is the address of your server?</string>
<string name="ftue_auth_choose_server_entry_hint">Server URL</string> <string name="ftue_auth_choose_server_entry_hint">Server URL</string>
<string name="ftue_auth_choose_server_ems_title">Want to host your own server?</string> <string name="ftue_auth_choose_server_ems_title">Want to host your own server?</string>
@ -1970,9 +1973,9 @@
<string name="ftue_auth_phone_confirmation_subtitle">A code was sent to %s</string> <string name="ftue_auth_phone_confirmation_subtitle">A code was sent to %s</string>
<string name="ftue_auth_phone_confirmation_resend_code">Resend code</string> <string name="ftue_auth_phone_confirmation_resend_code">Resend code</string>
<string name="ftue_auth_email_verification_title">Check your email to verify.</string> <string name="ftue_auth_email_verification_title">Verify your email</string>
<!-- Note for translators, %s is the users email address --> <!-- Note for translators, %s is the users email address -->
<string name="ftue_auth_email_verification_subtitle">To confirm your email, tap the button in the email we just sent to %s</string> <string name="ftue_auth_email_verification_subtitle">Follow the instructions sent to %s</string>
<string name="ftue_auth_email_verification_footer">Did not receive an email?</string> <string name="ftue_auth_email_verification_footer">Did not receive an email?</string>
<string name="ftue_auth_email_resend_email">Resend email</string> <string name="ftue_auth_email_resend_email">Resend email</string>
<string name="ftue_auth_forgot_password">Forgot password</string> <string name="ftue_auth_forgot_password">Forgot password</string>