diff --git a/changelog.d/6622.feature b/changelog.d/6622.feature new file mode 100644 index 0000000000..b3c8791ff0 --- /dev/null +++ b/changelog.d/6622.feature @@ -0,0 +1 @@ +FTUE - Allows the email address to be changed during the verification process diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewEvents.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewEvents.kt index ea6981a2b5..bbbf13fba9 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewEvents.kt @@ -53,7 +53,7 @@ sealed class OnboardingViewEvents : VectorViewEvents { object OnResetPasswordBreakerConfirmed : OnboardingViewEvents() object OnResetPasswordComplete : OnboardingViewEvents() - data class OnSendEmailSuccess(val email: String) : OnboardingViewEvents() + data class OnSendEmailSuccess(val email: String, val isRestoredSession: Boolean) : OnboardingViewEvents() data class OnSendMsisdnSuccess(val msisdn: String) : OnboardingViewEvents() data class OnWebLoginError(val errorCode: Int, val description: String, val failingUrl: String) : OnboardingViewEvents() diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt index 52c32d88e4..6cadb4308a 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt @@ -348,7 +348,10 @@ class OnboardingViewModel @AssistedInject constructor( overrideNextStage?.invoke() ?: _viewEvents.post(OnboardingViewEvents.DisplayStartRegistration) } RegistrationActionHandler.Result.UnsupportedStage -> _viewEvents.post(OnboardingViewEvents.DisplayRegistrationFallback) - is RegistrationActionHandler.Result.SendEmailSuccess -> _viewEvents.post(OnboardingViewEvents.OnSendEmailSuccess(it.email)) + is RegistrationActionHandler.Result.SendEmailSuccess -> { + _viewEvents.post(OnboardingViewEvents.OnSendEmailSuccess(it.email, isRestoredSession = false)) + setState { copy(registrationState = registrationState.copy(email = it.email)) } + } is RegistrationActionHandler.Result.SendMsisdnSuccess -> _viewEvents.post(OnboardingViewEvents.OnSendMsisdnSuccess(it.msisdn.msisdn)) is RegistrationActionHandler.Result.Error -> _viewEvents.post(OnboardingViewEvents.Failure(it.cause)) RegistrationActionHandler.Result.MissingNextStage -> { @@ -412,8 +415,8 @@ class OnboardingViewModel @AssistedInject constructor( authenticationService.cancelPendingLoginOrRegistration() setState { copy( - isLoading = false, - registrationState = RegistrationState(), + isLoading = false, + registrationState = RegistrationState(), ) } } @@ -486,7 +489,7 @@ class OnboardingViewModel @AssistedInject constructor( try { if (registrationWizard.isRegistrationStarted()) { currentThreePid?.let { - handle(OnboardingAction.PostViewEvent(OnboardingViewEvents.OnSendEmailSuccess(it))) + handle(OnboardingAction.PostViewEvent(OnboardingViewEvents.OnSendEmailSuccess(it, isRestoredSession = true))) } } } catch (e: Throwable) { diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewState.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewState.kt index fe2134618d..99678ea5c1 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewState.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewState.kt @@ -101,6 +101,7 @@ data class SelectedAuthenticationState( @Parcelize data class RegistrationState( + val email: String? = null, val isUserNameAvailable: Boolean = false, val selectedMatrixId: String? = null, ) : Parcelable diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/AbstractFtueAuthFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/AbstractFtueAuthFragment.kt index 7766523de9..072e94bc30 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/AbstractFtueAuthFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/AbstractFtueAuthFragment.kt @@ -46,6 +46,7 @@ abstract class AbstractFtueAuthFragment : VectorBaseFragment : VectorBaseFragment { + displayCancelDialog && viewModel.isRegistrationStarted && backIsHardExit() -> { // Ask for confirmation before cancelling the registration MaterialAlertDialogBuilder(requireActivity()) .setTitle(R.string.login_signup_cancel_confirmation_title) diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthEmailEntryFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthEmailEntryFragment.kt index 61da7e0d18..5de8fce82f 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthEmailEntryFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthEmailEntryFragment.kt @@ -25,6 +25,8 @@ import im.vector.app.core.extensions.associateContentStateWith import im.vector.app.core.extensions.autofillEmail 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.hasContent import im.vector.app.core.extensions.isEmail import im.vector.app.core.extensions.setOnImeDoneListener import im.vector.app.core.extensions.toReducedUrl @@ -61,6 +63,10 @@ class FtueAuthEmailEntryFragment @Inject constructor() : AbstractFtueAuthFragmen override fun updateWithState(state: OnboardingViewState) { views.emailEntryHeaderSubtitle.text = getString(R.string.ftue_auth_email_subtitle, state.selectedHomeserver.userFacingUrl.toReducedUrl()) + + if (!views.emailEntryInput.hasContent()) { + views.emailEntryInput.editText().setText(state.registrationState.email) + } } override fun onError(throwable: Throwable) { diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt index 867ab45834..150ab74ec2 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt @@ -196,7 +196,7 @@ class FtueAuthVariant( activity.popBackstack() } is OnboardingViewEvents.OnSendEmailSuccess -> { - openWaitForEmailVerification(viewEvents.email) + openWaitForEmailVerification(viewEvents.email, viewEvents.isRestoredSession) } is OnboardingViewEvents.OnSendMsisdnSuccess -> { openMsisdnConfirmation(viewEvents.msisdn) @@ -413,17 +413,19 @@ class FtueAuthVariant( } } - private fun openWaitForEmailVerification(email: String) { - supportFragmentManager.popBackStack(FRAGMENT_REGISTRATION_STAGE_TAG, FragmentManager.POP_BACK_STACK_INCLUSIVE) + private fun openWaitForEmailVerification(email: String, isRestoredSession: Boolean) { when { vectorFeatures.isOnboardingCombinedRegisterEnabled() -> addRegistrationStageFragmentToBackstack( FtueAuthWaitForEmailFragment::class.java, - FtueAuthWaitForEmailFragmentArgument(email), - ) - else -> addRegistrationStageFragmentToBackstack( - FtueAuthLegacyWaitForEmailFragment::class.java, - FtueAuthWaitForEmailFragmentArgument(email), + FtueAuthWaitForEmailFragmentArgument(email, isRestoredSession), ) + else -> { + supportFragmentManager.popBackStack(FRAGMENT_REGISTRATION_STAGE_TAG, FragmentManager.POP_BACK_STACK_INCLUSIVE) + addRegistrationStageFragmentToBackstack( + FtueAuthLegacyWaitForEmailFragment::class.java, + FtueAuthWaitForEmailFragmentArgument(email, isRestoredSession), + ) + } } } 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 b1a3258cae..eb00dc3e21 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 @@ -35,7 +35,8 @@ import javax.inject.Inject @Parcelize data class FtueAuthWaitForEmailFragmentArgument( - val email: String + val email: String, + val isRestoredSession: Boolean, ) : Parcelable /** @@ -48,6 +49,8 @@ class FtueAuthWaitForEmailFragment @Inject constructor( private val params: FtueAuthWaitForEmailFragmentArgument by args() private var inferHasLeftAndReturnedToScreen = false + override fun backIsHardExit() = params.isRestoredSession + override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentFtueWaitForEmailVerificationBinding { return FragmentFtueWaitForEmailVerificationBinding.inflate(inflater, container, false) } @@ -97,6 +100,11 @@ class FtueAuthWaitForEmailFragment @Inject constructor( } override fun resetViewModel() { - viewModel.handle(OnboardingAction.ResetAuthenticationAttempt) + when { + backIsHardExit() -> viewModel.handle(OnboardingAction.ResetAuthenticationAttempt) + else -> { + // delegate to the previous step + } + } } } diff --git a/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt b/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt index bad37d82cd..a9bbb3eb07 100644 --- a/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt +++ b/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt @@ -102,6 +102,48 @@ class OnboardingViewModelTest { viewModelWith(initialState) } + @Test + fun `given registration started with currentThreePid, when handling InitWith, then emits restored session OnSendEmailSuccess`() = runTest { + val test = viewModel.test() + fakeAuthenticationService.givenRegistrationWizard(FakeRegistrationWizard().also { + it.givenRegistrationStarted(hasStarted = true) + it.givenCurrentThreePid(AN_EMAIL) + }) + + viewModel.handle(OnboardingAction.InitWith(LoginConfig(A_HOMESERVER_URL, identityServerUrl = null))) + + test + .assertEvents(OnboardingViewEvents.OnSendEmailSuccess(AN_EMAIL, isRestoredSession = true)) + .finish() + } + + @Test + fun `given registration not started, when handling InitWith, then does nothing`() = runTest { + val test = viewModel.test() + fakeAuthenticationService.givenRegistrationWizard(FakeRegistrationWizard().also { it.givenRegistrationStarted(hasStarted = false) }) + + viewModel.handle(OnboardingAction.InitWith(LoginConfig(A_HOMESERVER_URL, identityServerUrl = null))) + + test + .assertNoEvents() + .finish() + } + + @Test + fun `given registration started without currentThreePid, when handling InitWith, then does nothing`() = runTest { + val test = viewModel.test() + fakeAuthenticationService.givenRegistrationWizard(FakeRegistrationWizard().also { + it.givenRegistrationStarted(hasStarted = true) + it.givenCurrentThreePid(threePid = null) + }) + + viewModel.handle(OnboardingAction.InitWith(LoginConfig(A_HOMESERVER_URL, identityServerUrl = null))) + + test + .assertNoEvents() + .finish() + } + @Test fun `when handling PostViewEvent, then emits contents as view event`() = runTest { val test = viewModel.test() @@ -254,6 +296,24 @@ class OnboardingViewModelTest { .finish() } + @Test + fun `given register action returns email success, when handling action, then updates registration state and emits email success`() = runTest { + val test = viewModel.test() + givenRegistrationResultFor(A_LOADABLE_REGISTER_ACTION, RegistrationActionHandler.Result.SendEmailSuccess(AN_EMAIL)) + + viewModel.handle(OnboardingAction.PostRegisterAction(A_LOADABLE_REGISTER_ACTION)) + + test + .assertStatesChanges( + initialState, + { copy(isLoading = true) }, + { copy(registrationState = RegistrationState(email = AN_EMAIL)) }, + { copy(isLoading = false) } + ) + .assertEvents(OnboardingViewEvents.OnSendEmailSuccess(AN_EMAIL, isRestoredSession = false)) + .finish() + } + @Test fun `given unavailable deeplink, when selecting homeserver, then emits failure with default homeserver as retry action`() = runTest { fakeContext.givenHasConnection() diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeRegistrationWizard.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeRegistrationWizard.kt index e0b4586931..4f0b1fe083 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeRegistrationWizard.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeRegistrationWizard.kt @@ -45,6 +45,14 @@ class FakeRegistrationWizard : RegistrationWizard by mockk(relaxed = false) { } } + fun givenRegistrationStarted(hasStarted: Boolean) { + coEvery { isRegistrationStarted() } returns hasStarted + } + + fun givenCurrentThreePid(threePid: String?) { + coEvery { getCurrentThreePid() } returns threePid + } + fun givenUserNameIsAvailable(userName: String) { coEvery { registrationAvailable(userName) } returns RegistrationAvailability.Available }