From 1e5c05722330a535775bc52b325a5e52c722c7fa Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Wed, 8 Dec 2021 17:01:26 +0000 Subject: [PATCH 1/2] clones the login view model domain to the ftue so that we can start overriding and merge login2 behaviour --- .../app/core/di/MavericksViewModelModule.kt | 6 + .../app/features/ftue/DefaultFTUEVariant.kt | 72 +- .../im/vector/app/features/ftue/FTUEAction.kt | 79 ++ .../vector/app/features/ftue/FTUEActivity.kt | 2 +- .../app/features/ftue/FTUEVariantFactory.kt | 5 +- .../app/features/ftue/FTUEViewEvents.kt | 50 ++ .../vector/app/features/ftue/FTUEViewModel.kt | 840 ++++++++++++++++++ .../vector/app/features/ftue/FTUEViewState.kt | 76 ++ 8 files changed, 1088 insertions(+), 42 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/ftue/FTUEAction.kt create mode 100644 vector/src/main/java/im/vector/app/features/ftue/FTUEViewEvents.kt create mode 100644 vector/src/main/java/im/vector/app/features/ftue/FTUEViewModel.kt create mode 100644 vector/src/main/java/im/vector/app/features/ftue/FTUEViewState.kt diff --git a/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt b/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt index d09cd21d19..2e33a6c682 100644 --- a/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt +++ b/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt @@ -37,6 +37,7 @@ import im.vector.app.features.crypto.verification.emoji.VerificationEmojiCodeVie import im.vector.app.features.devtools.RoomDevToolViewModel import im.vector.app.features.discovery.DiscoverySettingsViewModel import im.vector.app.features.discovery.change.SetIdentityServerViewModel +import im.vector.app.features.ftue.FTUEViewModel import im.vector.app.features.home.HomeActivityViewModel import im.vector.app.features.home.HomeDetailViewModel import im.vector.app.features.home.PromoteRestrictedViewModel @@ -447,6 +448,11 @@ interface MavericksViewModelModule { @MavericksViewModelKey(AccountCreatedViewModel::class) fun accountCreatedViewModelFactory(factory: AccountCreatedViewModel.Factory): MavericksAssistedViewModelFactory<*, *> + @Binds + @IntoMap + @MavericksViewModelKey(FTUEViewModel::class) + fun ftueViewModelFactory(factory: FTUEViewModel.Factory): MavericksAssistedViewModelFactory<*, *> + @Binds @IntoMap @MavericksViewModelKey(LoginViewModel2::class) diff --git a/vector/src/main/java/im/vector/app/features/ftue/DefaultFTUEVariant.kt b/vector/src/main/java/im/vector/app/features/ftue/DefaultFTUEVariant.kt index 98b1f98df0..00a9262a1f 100644 --- a/vector/src/main/java/im/vector/app/features/ftue/DefaultFTUEVariant.kt +++ b/vector/src/main/java/im/vector/app/features/ftue/DefaultFTUEVariant.kt @@ -35,7 +35,6 @@ import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.databinding.ActivityLoginBinding import im.vector.app.features.home.HomeActivity -import im.vector.app.features.login.LoginAction import im.vector.app.features.login.LoginCaptchaFragment import im.vector.app.features.login.LoginCaptchaFragmentArgument import im.vector.app.features.login.LoginConfig @@ -50,9 +49,6 @@ import im.vector.app.features.login.LoginServerSelectionFragment import im.vector.app.features.login.LoginServerUrlFormFragment import im.vector.app.features.login.LoginSignUpSignInSelectionFragment import im.vector.app.features.login.LoginSplashFragment -import im.vector.app.features.login.LoginViewEvents -import im.vector.app.features.login.LoginViewModel -import im.vector.app.features.login.LoginViewState import im.vector.app.features.login.LoginWaitForEmailFragment import im.vector.app.features.login.LoginWaitForEmailFragmentArgument import im.vector.app.features.login.LoginWebFragment @@ -72,7 +68,7 @@ private const val FRAGMENT_LOGIN_TAG = "FRAGMENT_LOGIN_TAG" class DefaultFTUEVariant( private val views: ActivityLoginBinding, - private val loginViewModel: LoginViewModel, + private val ftueViewModel: FTUEViewModel, private val activity: VectorBaseActivity, private val supportFragmentManager: FragmentManager ) : FTUEVariant { @@ -103,16 +99,16 @@ class DefaultFTUEVariant( } with(activity) { - loginViewModel.onEach { + ftueViewModel.onEach { updateWithState(it) } - loginViewModel.observeViewEvents { handleLoginViewEvents(it) } + ftueViewModel.observeViewEvents { handleLoginViewEvents(it) } } // Get config extra val loginConfig = activity.intent.getParcelableExtra(FTUEActivity.EXTRA_CONFIG) if (isFirstCreation) { - loginViewModel.handle(LoginAction.InitWith(loginConfig)) + ftueViewModel.handle(FTUEAction.InitWith(loginConfig)) } } @@ -124,17 +120,17 @@ class DefaultFTUEVariant( activity.addFragment(views.loginFragmentContainer, LoginSplashFragment::class.java) } - private fun handleLoginViewEvents(loginViewEvents: LoginViewEvents) { - when (loginViewEvents) { - is LoginViewEvents.RegistrationFlowResult -> { + private fun handleLoginViewEvents(ftueViewEvents: FTUEViewEvents) { + when (ftueViewEvents) { + is FTUEViewEvents.RegistrationFlowResult -> { // Check that all flows are supported by the application - if (loginViewEvents.flowResult.missingStages.any { !it.isSupported() }) { + if (ftueViewEvents.flowResult.missingStages.any { !it.isSupported() }) { // Display a popup to propose use web fallback onRegistrationStageNotSupported() } else { - if (loginViewEvents.isRegistrationStarted) { + if (ftueViewEvents.isRegistrationStarted) { // Go on with registration flow - handleRegistrationNavigation(loginViewEvents.flowResult) + handleRegistrationNavigation(ftueViewEvents.flowResult) } else { // First ask for login and password // I add a tag to indicate that this fragment is a registration stage. @@ -147,7 +143,7 @@ class DefaultFTUEVariant( } } } - is LoginViewEvents.OutdatedHomeserver -> { + is FTUEViewEvents.OutdatedHomeserver -> { MaterialAlertDialogBuilder(activity) .setTitle(R.string.login_error_outdated_homeserver_title) .setMessage(R.string.login_error_outdated_homeserver_warning_content) @@ -155,7 +151,7 @@ class DefaultFTUEVariant( .show() Unit } - is LoginViewEvents.OpenServerSelection -> + is FTUEViewEvents.OpenServerSelection -> activity.addFragmentToBackstack(views.loginFragmentContainer, LoginServerSelectionFragment::class.java, option = { ft -> @@ -167,63 +163,63 @@ class DefaultFTUEVariant( // TODO Disabled because it provokes a flickering // ft.setCustomAnimations(enterAnim, exitAnim, popEnterAnim, popExitAnim) }) - is LoginViewEvents.OnServerSelectionDone -> onServerSelectionDone(loginViewEvents) - is LoginViewEvents.OnSignModeSelected -> onSignModeSelected(loginViewEvents) - is LoginViewEvents.OnLoginFlowRetrieved -> + is FTUEViewEvents.OnServerSelectionDone -> onServerSelectionDone(ftueViewEvents) + is FTUEViewEvents.OnSignModeSelected -> onSignModeSelected(ftueViewEvents) + is FTUEViewEvents.OnLoginFlowRetrieved -> activity.addFragmentToBackstack(views.loginFragmentContainer, LoginSignUpSignInSelectionFragment::class.java, option = commonOption) - is LoginViewEvents.OnWebLoginError -> onWebLoginError(loginViewEvents) - is LoginViewEvents.OnForgetPasswordClicked -> + is FTUEViewEvents.OnWebLoginError -> onWebLoginError(ftueViewEvents) + is FTUEViewEvents.OnForgetPasswordClicked -> activity.addFragmentToBackstack(views.loginFragmentContainer, LoginResetPasswordFragment::class.java, option = commonOption) - is LoginViewEvents.OnResetPasswordSendThreePidDone -> { + is FTUEViewEvents.OnResetPasswordSendThreePidDone -> { supportFragmentManager.popBackStack(FRAGMENT_LOGIN_TAG, POP_BACK_STACK_EXCLUSIVE) activity.addFragmentToBackstack(views.loginFragmentContainer, LoginResetPasswordMailConfirmationFragment::class.java, option = commonOption) } - is LoginViewEvents.OnResetPasswordMailConfirmationSuccess -> { + is FTUEViewEvents.OnResetPasswordMailConfirmationSuccess -> { supportFragmentManager.popBackStack(FRAGMENT_LOGIN_TAG, POP_BACK_STACK_EXCLUSIVE) activity.addFragmentToBackstack(views.loginFragmentContainer, LoginResetPasswordSuccessFragment::class.java, option = commonOption) } - is LoginViewEvents.OnResetPasswordMailConfirmationSuccessDone -> { + is FTUEViewEvents.OnResetPasswordMailConfirmationSuccessDone -> { // Go back to the login fragment supportFragmentManager.popBackStack(FRAGMENT_LOGIN_TAG, POP_BACK_STACK_EXCLUSIVE) } - is LoginViewEvents.OnSendEmailSuccess -> { + is FTUEViewEvents.OnSendEmailSuccess -> { // Pop the enter email Fragment supportFragmentManager.popBackStack(FRAGMENT_REGISTRATION_STAGE_TAG, FragmentManager.POP_BACK_STACK_INCLUSIVE) activity.addFragmentToBackstack(views.loginFragmentContainer, LoginWaitForEmailFragment::class.java, - LoginWaitForEmailFragmentArgument(loginViewEvents.email), + LoginWaitForEmailFragmentArgument(ftueViewEvents.email), tag = FRAGMENT_REGISTRATION_STAGE_TAG, option = commonOption) } - is LoginViewEvents.OnSendMsisdnSuccess -> { + is FTUEViewEvents.OnSendMsisdnSuccess -> { // Pop the enter Msisdn Fragment supportFragmentManager.popBackStack(FRAGMENT_REGISTRATION_STAGE_TAG, FragmentManager.POP_BACK_STACK_INCLUSIVE) activity.addFragmentToBackstack(views.loginFragmentContainer, LoginGenericTextInputFormFragment::class.java, - LoginGenericTextInputFormFragmentArgument(TextInputFormFragmentMode.ConfirmMsisdn, true, loginViewEvents.msisdn), + LoginGenericTextInputFormFragmentArgument(TextInputFormFragmentMode.ConfirmMsisdn, true, ftueViewEvents.msisdn), tag = FRAGMENT_REGISTRATION_STAGE_TAG, option = commonOption) } - is LoginViewEvents.Failure, - is LoginViewEvents.Loading -> + is FTUEViewEvents.Failure, + is FTUEViewEvents.Loading -> // This is handled by the Fragments Unit }.exhaustive } - private fun updateWithState(loginViewState: LoginViewState) { - if (loginViewState.isUserLogged()) { + private fun updateWithState(ftueViewState: FTUEViewState) { + if (ftueViewState.isUserLogged()) { val intent = HomeActivity.newIntent( activity, - accountCreation = loginViewState.signMode == SignMode.SignUp + accountCreation = ftueViewState.signMode == SignMode.SignUp ) activity.startActivity(intent) activity.finish() @@ -231,10 +227,10 @@ class DefaultFTUEVariant( } // Loading - views.loginLoading.isVisible = loginViewState.isLoading() + views.loginLoading.isVisible = ftueViewState.isLoading() } - private fun onWebLoginError(onWebLoginError: LoginViewEvents.OnWebLoginError) { + private fun onWebLoginError(onWebLoginError: FTUEViewEvents.OnWebLoginError) { // Pop the backstack supportFragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE) @@ -246,7 +242,7 @@ class DefaultFTUEVariant( .show() } - private fun onServerSelectionDone(loginViewEvents: LoginViewEvents.OnServerSelectionDone) { + private fun onServerSelectionDone(loginViewEvents: FTUEViewEvents.OnServerSelectionDone) { when (loginViewEvents.serverType) { ServerType.MatrixOrg -> Unit // In this case, we wait for the login flow ServerType.EMS, @@ -257,7 +253,7 @@ class DefaultFTUEVariant( } } - private fun onSignModeSelected(loginViewEvents: LoginViewEvents.OnSignModeSelected) = withState(loginViewModel) { state -> + private fun onSignModeSelected(loginViewEvents: FTUEViewEvents.OnSignModeSelected) = withState(ftueViewModel) { state -> // state.signMode could not be ready yet. So use value from the ViewEvent when (loginViewEvents.signMode) { SignMode.Unknown -> error("Sign mode has to be set before calling this method") @@ -290,7 +286,7 @@ class DefaultFTUEVariant( override fun onNewIntent(intent: Intent?) { intent?.data ?.let { tryOrNull { it.getQueryParameter("loginToken") } } - ?.let { loginViewModel.handle(LoginAction.LoginWithToken(it)) } + ?.let { ftueViewModel.handle(FTUEAction.LoginWithToken(it)) } } private fun onRegistrationStageNotSupported() { diff --git a/vector/src/main/java/im/vector/app/features/ftue/FTUEAction.kt b/vector/src/main/java/im/vector/app/features/ftue/FTUEAction.kt new file mode 100644 index 0000000000..b43ffd3331 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/ftue/FTUEAction.kt @@ -0,0 +1,79 @@ +/* + * Copyright 2019 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.ftue + +import im.vector.app.core.platform.VectorViewModelAction +import im.vector.app.features.login.LoginConfig +import im.vector.app.features.login.ServerType +import im.vector.app.features.login.SignMode +import org.matrix.android.sdk.api.auth.data.Credentials +import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider +import org.matrix.android.sdk.api.auth.registration.RegisterThreePid +import org.matrix.android.sdk.internal.network.ssl.Fingerprint + +sealed class FTUEAction : VectorViewModelAction { + data class OnGetStarted(val resetLoginConfig: Boolean) : FTUEAction() + + data class UpdateServerType(val serverType: ServerType) : FTUEAction() + data class UpdateHomeServer(val homeServerUrl: String) : FTUEAction() + data class UpdateSignMode(val signMode: SignMode) : FTUEAction() + data class LoginWithToken(val loginToken: String) : FTUEAction() + data class WebLoginSuccess(val credentials: Credentials) : FTUEAction() + data class InitWith(val loginConfig: LoginConfig?) : FTUEAction() + data class ResetPassword(val email: String, val newPassword: String) : FTUEAction() + object ResetPasswordMailConfirmed : FTUEAction() + + // Login or Register, depending on the signMode + data class LoginOrRegister(val username: String, val password: String, val initialDeviceName: String) : FTUEAction() + + // Register actions + open class RegisterAction : FTUEAction() + + data class AddThreePid(val threePid: RegisterThreePid) : RegisterAction() + object SendAgainThreePid : RegisterAction() + + // TODO Confirm Email (from link in the email, open in the phone, intercepted by the app) + data class ValidateThreePid(val code: String) : RegisterAction() + + data class CheckIfEmailHasBeenValidated(val delayMillis: Long) : RegisterAction() + object StopEmailValidationCheck : RegisterAction() + + data class CaptchaDone(val captchaResponse: String) : RegisterAction() + object AcceptTerms : RegisterAction() + object RegisterDummy : RegisterAction() + + // Reset actions + open class ResetAction : FTUEAction() + + object ResetHomeServerType : ResetAction() + object ResetHomeServerUrl : ResetAction() + object ResetSignMode : ResetAction() + object ResetLogin : ResetAction() + object ResetResetPassword : ResetAction() + + // Homeserver history + object ClearHomeServerHistory : FTUEAction() + + // For the soft logout case + data class SetupSsoForSessionRecovery(val homeServerUrl: String, + val deviceId: String, + val ssoIdentityProviders: List?) : FTUEAction() + + data class PostViewEvent(val viewEvent: FTUEViewEvents) : FTUEAction() + + data class UserAcceptCertificate(val fingerprint: Fingerprint) : FTUEAction() +} diff --git a/vector/src/main/java/im/vector/app/features/ftue/FTUEActivity.kt b/vector/src/main/java/im/vector/app/features/ftue/FTUEActivity.kt index 805e39c48d..c54c0547a4 100644 --- a/vector/src/main/java/im/vector/app/features/ftue/FTUEActivity.kt +++ b/vector/src/main/java/im/vector/app/features/ftue/FTUEActivity.kt @@ -34,7 +34,7 @@ import javax.inject.Inject class FTUEActivity : VectorBaseActivity(), ToolbarConfigurable, UnlockedActivity { private val ftueVariant by lifecycleAwareLazy { - ftueVariantFactory.create(this, loginViewModel = lazyViewModel(), loginViewModel2 = lazyViewModel()) + ftueVariantFactory.create(this, ftueViewModel = lazyViewModel(), loginViewModel2 = lazyViewModel()) } @Inject lateinit var ftueVariantFactory: FTUEVariantFactory diff --git a/vector/src/main/java/im/vector/app/features/ftue/FTUEVariantFactory.kt b/vector/src/main/java/im/vector/app/features/ftue/FTUEVariantFactory.kt index 7efd6023fe..aabd0338a2 100644 --- a/vector/src/main/java/im/vector/app/features/ftue/FTUEVariantFactory.kt +++ b/vector/src/main/java/im/vector/app/features/ftue/FTUEVariantFactory.kt @@ -17,7 +17,6 @@ package im.vector.app.features.ftue import im.vector.app.features.VectorFeatures -import im.vector.app.features.login.LoginViewModel import im.vector.app.features.login2.LoginViewModel2 import javax.inject.Inject @@ -25,11 +24,11 @@ class FTUEVariantFactory @Inject constructor( private val vectorFeatures: VectorFeatures, ) { - fun create(activity: FTUEActivity, loginViewModel: Lazy, loginViewModel2: Lazy) = when (vectorFeatures.loginVariant()) { + fun create(activity: FTUEActivity, ftueViewModel: Lazy, loginViewModel2: Lazy) = when (vectorFeatures.loginVariant()) { VectorFeatures.LoginVariant.LEGACY -> error("Legacy is not supported by the FTUE") VectorFeatures.LoginVariant.FTUE -> DefaultFTUEVariant( views = activity.getBinding(), - loginViewModel = loginViewModel.value, + ftueViewModel = ftueViewModel.value, activity = activity, supportFragmentManager = activity.supportFragmentManager ) diff --git a/vector/src/main/java/im/vector/app/features/ftue/FTUEViewEvents.kt b/vector/src/main/java/im/vector/app/features/ftue/FTUEViewEvents.kt new file mode 100644 index 0000000000..d10063c797 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/ftue/FTUEViewEvents.kt @@ -0,0 +1,50 @@ +/* + * Copyright 2019 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package im.vector.app.features.ftue + +import im.vector.app.core.platform.VectorViewEvents +import im.vector.app.features.login.ServerType +import im.vector.app.features.login.SignMode +import org.matrix.android.sdk.api.auth.registration.FlowResult + +/** + * Transient events for Login + */ +sealed class FTUEViewEvents : VectorViewEvents { + data class Loading(val message: CharSequence? = null) : FTUEViewEvents() + data class Failure(val throwable: Throwable) : FTUEViewEvents() + + data class RegistrationFlowResult(val flowResult: FlowResult, val isRegistrationStarted: Boolean) : FTUEViewEvents() + object OutdatedHomeserver : FTUEViewEvents() + + // Navigation event + + object OpenServerSelection : FTUEViewEvents() + data class OnServerSelectionDone(val serverType: ServerType) : FTUEViewEvents() + object OnLoginFlowRetrieved : FTUEViewEvents() + data class OnSignModeSelected(val signMode: SignMode) : FTUEViewEvents() + object OnForgetPasswordClicked : FTUEViewEvents() + object OnResetPasswordSendThreePidDone : FTUEViewEvents() + object OnResetPasswordMailConfirmationSuccess : FTUEViewEvents() + object OnResetPasswordMailConfirmationSuccessDone : FTUEViewEvents() + + data class OnSendEmailSuccess(val email: String) : FTUEViewEvents() + data class OnSendMsisdnSuccess(val msisdn: String) : FTUEViewEvents() + + data class OnWebLoginError(val errorCode: Int, val description: String, val failingUrl: String) : FTUEViewEvents() +} diff --git a/vector/src/main/java/im/vector/app/features/ftue/FTUEViewModel.kt b/vector/src/main/java/im/vector/app/features/ftue/FTUEViewModel.kt new file mode 100644 index 0000000000..4c78d6fd30 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/ftue/FTUEViewModel.kt @@ -0,0 +1,840 @@ +/* + * Copyright 2019 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.ftue + +import android.content.Context +import android.net.Uri +import com.airbnb.mvrx.Fail +import com.airbnb.mvrx.Loading +import com.airbnb.mvrx.MavericksViewModelFactory +import com.airbnb.mvrx.Success +import com.airbnb.mvrx.Uninitialized +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import im.vector.app.R +import im.vector.app.core.di.ActiveSessionHolder +import im.vector.app.core.di.MavericksAssistedViewModelFactory +import im.vector.app.core.di.hiltMavericksViewModelFactory +import im.vector.app.core.extensions.configureAndStart +import im.vector.app.core.extensions.exhaustive +import im.vector.app.core.platform.VectorViewModel +import im.vector.app.core.resources.StringProvider +import im.vector.app.core.utils.ensureTrailingSlash +import im.vector.app.features.login.HomeServerConnectionConfigFactory +import im.vector.app.features.login.LoginConfig +import im.vector.app.features.login.LoginMode +import im.vector.app.features.login.ReAuthHelper +import im.vector.app.features.login.ServerType +import im.vector.app.features.login.SignMode +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch +import org.matrix.android.sdk.api.MatrixPatterns.getDomain +import org.matrix.android.sdk.api.auth.AuthenticationService +import org.matrix.android.sdk.api.auth.HomeServerHistoryService +import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig +import org.matrix.android.sdk.api.auth.data.LoginFlowTypes +import org.matrix.android.sdk.api.auth.login.LoginWizard +import org.matrix.android.sdk.api.auth.registration.FlowResult +import org.matrix.android.sdk.api.auth.registration.RegistrationResult +import org.matrix.android.sdk.api.auth.registration.RegistrationWizard +import org.matrix.android.sdk.api.auth.registration.Stage +import org.matrix.android.sdk.api.auth.wellknown.WellknownResult +import org.matrix.android.sdk.api.failure.Failure +import org.matrix.android.sdk.api.failure.MatrixIdFailure +import org.matrix.android.sdk.api.session.Session +import timber.log.Timber +import java.util.concurrent.CancellationException + +/** + * + */ +class FTUEViewModel @AssistedInject constructor( + @Assisted initialState: FTUEViewState, + private val applicationContext: Context, + private val authenticationService: AuthenticationService, + private val activeSessionHolder: ActiveSessionHolder, + private val homeServerConnectionConfigFactory: HomeServerConnectionConfigFactory, + private val reAuthHelper: ReAuthHelper, + private val stringProvider: StringProvider, + private val homeServerHistoryService: HomeServerHistoryService +) : VectorViewModel(initialState) { + + @AssistedFactory + interface Factory : MavericksAssistedViewModelFactory { + override fun create(initialState: FTUEViewState): FTUEViewModel + } + + companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() + + init { + getKnownCustomHomeServersUrls() + } + + private fun getKnownCustomHomeServersUrls() { + setState { + copy(knownCustomHomeServersUrls = homeServerHistoryService.getKnownServersUrls()) + } + } + + // Store the last action, to redo it after user has trusted the untrusted certificate + private var lastAction: FTUEAction? = null + private var currentHomeServerConnectionConfig: HomeServerConnectionConfig? = null + + private val matrixOrgUrl = stringProvider.getString(R.string.matrix_org_server_url).ensureTrailingSlash() + + val currentThreePid: String? + get() = registrationWizard?.currentThreePid + + // True when login and password has been sent with success to the homeserver + val isRegistrationStarted: Boolean + get() = authenticationService.isRegistrationStarted + + private val registrationWizard: RegistrationWizard? + get() = authenticationService.getRegistrationWizard() + + private val loginWizard: LoginWizard? + get() = authenticationService.getLoginWizard() + + private var loginConfig: LoginConfig? = null + + private var currentJob: Job? = null + set(value) { + // Cancel any previous Job + field?.cancel() + field = value + } + + override fun handle(action: FTUEAction) { + when (action) { + is FTUEAction.OnGetStarted -> handleOnGetStarted(action) + is FTUEAction.UpdateServerType -> handleUpdateServerType(action) + is FTUEAction.UpdateSignMode -> handleUpdateSignMode(action) + is FTUEAction.InitWith -> handleInitWith(action) + is FTUEAction.UpdateHomeServer -> handleUpdateHomeserver(action).also { lastAction = action } + is FTUEAction.LoginOrRegister -> handleLoginOrRegister(action).also { lastAction = action } + is FTUEAction.LoginWithToken -> handleLoginWithToken(action) + is FTUEAction.WebLoginSuccess -> handleWebLoginSuccess(action) + is FTUEAction.ResetPassword -> handleResetPassword(action) + is FTUEAction.ResetPasswordMailConfirmed -> handleResetPasswordMailConfirmed() + is FTUEAction.RegisterAction -> handleRegisterAction(action) + is FTUEAction.ResetAction -> handleResetAction(action) + is FTUEAction.SetupSsoForSessionRecovery -> handleSetupSsoForSessionRecovery(action) + is FTUEAction.UserAcceptCertificate -> handleUserAcceptCertificate(action) + FTUEAction.ClearHomeServerHistory -> handleClearHomeServerHistory() + is FTUEAction.PostViewEvent -> _viewEvents.post(action.viewEvent) + }.exhaustive + } + + private fun handleOnGetStarted(action: FTUEAction.OnGetStarted) { + if (action.resetLoginConfig) { + loginConfig = null + } + + val configUrl = loginConfig?.homeServerUrl?.takeIf { it.isNotEmpty() } + if (configUrl != null) { + // Use config from uri + val homeServerConnectionConfig = homeServerConnectionConfigFactory.create(configUrl) + if (homeServerConnectionConfig == null) { + // Url is invalid, in this case, just use the regular flow + Timber.w("Url from config url was invalid: $configUrl") + _viewEvents.post(FTUEViewEvents.OpenServerSelection) + } else { + getLoginFlow(homeServerConnectionConfig, ServerType.Other) + } + } else { + _viewEvents.post(FTUEViewEvents.OpenServerSelection) + } + } + + private fun handleUserAcceptCertificate(action: FTUEAction.UserAcceptCertificate) { + // It happens when we get the login flow, or during direct authentication. + // So alter the homeserver config and retrieve again the login flow + when (val finalLastAction = lastAction) { + is FTUEAction.UpdateHomeServer -> { + currentHomeServerConnectionConfig + ?.let { it.copy(allowedFingerprints = it.allowedFingerprints + action.fingerprint) } + ?.let { getLoginFlow(it) } + } + is FTUEAction.LoginOrRegister -> + handleDirectLogin( + finalLastAction, + HomeServerConnectionConfig.Builder() + // Will be replaced by the task + .withHomeServerUri("https://dummy.org") + .withAllowedFingerPrints(listOf(action.fingerprint)) + .build() + ) + } + } + + private fun rememberHomeServer(homeServerUrl: String) { + homeServerHistoryService.addHomeServerToHistory(homeServerUrl) + getKnownCustomHomeServersUrls() + } + + private fun handleClearHomeServerHistory() { + homeServerHistoryService.clearHistory() + getKnownCustomHomeServersUrls() + } + + private fun handleLoginWithToken(action: FTUEAction.LoginWithToken) { + val safeLoginWizard = loginWizard + + if (safeLoginWizard == null) { + setState { + copy( + asyncLoginAction = Fail(Throwable("Bad configuration")) + ) + } + } else { + setState { + copy( + asyncLoginAction = Loading() + ) + } + + currentJob = viewModelScope.launch { + try { + safeLoginWizard.loginWithToken(action.loginToken) + } catch (failure: Throwable) { + _viewEvents.post(FTUEViewEvents.Failure(failure)) + setState { + copy( + asyncLoginAction = Fail(failure) + ) + } + null + } + ?.let { onSessionCreated(it) } + } + } + } + + private fun handleSetupSsoForSessionRecovery(action: FTUEAction.SetupSsoForSessionRecovery) { + setState { + copy( + signMode = SignMode.SignIn, + loginMode = LoginMode.Sso(action.ssoIdentityProviders), + homeServerUrlFromUser = action.homeServerUrl, + homeServerUrl = action.homeServerUrl, + deviceId = action.deviceId + ) + } + } + + private fun handleRegisterAction(action: FTUEAction.RegisterAction) { + when (action) { + is FTUEAction.CaptchaDone -> handleCaptchaDone(action) + is FTUEAction.AcceptTerms -> handleAcceptTerms() + is FTUEAction.RegisterDummy -> handleRegisterDummy() + is FTUEAction.AddThreePid -> handleAddThreePid(action) + is FTUEAction.SendAgainThreePid -> handleSendAgainThreePid() + is FTUEAction.ValidateThreePid -> handleValidateThreePid(action) + is FTUEAction.CheckIfEmailHasBeenValidated -> handleCheckIfEmailHasBeenValidated(action) + is FTUEAction.StopEmailValidationCheck -> handleStopEmailValidationCheck() + } + } + + private fun handleCheckIfEmailHasBeenValidated(action: FTUEAction.CheckIfEmailHasBeenValidated) { + // We do not want the common progress bar to be displayed, so we do not change asyncRegistration value in the state + currentJob = executeRegistrationStep(withLoading = false) { + it.checkIfEmailHasBeenValidated(action.delayMillis) + } + } + + private fun handleStopEmailValidationCheck() { + currentJob = null + } + + private fun handleValidateThreePid(action: FTUEAction.ValidateThreePid) { + currentJob = executeRegistrationStep { + it.handleValidateThreePid(action.code) + } + } + + private fun executeRegistrationStep(withLoading: Boolean = true, + block: suspend (RegistrationWizard) -> RegistrationResult): Job { + if (withLoading) { + setState { copy(asyncRegistration = Loading()) } + } + return viewModelScope.launch { + try { + registrationWizard?.let { block(it) } + /* + // Simulate registration disabled + throw Failure.ServerError(MatrixError( + code = MatrixError.FORBIDDEN, + message = "Registration is disabled" + ), 403)) + */ + } catch (failure: Throwable) { + if (failure !is CancellationException) { + _viewEvents.post(FTUEViewEvents.Failure(failure)) + } + null + } + ?.let { data -> + when (data) { + is RegistrationResult.Success -> onSessionCreated(data.session) + is RegistrationResult.FlowResponse -> onFlowResponse(data.flowResult) + } + } + + setState { + copy( + asyncRegistration = Uninitialized + ) + } + } + } + + private fun handleAddThreePid(action: FTUEAction.AddThreePid) { + setState { copy(asyncRegistration = Loading()) } + currentJob = viewModelScope.launch { + try { + registrationWizard?.addThreePid(action.threePid) + } catch (failure: Throwable) { + _viewEvents.post(FTUEViewEvents.Failure(failure)) + } + setState { + copy( + asyncRegistration = Uninitialized + ) + } + } + } + + private fun handleSendAgainThreePid() { + setState { copy(asyncRegistration = Loading()) } + currentJob = viewModelScope.launch { + try { + registrationWizard?.sendAgainThreePid() + } catch (failure: Throwable) { + _viewEvents.post(FTUEViewEvents.Failure(failure)) + } + setState { + copy( + asyncRegistration = Uninitialized + ) + } + } + } + + private fun handleAcceptTerms() { + currentJob = executeRegistrationStep { + it.acceptTerms() + } + } + + private fun handleRegisterDummy() { + currentJob = executeRegistrationStep { + it.dummy() + } + } + + private fun handleRegisterWith(action: FTUEAction.LoginOrRegister) { + reAuthHelper.data = action.password + currentJob = executeRegistrationStep { + it.createAccount( + action.username, + action.password, + action.initialDeviceName + ) + } + } + + private fun handleCaptchaDone(action: FTUEAction.CaptchaDone) { + currentJob = executeRegistrationStep { + it.performReCaptcha(action.captchaResponse) + } + } + + private fun handleResetAction(action: FTUEAction.ResetAction) { + // Cancel any request + currentJob = null + + when (action) { + FTUEAction.ResetHomeServerType -> { + setState { + copy( + serverType = ServerType.Unknown + ) + } + } + FTUEAction.ResetHomeServerUrl -> { + viewModelScope.launch { + authenticationService.reset() + setState { + copy( + asyncHomeServerLoginFlowRequest = Uninitialized, + homeServerUrlFromUser = null, + homeServerUrl = null, + loginMode = LoginMode.Unknown, + serverType = ServerType.Unknown, + loginModeSupportedTypes = emptyList() + ) + } + } + } + FTUEAction.ResetSignMode -> { + setState { + copy( + asyncHomeServerLoginFlowRequest = Uninitialized, + signMode = SignMode.Unknown, + loginMode = LoginMode.Unknown, + loginModeSupportedTypes = emptyList() + ) + } + } + FTUEAction.ResetLogin -> { + viewModelScope.launch { + authenticationService.cancelPendingLoginOrRegistration() + setState { + copy( + asyncLoginAction = Uninitialized, + asyncRegistration = Uninitialized + ) + } + } + } + FTUEAction.ResetResetPassword -> { + setState { + copy( + asyncResetPassword = Uninitialized, + asyncResetMailConfirmed = Uninitialized, + resetPasswordEmail = null + ) + } + } + } + } + + private fun handleUpdateSignMode(action: FTUEAction.UpdateSignMode) { + setState { + copy( + signMode = action.signMode + ) + } + + when (action.signMode) { + SignMode.SignUp -> startRegistrationFlow() + SignMode.SignIn -> startAuthenticationFlow() + SignMode.SignInWithMatrixId -> _viewEvents.post(FTUEViewEvents.OnSignModeSelected(SignMode.SignInWithMatrixId)) + SignMode.Unknown -> Unit + } + } + + private fun handleUpdateServerType(action: FTUEAction.UpdateServerType) { + setState { + copy( + serverType = action.serverType + ) + } + + when (action.serverType) { + ServerType.Unknown -> Unit /* Should not happen */ + ServerType.MatrixOrg -> + // Request login flow here + handle(FTUEAction.UpdateHomeServer(matrixOrgUrl)) + ServerType.EMS, + ServerType.Other -> _viewEvents.post(FTUEViewEvents.OnServerSelectionDone(action.serverType)) + }.exhaustive + } + + private fun handleInitWith(action: FTUEAction.InitWith) { + loginConfig = action.loginConfig + + // If there is a pending email validation continue on this step + try { + if (registrationWizard?.isRegistrationStarted == true) { + currentThreePid?.let { + handle(FTUEAction.PostViewEvent(FTUEViewEvents.OnSendEmailSuccess(it))) + } + } + } catch (e: Throwable) { + // NOOP. API is designed to use wizards in a login/registration flow, + // but we need to check the state anyway. + } + } + + private fun handleResetPassword(action: FTUEAction.ResetPassword) { + val safeLoginWizard = loginWizard + + if (safeLoginWizard == null) { + setState { + copy( + asyncResetPassword = Fail(Throwable("Bad configuration")), + asyncResetMailConfirmed = Uninitialized + ) + } + } else { + setState { + copy( + asyncResetPassword = Loading(), + asyncResetMailConfirmed = Uninitialized + ) + } + + currentJob = viewModelScope.launch { + try { + safeLoginWizard.resetPassword(action.email, action.newPassword) + } catch (failure: Throwable) { + setState { + copy( + asyncResetPassword = Fail(failure) + ) + } + return@launch + } + + setState { + copy( + asyncResetPassword = Success(Unit), + resetPasswordEmail = action.email + ) + } + + _viewEvents.post(FTUEViewEvents.OnResetPasswordSendThreePidDone) + } + } + } + + private fun handleResetPasswordMailConfirmed() { + val safeLoginWizard = loginWizard + + if (safeLoginWizard == null) { + setState { + copy( + asyncResetPassword = Uninitialized, + asyncResetMailConfirmed = Fail(Throwable("Bad configuration")) + ) + } + } else { + setState { + copy( + asyncResetPassword = Uninitialized, + asyncResetMailConfirmed = Loading() + ) + } + + currentJob = viewModelScope.launch { + try { + safeLoginWizard.resetPasswordMailConfirmed() + } catch (failure: Throwable) { + setState { + copy( + asyncResetMailConfirmed = Fail(failure) + ) + } + return@launch + } + setState { + copy( + asyncResetMailConfirmed = Success(Unit), + resetPasswordEmail = null + ) + } + + _viewEvents.post(FTUEViewEvents.OnResetPasswordMailConfirmationSuccess) + } + } + } + + private fun handleLoginOrRegister(action: FTUEAction.LoginOrRegister) = withState { state -> + when (state.signMode) { + SignMode.Unknown -> error("Developer error, invalid sign mode") + SignMode.SignIn -> handleLogin(action) + SignMode.SignUp -> handleRegisterWith(action) + SignMode.SignInWithMatrixId -> handleDirectLogin(action, null) + }.exhaustive + } + + private fun handleDirectLogin(action: FTUEAction.LoginOrRegister, homeServerConnectionConfig: HomeServerConnectionConfig?) { + setState { + copy( + asyncLoginAction = Loading() + ) + } + + currentJob = viewModelScope.launch { + val data = try { + authenticationService.getWellKnownData(action.username, homeServerConnectionConfig) + } catch (failure: Throwable) { + onDirectLoginError(failure) + return@launch + } + when (data) { + is WellknownResult.Prompt -> + onWellknownSuccess(action, data, homeServerConnectionConfig) + is WellknownResult.FailPrompt -> + // Relax on IS discovery if homeserver is valid + if (data.homeServerUrl != null && data.wellKnown != null) { + onWellknownSuccess(action, WellknownResult.Prompt(data.homeServerUrl!!, null, data.wellKnown!!), homeServerConnectionConfig) + } else { + onWellKnownError() + } + else -> { + onWellKnownError() + } + }.exhaustive + } + } + + private fun onWellKnownError() { + setState { + copy( + asyncLoginAction = Uninitialized + ) + } + _viewEvents.post(FTUEViewEvents.Failure(Exception(stringProvider.getString(R.string.autodiscover_well_known_error)))) + } + + private suspend fun onWellknownSuccess(action: FTUEAction.LoginOrRegister, + wellKnownPrompt: WellknownResult.Prompt, + homeServerConnectionConfig: HomeServerConnectionConfig?) { + val alteredHomeServerConnectionConfig = homeServerConnectionConfig + ?.copy( + homeServerUriBase = Uri.parse(wellKnownPrompt.homeServerUrl), + identityServerUri = wellKnownPrompt.identityServerUrl?.let { Uri.parse(it) } + ) + ?: HomeServerConnectionConfig( + homeServerUri = Uri.parse("https://${action.username.getDomain()}"), + homeServerUriBase = Uri.parse(wellKnownPrompt.homeServerUrl), + identityServerUri = wellKnownPrompt.identityServerUrl?.let { Uri.parse(it) } + ) + + val data = try { + authenticationService.directAuthentication( + alteredHomeServerConnectionConfig, + action.username, + action.password, + action.initialDeviceName) + } catch (failure: Throwable) { + onDirectLoginError(failure) + return + } + onSessionCreated(data) + } + + private fun onDirectLoginError(failure: Throwable) { + when (failure) { + is MatrixIdFailure.InvalidMatrixId, + is Failure.UnrecognizedCertificateFailure -> { + // Display this error in a dialog + _viewEvents.post(FTUEViewEvents.Failure(failure)) + setState { + copy( + asyncLoginAction = Uninitialized + ) + } + } + else -> { + setState { + copy( + asyncLoginAction = Fail(failure) + ) + } + } + } + } + + private fun handleLogin(action: FTUEAction.LoginOrRegister) { + val safeLoginWizard = loginWizard + + if (safeLoginWizard == null) { + setState { + copy( + asyncLoginAction = Fail(Throwable("Bad configuration")) + ) + } + } else { + setState { + copy( + asyncLoginAction = Loading() + ) + } + + currentJob = viewModelScope.launch { + try { + safeLoginWizard.login( + action.username, + action.password, + action.initialDeviceName + ) + } catch (failure: Throwable) { + setState { + copy( + asyncLoginAction = Fail(failure) + ) + } + null + } + ?.let { + reAuthHelper.data = action.password + onSessionCreated(it) + } + } + } + } + + private fun startRegistrationFlow() { + currentJob = executeRegistrationStep { + it.getRegistrationFlow() + } + } + + private fun startAuthenticationFlow() { + // Ensure Wizard is ready + loginWizard + + _viewEvents.post(FTUEViewEvents.OnSignModeSelected(SignMode.SignIn)) + } + + private fun onFlowResponse(flowResult: FlowResult) { + // If dummy stage is mandatory, and password is already sent, do the dummy stage now + if (isRegistrationStarted && + flowResult.missingStages.any { it is Stage.Dummy && it.mandatory }) { + handleRegisterDummy() + } else { + // Notify the user + _viewEvents.post(FTUEViewEvents.RegistrationFlowResult(flowResult, isRegistrationStarted)) + } + } + + private suspend fun onSessionCreated(session: Session) { + activeSessionHolder.setActiveSession(session) + + authenticationService.reset() + session.configureAndStart(applicationContext) + setState { + copy( + asyncLoginAction = Success(Unit) + ) + } + } + + private fun handleWebLoginSuccess(action: FTUEAction.WebLoginSuccess) = withState { state -> + val homeServerConnectionConfigFinal = homeServerConnectionConfigFactory.create(state.homeServerUrl) + + if (homeServerConnectionConfigFinal == null) { + // Should not happen + Timber.w("homeServerConnectionConfig is null") + } else { + currentJob = viewModelScope.launch { + try { + authenticationService.createSessionFromSso(homeServerConnectionConfigFinal, action.credentials) + } catch (failure: Throwable) { + setState { + copy(asyncLoginAction = Fail(failure)) + } + null + } + ?.let { onSessionCreated(it) } + } + } + } + + private fun handleUpdateHomeserver(action: FTUEAction.UpdateHomeServer) { + val homeServerConnectionConfig = homeServerConnectionConfigFactory.create(action.homeServerUrl) + if (homeServerConnectionConfig == null) { + // This is invalid + _viewEvents.post(FTUEViewEvents.Failure(Throwable("Unable to create a HomeServerConnectionConfig"))) + } else { + getLoginFlow(homeServerConnectionConfig) + } + } + + private fun getLoginFlow(homeServerConnectionConfig: HomeServerConnectionConfig, + serverTypeOverride: ServerType? = null) { + currentHomeServerConnectionConfig = homeServerConnectionConfig + + currentJob = viewModelScope.launch { + authenticationService.cancelPendingLoginOrRegistration() + + setState { + copy( + asyncHomeServerLoginFlowRequest = Loading(), + // If user has entered https://matrix.org, ensure that server type is ServerType.MatrixOrg + // It is also useful to set the value again in the case of a certificate error on matrix.org + serverType = if (homeServerConnectionConfig.homeServerUri.toString() == matrixOrgUrl) { + ServerType.MatrixOrg + } else { + serverTypeOverride ?: serverType + } + ) + } + + val data = try { + authenticationService.getLoginFlow(homeServerConnectionConfig) + } catch (failure: Throwable) { + _viewEvents.post(FTUEViewEvents.Failure(failure)) + setState { + copy( + asyncHomeServerLoginFlowRequest = Uninitialized, + // If we were trying to retrieve matrix.org login flow, also reset the serverType + serverType = if (serverType == ServerType.MatrixOrg) ServerType.Unknown else serverType + ) + } + null + } + + data ?: return@launch + + // Valid Homeserver, add it to the history. + // Note: we add what the user has input, data.homeServerUrlBase can be different + rememberHomeServer(homeServerConnectionConfig.homeServerUri.toString()) + + val loginMode = when { + // SSO login is taken first + data.supportedLoginTypes.contains(LoginFlowTypes.SSO) && + data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.SsoAndPassword(data.ssoIdentityProviders) + data.supportedLoginTypes.contains(LoginFlowTypes.SSO) -> LoginMode.Sso(data.ssoIdentityProviders) + data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.Password + else -> LoginMode.Unsupported + } + + setState { + copy( + asyncHomeServerLoginFlowRequest = Uninitialized, + homeServerUrlFromUser = homeServerConnectionConfig.homeServerUri.toString(), + homeServerUrl = data.homeServerUrl, + loginMode = loginMode, + loginModeSupportedTypes = data.supportedLoginTypes.toList() + ) + } + if ((loginMode == LoginMode.Password && !data.isLoginAndRegistrationSupported) || + data.isOutdatedHomeserver) { + // Notify the UI + _viewEvents.post(FTUEViewEvents.OutdatedHomeserver) + } + _viewEvents.post(FTUEViewEvents.OnLoginFlowRetrieved) + } + } + + fun getInitialHomeServerUrl(): String? { + return loginConfig?.homeServerUrl + } + + fun getSsoUrl(redirectUrl: String, deviceId: String?, providerId: String?): String? { + return authenticationService.getSsoUrl(redirectUrl, deviceId, providerId) + } + + fun getFallbackUrl(forSignIn: Boolean, deviceId: String?): String? { + return authenticationService.getFallbackUrl(forSignIn, deviceId) + } +} diff --git a/vector/src/main/java/im/vector/app/features/ftue/FTUEViewState.kt b/vector/src/main/java/im/vector/app/features/ftue/FTUEViewState.kt new file mode 100644 index 0000000000..9f34ed3782 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/ftue/FTUEViewState.kt @@ -0,0 +1,76 @@ +/* + * Copyright 2019 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.ftue + +import com.airbnb.mvrx.Async +import com.airbnb.mvrx.Loading +import com.airbnb.mvrx.MavericksState +import com.airbnb.mvrx.PersistState +import com.airbnb.mvrx.Success +import com.airbnb.mvrx.Uninitialized +import im.vector.app.features.login.LoginMode +import im.vector.app.features.login.ServerType +import im.vector.app.features.login.SignMode + +data class FTUEViewState( + val asyncLoginAction: Async = Uninitialized, + val asyncHomeServerLoginFlowRequest: Async = Uninitialized, + val asyncResetPassword: Async = Uninitialized, + val asyncResetMailConfirmed: Async = Uninitialized, + val asyncRegistration: Async = Uninitialized, + + // User choices + @PersistState + val serverType: ServerType = ServerType.Unknown, + @PersistState + val signMode: SignMode = SignMode.Unknown, + @PersistState + val resetPasswordEmail: String? = null, + @PersistState + val homeServerUrlFromUser: String? = null, + + // Can be modified after a Wellknown request + @PersistState + val homeServerUrl: String? = null, + + // For SSO session recovery + @PersistState + val deviceId: String? = null, + + // Network result + @PersistState + val loginMode: LoginMode = LoginMode.Unknown, + // Supported types for the login. We cannot use a sealed class for LoginType because it is not serializable + @PersistState + val loginModeSupportedTypes: List = emptyList(), + val knownCustomHomeServersUrls: List = emptyList() +) : MavericksState { + + fun isLoading(): Boolean { + return asyncLoginAction is Loading || + asyncHomeServerLoginFlowRequest is Loading || + asyncResetPassword is Loading || + asyncResetMailConfirmed is Loading || + asyncRegistration is Loading || + // Keep loading when it is success because of the delay to switch to the next Activity + asyncLoginAction is Success + } + + fun isUserLogged(): Boolean { + return asyncLoginAction is Success + } +} From 98078da13d830a87a08b25398b74642803348772 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Thu, 9 Dec 2021 11:03:34 +0000 Subject: [PATCH 2/2] renaming the ftue domain to onboarding and ftue to ftue auth --- vector/build.gradle | 2 +- .../features/DebugFeaturesStateFactory.kt | 6 +- .../debug/features/DebugVectorFeatures.kt | 4 +- vector/src/main/AndroidManifest.xml | 2 +- .../app/core/di/MavericksViewModelModule.kt | 6 +- .../im/vector/app/features/VectorFeatures.kt | 10 +- .../login2/created/AccountCreatedFragment.kt | 4 +- .../features/navigation/DefaultNavigator.kt | 19 +- .../Login2Variant.kt} | 8 +- .../OnboardingAction.kt} | 36 ++-- .../OnboardingActivity.kt} | 26 ++- .../OnboardingAuthVariant.kt} | 76 ++++---- .../features/onboarding/OnboardingVariant.kt | 25 +++ .../OnboardingVariantFactory.kt} | 17 +- .../OnboardingViewEvents.kt} | 34 ++-- .../OnboardingViewModel.kt} | 162 +++++++++--------- .../OnboardingViewState.kt} | 4 +- 17 files changed, 232 insertions(+), 209 deletions(-) rename vector/src/main/java/im/vector/app/features/{ftue/FTUEWipVariant.kt => onboarding/Login2Variant.kt} (99%) rename vector/src/main/java/im/vector/app/features/{ftue/FTUEAction.kt => onboarding/OnboardingAction.kt} (68%) rename vector/src/main/java/im/vector/app/features/{ftue/FTUEActivity.kt => onboarding/OnboardingActivity.kt} (71%) rename vector/src/main/java/im/vector/app/features/{ftue/DefaultFTUEVariant.kt => onboarding/OnboardingAuthVariant.kt} (84%) create mode 100644 vector/src/main/java/im/vector/app/features/onboarding/OnboardingVariant.kt rename vector/src/main/java/im/vector/app/features/{ftue/FTUEVariantFactory.kt => onboarding/OnboardingVariantFactory.kt} (65%) rename vector/src/main/java/im/vector/app/features/{ftue/FTUEViewEvents.kt => onboarding/OnboardingViewEvents.kt} (50%) rename vector/src/main/java/im/vector/app/features/{ftue/FTUEViewModel.kt => onboarding/OnboardingViewModel.kt} (79%) rename vector/src/main/java/im/vector/app/features/{ftue/FTUEViewState.kt => onboarding/OnboardingViewState.kt} (97%) diff --git a/vector/build.gradle b/vector/build.gradle index f952f76775..fcbdf67d92 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -140,7 +140,7 @@ android { buildConfigField "String", "BUILD_NUMBER", "\"${buildNumber}\"" resValue "string", "build_number", "\"${buildNumber}\"" - buildConfigField "im.vector.app.features.VectorFeatures.LoginVariant", "LOGIN_VARIANT", "im.vector.app.features.VectorFeatures.LoginVariant.LEGACY" + buildConfigField "im.vector.app.features.VectorFeatures.OnboardingVariant", "ONBOARDING_VARIANT", "im.vector.app.features.VectorFeatures.OnboardingVariant.LEGACY" buildConfigField "im.vector.app.features.crypto.keysrequest.OutboundSessionKeySharingStrategy", "outboundSessionKeySharingStrategy", "im.vector.app.features.crypto.keysrequest.OutboundSessionKeySharingStrategy.WhenTyping" diff --git a/vector/src/debug/java/im/vector/app/features/debug/features/DebugFeaturesStateFactory.kt b/vector/src/debug/java/im/vector/app/features/debug/features/DebugFeaturesStateFactory.kt index ca5d26aaeb..637071dd59 100644 --- a/vector/src/debug/java/im/vector/app/features/debug/features/DebugFeaturesStateFactory.kt +++ b/vector/src/debug/java/im/vector/app/features/debug/features/DebugFeaturesStateFactory.kt @@ -27,9 +27,9 @@ class DebugFeaturesStateFactory @Inject constructor( fun create(): FeaturesState { return FeaturesState(listOf( createEnumFeature( - label = "Login version", - selection = debugFeatures.loginVariant(), - default = defaultFeatures.loginVariant() + label = "Onboarding variant", + selection = debugFeatures.onboardingVariant(), + default = defaultFeatures.onboardingVariant() ) )) } diff --git a/vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt b/vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt index 638509e76b..4dbb6a5698 100644 --- a/vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt +++ b/vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt @@ -38,8 +38,8 @@ class DebugVectorFeatures( private val dataStore = context.dataStore - override fun loginVariant(): VectorFeatures.LoginVariant { - return readPreferences().getEnum() ?: vectorFeatures.loginVariant() + override fun onboardingVariant(): VectorFeatures.OnboardingVariant { + return readPreferences().getEnum() ?: vectorFeatures.onboardingVariant() } fun > hasEnumOverride(type: KClass) = readPreferences().containsEnum(type) diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml index 683aebe754..14796f9d2e 100644 --- a/vector/src/main/AndroidManifest.xml +++ b/vector/src/main/AndroidManifest.xml @@ -137,7 +137,7 @@ android:windowSoftInputMode="adjustResize" /> diff --git a/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt b/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt index 2e33a6c682..04c23c7d3d 100644 --- a/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt +++ b/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt @@ -37,7 +37,6 @@ import im.vector.app.features.crypto.verification.emoji.VerificationEmojiCodeVie import im.vector.app.features.devtools.RoomDevToolViewModel import im.vector.app.features.discovery.DiscoverySettingsViewModel import im.vector.app.features.discovery.change.SetIdentityServerViewModel -import im.vector.app.features.ftue.FTUEViewModel import im.vector.app.features.home.HomeActivityViewModel import im.vector.app.features.home.HomeDetailViewModel import im.vector.app.features.home.PromoteRestrictedViewModel @@ -58,6 +57,7 @@ import im.vector.app.features.login.LoginViewModel import im.vector.app.features.login2.LoginViewModel2 import im.vector.app.features.login2.created.AccountCreatedViewModel import im.vector.app.features.matrixto.MatrixToBottomSheetViewModel +import im.vector.app.features.onboarding.OnboardingViewModel import im.vector.app.features.poll.create.CreatePollViewModel import im.vector.app.features.rageshake.BugReportViewModel import im.vector.app.features.reactions.EmojiSearchResultViewModel @@ -450,8 +450,8 @@ interface MavericksViewModelModule { @Binds @IntoMap - @MavericksViewModelKey(FTUEViewModel::class) - fun ftueViewModelFactory(factory: FTUEViewModel.Factory): MavericksAssistedViewModelFactory<*, *> + @MavericksViewModelKey(OnboardingViewModel::class) + fun ftueViewModelFactory(factory: OnboardingViewModel.Factory): MavericksAssistedViewModelFactory<*, *> @Binds @IntoMap diff --git a/vector/src/main/java/im/vector/app/features/VectorFeatures.kt b/vector/src/main/java/im/vector/app/features/VectorFeatures.kt index 58594be293..4228c6ebcd 100644 --- a/vector/src/main/java/im/vector/app/features/VectorFeatures.kt +++ b/vector/src/main/java/im/vector/app/features/VectorFeatures.kt @@ -20,12 +20,12 @@ import im.vector.app.BuildConfig interface VectorFeatures { - fun loginVariant(): LoginVariant + fun onboardingVariant(): OnboardingVariant - enum class LoginVariant { + enum class OnboardingVariant { LEGACY, - FTUE, - FTUE_WIP + LOGIN_2, + FTUE_AUTH } enum class NotificationSettingsVersion { @@ -35,5 +35,5 @@ interface VectorFeatures { } class DefaultVectorFeatures : VectorFeatures { - override fun loginVariant(): VectorFeatures.LoginVariant = BuildConfig.LOGIN_VARIANT + override fun onboardingVariant(): VectorFeatures.OnboardingVariant = BuildConfig.ONBOARDING_VARIANT } diff --git a/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedFragment.kt b/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedFragment.kt index 92fd26e5e8..8223053ad8 100644 --- a/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedFragment.kt +++ b/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedFragment.kt @@ -34,12 +34,12 @@ import im.vector.app.core.resources.ColorProvider import im.vector.app.databinding.DialogBaseEditTextBinding import im.vector.app.databinding.FragmentLoginAccountCreatedBinding import im.vector.app.features.displayname.getBestName -import im.vector.app.features.ftue.FTUEActivity import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.room.detail.timeline.helper.MatrixItemColorProvider import im.vector.app.features.login2.AbstractLoginFragment2 import im.vector.app.features.login2.LoginAction2 import im.vector.app.features.login2.LoginViewState2 +import im.vector.app.features.onboarding.OnboardingActivity import org.matrix.android.sdk.api.util.MatrixItem import java.util.UUID import javax.inject.Inject @@ -130,7 +130,7 @@ class AccountCreatedFragment @Inject constructor( private fun invalidateState(state: AccountCreatedViewState) { // Ugly hack... - (activity as? FTUEActivity)?.setIsLoading(state.isLoading) + (activity as? OnboardingActivity)?.setIsLoading(state.isLoading) views.loginAccountCreatedSubtitle.text = getString(R.string.login_account_created_subtitle, state.userId) diff --git a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt index 393406a07d..30ead8a6bf 100644 --- a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt +++ b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt @@ -38,6 +38,7 @@ import im.vector.app.core.error.fatalError import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.core.utils.toast import im.vector.app.features.VectorFeatures +import im.vector.app.features.VectorFeatures.OnboardingVariant import im.vector.app.features.analytics.ui.consent.AnalyticsOptInActivity import im.vector.app.features.call.conference.JitsiCallViewModel import im.vector.app.features.call.conference.VectorJitsiActivity @@ -51,7 +52,6 @@ import im.vector.app.features.crypto.verification.SupportedVerificationMethodsPr import im.vector.app.features.crypto.verification.VerificationBottomSheet import im.vector.app.features.debug.DebugMenuActivity import im.vector.app.features.devtools.RoomDevToolActivity -import im.vector.app.features.ftue.FTUEActivity import im.vector.app.features.home.room.detail.RoomDetailActivity import im.vector.app.features.home.room.detail.RoomDetailArgs import im.vector.app.features.home.room.detail.search.SearchActivity @@ -64,6 +64,7 @@ import im.vector.app.features.matrixto.MatrixToBottomSheet import im.vector.app.features.media.AttachmentData import im.vector.app.features.media.BigImageViewerActivity import im.vector.app.features.media.VectorAttachmentViewerActivity +import im.vector.app.features.onboarding.OnboardingActivity import im.vector.app.features.pin.PinActivity import im.vector.app.features.pin.PinArgs import im.vector.app.features.pin.PinMode @@ -111,20 +112,20 @@ class DefaultNavigator @Inject constructor( ) : Navigator { override fun openLogin(context: Context, loginConfig: LoginConfig?, flags: Int) { - val intent = when (features.loginVariant()) { - VectorFeatures.LoginVariant.LEGACY -> LoginActivity.newIntent(context, loginConfig) - VectorFeatures.LoginVariant.FTUE, - VectorFeatures.LoginVariant.FTUE_WIP -> FTUEActivity.newIntent(context, loginConfig) + val intent = when (features.onboardingVariant()) { + OnboardingVariant.LEGACY -> LoginActivity.newIntent(context, loginConfig) + OnboardingVariant.LOGIN_2, + OnboardingVariant.FTUE_AUTH -> OnboardingActivity.newIntent(context, loginConfig) } intent.addFlags(flags) context.startActivity(intent) } override fun loginSSORedirect(context: Context, data: Uri?) { - val intent = when (features.loginVariant()) { - VectorFeatures.LoginVariant.LEGACY -> LoginActivity.redirectIntent(context, data) - VectorFeatures.LoginVariant.FTUE, - VectorFeatures.LoginVariant.FTUE_WIP -> FTUEActivity.redirectIntent(context, data) + val intent = when (features.onboardingVariant()) { + OnboardingVariant.LEGACY -> LoginActivity.redirectIntent(context, data) + OnboardingVariant.LOGIN_2, + OnboardingVariant.FTUE_AUTH -> OnboardingActivity.redirectIntent(context, data) } context.startActivity(intent) } diff --git a/vector/src/main/java/im/vector/app/features/ftue/FTUEWipVariant.kt b/vector/src/main/java/im/vector/app/features/onboarding/Login2Variant.kt similarity index 99% rename from vector/src/main/java/im/vector/app/features/ftue/FTUEWipVariant.kt rename to vector/src/main/java/im/vector/app/features/onboarding/Login2Variant.kt index c1fc49db00..107c08da5a 100644 --- a/vector/src/main/java/im/vector/app/features/ftue/FTUEWipVariant.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/Login2Variant.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package im.vector.app.features.ftue +package im.vector.app.features.onboarding import android.content.Intent import android.view.View @@ -72,12 +72,12 @@ import org.matrix.android.sdk.api.extensions.tryOrNull private const val FRAGMENT_REGISTRATION_STAGE_TAG = "FRAGMENT_REGISTRATION_STAGE_TAG" private const val FRAGMENT_LOGIN_TAG = "FRAGMENT_LOGIN_TAG" -class FTUEWipVariant( +class Login2Variant( private val views: ActivityLoginBinding, private val loginViewModel: LoginViewModel2, private val activity: VectorBaseActivity, private val supportFragmentManager: FragmentManager -) : FTUEVariant { +) : OnboardingVariant { private val enterAnim = R.anim.enter_fade_in private val exitAnim = R.anim.exit_fade_out @@ -112,7 +112,7 @@ class FTUEWipVariant( } // Get config extra - val loginConfig = activity.intent.getParcelableExtra(FTUEActivity.EXTRA_CONFIG) + val loginConfig = activity.intent.getParcelableExtra(OnboardingActivity.EXTRA_CONFIG) if (isFirstCreation) { // TODO Check this loginViewModel.handle(LoginAction2.InitWith(loginConfig)) diff --git a/vector/src/main/java/im/vector/app/features/ftue/FTUEAction.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingAction.kt similarity index 68% rename from vector/src/main/java/im/vector/app/features/ftue/FTUEAction.kt rename to vector/src/main/java/im/vector/app/features/onboarding/OnboardingAction.kt index b43ffd3331..4af0825044 100644 --- a/vector/src/main/java/im/vector/app/features/ftue/FTUEAction.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingAction.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package im.vector.app.features.ftue +package im.vector.app.features.onboarding import im.vector.app.core.platform.VectorViewModelAction import im.vector.app.features.login.LoginConfig @@ -25,23 +25,23 @@ import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider import org.matrix.android.sdk.api.auth.registration.RegisterThreePid import org.matrix.android.sdk.internal.network.ssl.Fingerprint -sealed class FTUEAction : VectorViewModelAction { - data class OnGetStarted(val resetLoginConfig: Boolean) : FTUEAction() +sealed class OnboardingAction : VectorViewModelAction { + data class OnGetStarted(val resetLoginConfig: Boolean) : OnboardingAction() - data class UpdateServerType(val serverType: ServerType) : FTUEAction() - data class UpdateHomeServer(val homeServerUrl: String) : FTUEAction() - data class UpdateSignMode(val signMode: SignMode) : FTUEAction() - data class LoginWithToken(val loginToken: String) : FTUEAction() - data class WebLoginSuccess(val credentials: Credentials) : FTUEAction() - data class InitWith(val loginConfig: LoginConfig?) : FTUEAction() - data class ResetPassword(val email: String, val newPassword: String) : FTUEAction() - object ResetPasswordMailConfirmed : FTUEAction() + data class UpdateServerType(val serverType: ServerType) : OnboardingAction() + data class UpdateHomeServer(val homeServerUrl: String) : OnboardingAction() + data class UpdateSignMode(val signMode: SignMode) : OnboardingAction() + data class LoginWithToken(val loginToken: String) : OnboardingAction() + data class WebLoginSuccess(val credentials: Credentials) : OnboardingAction() + data class InitWith(val loginConfig: LoginConfig?) : OnboardingAction() + data class ResetPassword(val email: String, val newPassword: String) : OnboardingAction() + object ResetPasswordMailConfirmed : OnboardingAction() // Login or Register, depending on the signMode - data class LoginOrRegister(val username: String, val password: String, val initialDeviceName: String) : FTUEAction() + data class LoginOrRegister(val username: String, val password: String, val initialDeviceName: String) : OnboardingAction() // Register actions - open class RegisterAction : FTUEAction() + open class RegisterAction : OnboardingAction() data class AddThreePid(val threePid: RegisterThreePid) : RegisterAction() object SendAgainThreePid : RegisterAction() @@ -57,7 +57,7 @@ sealed class FTUEAction : VectorViewModelAction { object RegisterDummy : RegisterAction() // Reset actions - open class ResetAction : FTUEAction() + open class ResetAction : OnboardingAction() object ResetHomeServerType : ResetAction() object ResetHomeServerUrl : ResetAction() @@ -66,14 +66,14 @@ sealed class FTUEAction : VectorViewModelAction { object ResetResetPassword : ResetAction() // Homeserver history - object ClearHomeServerHistory : FTUEAction() + object ClearHomeServerHistory : OnboardingAction() // For the soft logout case data class SetupSsoForSessionRecovery(val homeServerUrl: String, val deviceId: String, - val ssoIdentityProviders: List?) : FTUEAction() + val ssoIdentityProviders: List?) : OnboardingAction() - data class PostViewEvent(val viewEvent: FTUEViewEvents) : FTUEAction() + data class PostViewEvent(val viewEvent: OnboardingViewEvents) : OnboardingAction() - data class UserAcceptCertificate(val fingerprint: Fingerprint) : FTUEAction() + data class UserAcceptCertificate(val fingerprint: Fingerprint) : OnboardingAction() } diff --git a/vector/src/main/java/im/vector/app/features/ftue/FTUEActivity.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingActivity.kt similarity index 71% rename from vector/src/main/java/im/vector/app/features/ftue/FTUEActivity.kt rename to vector/src/main/java/im/vector/app/features/onboarding/OnboardingActivity.kt index c54c0547a4..0db9fb0c76 100644 --- a/vector/src/main/java/im/vector/app/features/ftue/FTUEActivity.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingActivity.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package im.vector.app.features.ftue +package im.vector.app.features.onboarding import android.content.Context import android.content.Intent @@ -31,13 +31,13 @@ import im.vector.app.features.pin.UnlockedActivity import javax.inject.Inject @AndroidEntryPoint -class FTUEActivity : VectorBaseActivity(), ToolbarConfigurable, UnlockedActivity { +class OnboardingActivity : VectorBaseActivity(), ToolbarConfigurable, UnlockedActivity { - private val ftueVariant by lifecycleAwareLazy { - ftueVariantFactory.create(this, ftueViewModel = lazyViewModel(), loginViewModel2 = lazyViewModel()) + private val onboardingVariant by lifecycleAwareLazy { + onboardingVariantFactory.create(this, onboardingViewModel = lazyViewModel(), loginViewModel2 = lazyViewModel()) } - @Inject lateinit var ftueVariantFactory: FTUEVariantFactory + @Inject lateinit var onboardingVariantFactory: OnboardingVariantFactory override fun getBinding() = ActivityLoginBinding.inflate(layoutInflater) @@ -49,37 +49,31 @@ class FTUEActivity : VectorBaseActivity(), ToolbarConfigur override fun onNewIntent(intent: Intent?) { super.onNewIntent(intent) - ftueVariant.onNewIntent(intent) + onboardingVariant.onNewIntent(intent) } override fun initUiAndData() { - ftueVariant.initUiAndData(isFirstCreation()) + onboardingVariant.initUiAndData(isFirstCreation()) } // Hack for AccountCreatedFragment fun setIsLoading(isLoading: Boolean) { - ftueVariant.setIsLoading(isLoading) + onboardingVariant.setIsLoading(isLoading) } companion object { const val EXTRA_CONFIG = "EXTRA_CONFIG" fun newIntent(context: Context, loginConfig: LoginConfig?): Intent { - return Intent(context, FTUEActivity::class.java).apply { + return Intent(context, OnboardingActivity::class.java).apply { putExtra(EXTRA_CONFIG, loginConfig) } } fun redirectIntent(context: Context, data: Uri?): Intent { - return Intent(context, FTUEActivity::class.java).apply { + return Intent(context, OnboardingActivity::class.java).apply { setData(data) } } } } - -interface FTUEVariant { - fun onNewIntent(intent: Intent?) - fun initUiAndData(isFirstCreation: Boolean) - fun setIsLoading(isLoading: Boolean) -} diff --git a/vector/src/main/java/im/vector/app/features/ftue/DefaultFTUEVariant.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingAuthVariant.kt similarity index 84% rename from vector/src/main/java/im/vector/app/features/ftue/DefaultFTUEVariant.kt rename to vector/src/main/java/im/vector/app/features/onboarding/OnboardingAuthVariant.kt index 00a9262a1f..8e26a17138 100644 --- a/vector/src/main/java/im/vector/app/features/ftue/DefaultFTUEVariant.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingAuthVariant.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package im.vector.app.features.ftue +package im.vector.app.features.onboarding import android.content.Intent import android.view.View @@ -66,12 +66,12 @@ import org.matrix.android.sdk.api.extensions.tryOrNull private const val FRAGMENT_REGISTRATION_STAGE_TAG = "FRAGMENT_REGISTRATION_STAGE_TAG" private const val FRAGMENT_LOGIN_TAG = "FRAGMENT_LOGIN_TAG" -class DefaultFTUEVariant( +class OnboardingAuthVariant( private val views: ActivityLoginBinding, - private val ftueViewModel: FTUEViewModel, + private val onboardingViewModel: OnboardingViewModel, private val activity: VectorBaseActivity, private val supportFragmentManager: FragmentManager -) : FTUEVariant { +) : OnboardingVariant { private val enterAnim = R.anim.enter_fade_in private val exitAnim = R.anim.exit_fade_out @@ -99,16 +99,16 @@ class DefaultFTUEVariant( } with(activity) { - ftueViewModel.onEach { + onboardingViewModel.onEach { updateWithState(it) } - ftueViewModel.observeViewEvents { handleLoginViewEvents(it) } + onboardingViewModel.observeViewEvents { handleLoginViewEvents(it) } } // Get config extra - val loginConfig = activity.intent.getParcelableExtra(FTUEActivity.EXTRA_CONFIG) + val loginConfig = activity.intent.getParcelableExtra(OnboardingActivity.EXTRA_CONFIG) if (isFirstCreation) { - ftueViewModel.handle(FTUEAction.InitWith(loginConfig)) + onboardingViewModel.handle(OnboardingAction.InitWith(loginConfig)) } } @@ -120,17 +120,17 @@ class DefaultFTUEVariant( activity.addFragment(views.loginFragmentContainer, LoginSplashFragment::class.java) } - private fun handleLoginViewEvents(ftueViewEvents: FTUEViewEvents) { - when (ftueViewEvents) { - is FTUEViewEvents.RegistrationFlowResult -> { + private fun handleLoginViewEvents(onboardingViewEvents: OnboardingViewEvents) { + when (onboardingViewEvents) { + is OnboardingViewEvents.RegistrationFlowResult -> { // Check that all flows are supported by the application - if (ftueViewEvents.flowResult.missingStages.any { !it.isSupported() }) { + if (onboardingViewEvents.flowResult.missingStages.any { !it.isSupported() }) { // Display a popup to propose use web fallback onRegistrationStageNotSupported() } else { - if (ftueViewEvents.isRegistrationStarted) { + if (onboardingViewEvents.isRegistrationStarted) { // Go on with registration flow - handleRegistrationNavigation(ftueViewEvents.flowResult) + handleRegistrationNavigation(onboardingViewEvents.flowResult) } else { // First ask for login and password // I add a tag to indicate that this fragment is a registration stage. @@ -143,7 +143,7 @@ class DefaultFTUEVariant( } } } - is FTUEViewEvents.OutdatedHomeserver -> { + is OnboardingViewEvents.OutdatedHomeserver -> { MaterialAlertDialogBuilder(activity) .setTitle(R.string.login_error_outdated_homeserver_title) .setMessage(R.string.login_error_outdated_homeserver_warning_content) @@ -151,7 +151,7 @@ class DefaultFTUEVariant( .show() Unit } - is FTUEViewEvents.OpenServerSelection -> + is OnboardingViewEvents.OpenServerSelection -> activity.addFragmentToBackstack(views.loginFragmentContainer, LoginServerSelectionFragment::class.java, option = { ft -> @@ -163,63 +163,63 @@ class DefaultFTUEVariant( // TODO Disabled because it provokes a flickering // ft.setCustomAnimations(enterAnim, exitAnim, popEnterAnim, popExitAnim) }) - is FTUEViewEvents.OnServerSelectionDone -> onServerSelectionDone(ftueViewEvents) - is FTUEViewEvents.OnSignModeSelected -> onSignModeSelected(ftueViewEvents) - is FTUEViewEvents.OnLoginFlowRetrieved -> + is OnboardingViewEvents.OnServerSelectionDone -> onServerSelectionDone(onboardingViewEvents) + is OnboardingViewEvents.OnSignModeSelected -> onSignModeSelected(onboardingViewEvents) + is OnboardingViewEvents.OnLoginFlowRetrieved -> activity.addFragmentToBackstack(views.loginFragmentContainer, LoginSignUpSignInSelectionFragment::class.java, option = commonOption) - is FTUEViewEvents.OnWebLoginError -> onWebLoginError(ftueViewEvents) - is FTUEViewEvents.OnForgetPasswordClicked -> + is OnboardingViewEvents.OnWebLoginError -> onWebLoginError(onboardingViewEvents) + is OnboardingViewEvents.OnForgetPasswordClicked -> activity.addFragmentToBackstack(views.loginFragmentContainer, LoginResetPasswordFragment::class.java, option = commonOption) - is FTUEViewEvents.OnResetPasswordSendThreePidDone -> { + is OnboardingViewEvents.OnResetPasswordSendThreePidDone -> { supportFragmentManager.popBackStack(FRAGMENT_LOGIN_TAG, POP_BACK_STACK_EXCLUSIVE) activity.addFragmentToBackstack(views.loginFragmentContainer, LoginResetPasswordMailConfirmationFragment::class.java, option = commonOption) } - is FTUEViewEvents.OnResetPasswordMailConfirmationSuccess -> { + is OnboardingViewEvents.OnResetPasswordMailConfirmationSuccess -> { supportFragmentManager.popBackStack(FRAGMENT_LOGIN_TAG, POP_BACK_STACK_EXCLUSIVE) activity.addFragmentToBackstack(views.loginFragmentContainer, LoginResetPasswordSuccessFragment::class.java, option = commonOption) } - is FTUEViewEvents.OnResetPasswordMailConfirmationSuccessDone -> { + is OnboardingViewEvents.OnResetPasswordMailConfirmationSuccessDone -> { // Go back to the login fragment supportFragmentManager.popBackStack(FRAGMENT_LOGIN_TAG, POP_BACK_STACK_EXCLUSIVE) } - is FTUEViewEvents.OnSendEmailSuccess -> { + is OnboardingViewEvents.OnSendEmailSuccess -> { // Pop the enter email Fragment supportFragmentManager.popBackStack(FRAGMENT_REGISTRATION_STAGE_TAG, FragmentManager.POP_BACK_STACK_INCLUSIVE) activity.addFragmentToBackstack(views.loginFragmentContainer, LoginWaitForEmailFragment::class.java, - LoginWaitForEmailFragmentArgument(ftueViewEvents.email), + LoginWaitForEmailFragmentArgument(onboardingViewEvents.email), tag = FRAGMENT_REGISTRATION_STAGE_TAG, option = commonOption) } - is FTUEViewEvents.OnSendMsisdnSuccess -> { + is OnboardingViewEvents.OnSendMsisdnSuccess -> { // Pop the enter Msisdn Fragment supportFragmentManager.popBackStack(FRAGMENT_REGISTRATION_STAGE_TAG, FragmentManager.POP_BACK_STACK_INCLUSIVE) activity.addFragmentToBackstack(views.loginFragmentContainer, LoginGenericTextInputFormFragment::class.java, - LoginGenericTextInputFormFragmentArgument(TextInputFormFragmentMode.ConfirmMsisdn, true, ftueViewEvents.msisdn), + LoginGenericTextInputFormFragmentArgument(TextInputFormFragmentMode.ConfirmMsisdn, true, onboardingViewEvents.msisdn), tag = FRAGMENT_REGISTRATION_STAGE_TAG, option = commonOption) } - is FTUEViewEvents.Failure, - is FTUEViewEvents.Loading -> + is OnboardingViewEvents.Failure, + is OnboardingViewEvents.Loading -> // This is handled by the Fragments Unit }.exhaustive } - private fun updateWithState(ftueViewState: FTUEViewState) { - if (ftueViewState.isUserLogged()) { + private fun updateWithState(onboardingViewState: OnboardingViewState) { + if (onboardingViewState.isUserLogged()) { val intent = HomeActivity.newIntent( activity, - accountCreation = ftueViewState.signMode == SignMode.SignUp + accountCreation = onboardingViewState.signMode == SignMode.SignUp ) activity.startActivity(intent) activity.finish() @@ -227,10 +227,10 @@ class DefaultFTUEVariant( } // Loading - views.loginLoading.isVisible = ftueViewState.isLoading() + views.loginLoading.isVisible = onboardingViewState.isLoading() } - private fun onWebLoginError(onWebLoginError: FTUEViewEvents.OnWebLoginError) { + private fun onWebLoginError(onWebLoginError: OnboardingViewEvents.OnWebLoginError) { // Pop the backstack supportFragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE) @@ -242,7 +242,7 @@ class DefaultFTUEVariant( .show() } - private fun onServerSelectionDone(loginViewEvents: FTUEViewEvents.OnServerSelectionDone) { + private fun onServerSelectionDone(loginViewEvents: OnboardingViewEvents.OnServerSelectionDone) { when (loginViewEvents.serverType) { ServerType.MatrixOrg -> Unit // In this case, we wait for the login flow ServerType.EMS, @@ -253,7 +253,7 @@ class DefaultFTUEVariant( } } - private fun onSignModeSelected(loginViewEvents: FTUEViewEvents.OnSignModeSelected) = withState(ftueViewModel) { state -> + private fun onSignModeSelected(loginViewEvents: OnboardingViewEvents.OnSignModeSelected) = withState(onboardingViewModel) { state -> // state.signMode could not be ready yet. So use value from the ViewEvent when (loginViewEvents.signMode) { SignMode.Unknown -> error("Sign mode has to be set before calling this method") @@ -286,7 +286,7 @@ class DefaultFTUEVariant( override fun onNewIntent(intent: Intent?) { intent?.data ?.let { tryOrNull { it.getQueryParameter("loginToken") } } - ?.let { ftueViewModel.handle(FTUEAction.LoginWithToken(it)) } + ?.let { onboardingViewModel.handle(OnboardingAction.LoginWithToken(it)) } } private fun onRegistrationStageNotSupported() { diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingVariant.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingVariant.kt new file mode 100644 index 0000000000..91c125fa5b --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingVariant.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.onboarding + +import android.content.Intent + +interface OnboardingVariant { + fun onNewIntent(intent: Intent?) + fun initUiAndData(isFirstCreation: Boolean) + fun setIsLoading(isLoading: Boolean) +} diff --git a/vector/src/main/java/im/vector/app/features/ftue/FTUEVariantFactory.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingVariantFactory.kt similarity index 65% rename from vector/src/main/java/im/vector/app/features/ftue/FTUEVariantFactory.kt rename to vector/src/main/java/im/vector/app/features/onboarding/OnboardingVariantFactory.kt index aabd0338a2..ea0ada56ba 100644 --- a/vector/src/main/java/im/vector/app/features/ftue/FTUEVariantFactory.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingVariantFactory.kt @@ -14,25 +14,28 @@ * limitations under the License. */ -package im.vector.app.features.ftue +package im.vector.app.features.onboarding import im.vector.app.features.VectorFeatures import im.vector.app.features.login2.LoginViewModel2 import javax.inject.Inject -class FTUEVariantFactory @Inject constructor( +class OnboardingVariantFactory @Inject constructor( private val vectorFeatures: VectorFeatures, ) { - fun create(activity: FTUEActivity, ftueViewModel: Lazy, loginViewModel2: Lazy) = when (vectorFeatures.loginVariant()) { - VectorFeatures.LoginVariant.LEGACY -> error("Legacy is not supported by the FTUE") - VectorFeatures.LoginVariant.FTUE -> DefaultFTUEVariant( + fun create(activity: OnboardingActivity, + onboardingViewModel: Lazy, + loginViewModel2: Lazy + ) = when (vectorFeatures.onboardingVariant()) { + VectorFeatures.OnboardingVariant.LEGACY -> error("Legacy is not supported by the FTUE") + VectorFeatures.OnboardingVariant.FTUE_AUTH -> OnboardingAuthVariant( views = activity.getBinding(), - ftueViewModel = ftueViewModel.value, + onboardingViewModel = onboardingViewModel.value, activity = activity, supportFragmentManager = activity.supportFragmentManager ) - VectorFeatures.LoginVariant.FTUE_WIP -> FTUEWipVariant( + VectorFeatures.OnboardingVariant.LOGIN_2 -> Login2Variant( views = activity.getBinding(), loginViewModel = loginViewModel2.value, activity = activity, diff --git a/vector/src/main/java/im/vector/app/features/ftue/FTUEViewEvents.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewEvents.kt similarity index 50% rename from vector/src/main/java/im/vector/app/features/ftue/FTUEViewEvents.kt rename to vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewEvents.kt index d10063c797..ab782a9908 100644 --- a/vector/src/main/java/im/vector/app/features/ftue/FTUEViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewEvents.kt @@ -15,7 +15,7 @@ * */ -package im.vector.app.features.ftue +package im.vector.app.features.onboarding import im.vector.app.core.platform.VectorViewEvents import im.vector.app.features.login.ServerType @@ -25,26 +25,26 @@ import org.matrix.android.sdk.api.auth.registration.FlowResult /** * Transient events for Login */ -sealed class FTUEViewEvents : VectorViewEvents { - data class Loading(val message: CharSequence? = null) : FTUEViewEvents() - data class Failure(val throwable: Throwable) : FTUEViewEvents() +sealed class OnboardingViewEvents : VectorViewEvents { + data class Loading(val message: CharSequence? = null) : OnboardingViewEvents() + data class Failure(val throwable: Throwable) : OnboardingViewEvents() - data class RegistrationFlowResult(val flowResult: FlowResult, val isRegistrationStarted: Boolean) : FTUEViewEvents() - object OutdatedHomeserver : FTUEViewEvents() + data class RegistrationFlowResult(val flowResult: FlowResult, val isRegistrationStarted: Boolean) : OnboardingViewEvents() + object OutdatedHomeserver : OnboardingViewEvents() // Navigation event - object OpenServerSelection : FTUEViewEvents() - data class OnServerSelectionDone(val serverType: ServerType) : FTUEViewEvents() - object OnLoginFlowRetrieved : FTUEViewEvents() - data class OnSignModeSelected(val signMode: SignMode) : FTUEViewEvents() - object OnForgetPasswordClicked : FTUEViewEvents() - object OnResetPasswordSendThreePidDone : FTUEViewEvents() - object OnResetPasswordMailConfirmationSuccess : FTUEViewEvents() - object OnResetPasswordMailConfirmationSuccessDone : FTUEViewEvents() + object OpenServerSelection : OnboardingViewEvents() + data class OnServerSelectionDone(val serverType: ServerType) : OnboardingViewEvents() + object OnLoginFlowRetrieved : OnboardingViewEvents() + data class OnSignModeSelected(val signMode: SignMode) : OnboardingViewEvents() + object OnForgetPasswordClicked : OnboardingViewEvents() + object OnResetPasswordSendThreePidDone : OnboardingViewEvents() + object OnResetPasswordMailConfirmationSuccess : OnboardingViewEvents() + object OnResetPasswordMailConfirmationSuccessDone : OnboardingViewEvents() - data class OnSendEmailSuccess(val email: String) : FTUEViewEvents() - data class OnSendMsisdnSuccess(val msisdn: String) : FTUEViewEvents() + data class OnSendEmailSuccess(val email: String) : OnboardingViewEvents() + data class OnSendMsisdnSuccess(val msisdn: String) : OnboardingViewEvents() - data class OnWebLoginError(val errorCode: Int, val description: String, val failingUrl: String) : FTUEViewEvents() + data class OnWebLoginError(val errorCode: Int, val description: String, val failingUrl: String) : OnboardingViewEvents() } diff --git a/vector/src/main/java/im/vector/app/features/ftue/FTUEViewModel.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt similarity index 79% rename from vector/src/main/java/im/vector/app/features/ftue/FTUEViewModel.kt rename to vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt index 4c78d6fd30..32a6d63f9b 100644 --- a/vector/src/main/java/im/vector/app/features/ftue/FTUEViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package im.vector.app.features.ftue +package im.vector.app.features.onboarding import android.content.Context import android.net.Uri @@ -63,8 +63,8 @@ import java.util.concurrent.CancellationException /** * */ -class FTUEViewModel @AssistedInject constructor( - @Assisted initialState: FTUEViewState, +class OnboardingViewModel @AssistedInject constructor( + @Assisted initialState: OnboardingViewState, private val applicationContext: Context, private val authenticationService: AuthenticationService, private val activeSessionHolder: ActiveSessionHolder, @@ -72,14 +72,14 @@ class FTUEViewModel @AssistedInject constructor( private val reAuthHelper: ReAuthHelper, private val stringProvider: StringProvider, private val homeServerHistoryService: HomeServerHistoryService -) : VectorViewModel(initialState) { +) : VectorViewModel(initialState) { @AssistedFactory - interface Factory : MavericksAssistedViewModelFactory { - override fun create(initialState: FTUEViewState): FTUEViewModel + interface Factory : MavericksAssistedViewModelFactory { + override fun create(initialState: OnboardingViewState): OnboardingViewModel } - companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() + companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() init { getKnownCustomHomeServersUrls() @@ -92,7 +92,7 @@ class FTUEViewModel @AssistedInject constructor( } // Store the last action, to redo it after user has trusted the untrusted certificate - private var lastAction: FTUEAction? = null + private var lastAction: OnboardingAction? = null private var currentHomeServerConnectionConfig: HomeServerConnectionConfig? = null private val matrixOrgUrl = stringProvider.getString(R.string.matrix_org_server_url).ensureTrailingSlash() @@ -119,28 +119,28 @@ class FTUEViewModel @AssistedInject constructor( field = value } - override fun handle(action: FTUEAction) { + override fun handle(action: OnboardingAction) { when (action) { - is FTUEAction.OnGetStarted -> handleOnGetStarted(action) - is FTUEAction.UpdateServerType -> handleUpdateServerType(action) - is FTUEAction.UpdateSignMode -> handleUpdateSignMode(action) - is FTUEAction.InitWith -> handleInitWith(action) - is FTUEAction.UpdateHomeServer -> handleUpdateHomeserver(action).also { lastAction = action } - is FTUEAction.LoginOrRegister -> handleLoginOrRegister(action).also { lastAction = action } - is FTUEAction.LoginWithToken -> handleLoginWithToken(action) - is FTUEAction.WebLoginSuccess -> handleWebLoginSuccess(action) - is FTUEAction.ResetPassword -> handleResetPassword(action) - is FTUEAction.ResetPasswordMailConfirmed -> handleResetPasswordMailConfirmed() - is FTUEAction.RegisterAction -> handleRegisterAction(action) - is FTUEAction.ResetAction -> handleResetAction(action) - is FTUEAction.SetupSsoForSessionRecovery -> handleSetupSsoForSessionRecovery(action) - is FTUEAction.UserAcceptCertificate -> handleUserAcceptCertificate(action) - FTUEAction.ClearHomeServerHistory -> handleClearHomeServerHistory() - is FTUEAction.PostViewEvent -> _viewEvents.post(action.viewEvent) + is OnboardingAction.OnGetStarted -> handleOnGetStarted(action) + is OnboardingAction.UpdateServerType -> handleUpdateServerType(action) + is OnboardingAction.UpdateSignMode -> handleUpdateSignMode(action) + is OnboardingAction.InitWith -> handleInitWith(action) + is OnboardingAction.UpdateHomeServer -> handleUpdateHomeserver(action).also { lastAction = action } + is OnboardingAction.LoginOrRegister -> handleLoginOrRegister(action).also { lastAction = action } + is OnboardingAction.LoginWithToken -> handleLoginWithToken(action) + is OnboardingAction.WebLoginSuccess -> handleWebLoginSuccess(action) + is OnboardingAction.ResetPassword -> handleResetPassword(action) + is OnboardingAction.ResetPasswordMailConfirmed -> handleResetPasswordMailConfirmed() + is OnboardingAction.RegisterAction -> handleRegisterAction(action) + is OnboardingAction.ResetAction -> handleResetAction(action) + is OnboardingAction.SetupSsoForSessionRecovery -> handleSetupSsoForSessionRecovery(action) + is OnboardingAction.UserAcceptCertificate -> handleUserAcceptCertificate(action) + OnboardingAction.ClearHomeServerHistory -> handleClearHomeServerHistory() + is OnboardingAction.PostViewEvent -> _viewEvents.post(action.viewEvent) }.exhaustive } - private fun handleOnGetStarted(action: FTUEAction.OnGetStarted) { + private fun handleOnGetStarted(action: OnboardingAction.OnGetStarted) { if (action.resetLoginConfig) { loginConfig = null } @@ -152,25 +152,25 @@ class FTUEViewModel @AssistedInject constructor( if (homeServerConnectionConfig == null) { // Url is invalid, in this case, just use the regular flow Timber.w("Url from config url was invalid: $configUrl") - _viewEvents.post(FTUEViewEvents.OpenServerSelection) + _viewEvents.post(OnboardingViewEvents.OpenServerSelection) } else { getLoginFlow(homeServerConnectionConfig, ServerType.Other) } } else { - _viewEvents.post(FTUEViewEvents.OpenServerSelection) + _viewEvents.post(OnboardingViewEvents.OpenServerSelection) } } - private fun handleUserAcceptCertificate(action: FTUEAction.UserAcceptCertificate) { + private fun handleUserAcceptCertificate(action: OnboardingAction.UserAcceptCertificate) { // It happens when we get the login flow, or during direct authentication. // So alter the homeserver config and retrieve again the login flow when (val finalLastAction = lastAction) { - is FTUEAction.UpdateHomeServer -> { + is OnboardingAction.UpdateHomeServer -> { currentHomeServerConnectionConfig ?.let { it.copy(allowedFingerprints = it.allowedFingerprints + action.fingerprint) } ?.let { getLoginFlow(it) } } - is FTUEAction.LoginOrRegister -> + is OnboardingAction.LoginOrRegister -> handleDirectLogin( finalLastAction, HomeServerConnectionConfig.Builder() @@ -192,7 +192,7 @@ class FTUEViewModel @AssistedInject constructor( getKnownCustomHomeServersUrls() } - private fun handleLoginWithToken(action: FTUEAction.LoginWithToken) { + private fun handleLoginWithToken(action: OnboardingAction.LoginWithToken) { val safeLoginWizard = loginWizard if (safeLoginWizard == null) { @@ -212,7 +212,7 @@ class FTUEViewModel @AssistedInject constructor( try { safeLoginWizard.loginWithToken(action.loginToken) } catch (failure: Throwable) { - _viewEvents.post(FTUEViewEvents.Failure(failure)) + _viewEvents.post(OnboardingViewEvents.Failure(failure)) setState { copy( asyncLoginAction = Fail(failure) @@ -225,7 +225,7 @@ class FTUEViewModel @AssistedInject constructor( } } - private fun handleSetupSsoForSessionRecovery(action: FTUEAction.SetupSsoForSessionRecovery) { + private fun handleSetupSsoForSessionRecovery(action: OnboardingAction.SetupSsoForSessionRecovery) { setState { copy( signMode = SignMode.SignIn, @@ -237,20 +237,20 @@ class FTUEViewModel @AssistedInject constructor( } } - private fun handleRegisterAction(action: FTUEAction.RegisterAction) { + private fun handleRegisterAction(action: OnboardingAction.RegisterAction) { when (action) { - is FTUEAction.CaptchaDone -> handleCaptchaDone(action) - is FTUEAction.AcceptTerms -> handleAcceptTerms() - is FTUEAction.RegisterDummy -> handleRegisterDummy() - is FTUEAction.AddThreePid -> handleAddThreePid(action) - is FTUEAction.SendAgainThreePid -> handleSendAgainThreePid() - is FTUEAction.ValidateThreePid -> handleValidateThreePid(action) - is FTUEAction.CheckIfEmailHasBeenValidated -> handleCheckIfEmailHasBeenValidated(action) - is FTUEAction.StopEmailValidationCheck -> handleStopEmailValidationCheck() + is OnboardingAction.CaptchaDone -> handleCaptchaDone(action) + is OnboardingAction.AcceptTerms -> handleAcceptTerms() + is OnboardingAction.RegisterDummy -> handleRegisterDummy() + is OnboardingAction.AddThreePid -> handleAddThreePid(action) + is OnboardingAction.SendAgainThreePid -> handleSendAgainThreePid() + is OnboardingAction.ValidateThreePid -> handleValidateThreePid(action) + is OnboardingAction.CheckIfEmailHasBeenValidated -> handleCheckIfEmailHasBeenValidated(action) + is OnboardingAction.StopEmailValidationCheck -> handleStopEmailValidationCheck() } } - private fun handleCheckIfEmailHasBeenValidated(action: FTUEAction.CheckIfEmailHasBeenValidated) { + private fun handleCheckIfEmailHasBeenValidated(action: OnboardingAction.CheckIfEmailHasBeenValidated) { // We do not want the common progress bar to be displayed, so we do not change asyncRegistration value in the state currentJob = executeRegistrationStep(withLoading = false) { it.checkIfEmailHasBeenValidated(action.delayMillis) @@ -261,7 +261,7 @@ class FTUEViewModel @AssistedInject constructor( currentJob = null } - private fun handleValidateThreePid(action: FTUEAction.ValidateThreePid) { + private fun handleValidateThreePid(action: OnboardingAction.ValidateThreePid) { currentJob = executeRegistrationStep { it.handleValidateThreePid(action.code) } @@ -284,7 +284,7 @@ class FTUEViewModel @AssistedInject constructor( */ } catch (failure: Throwable) { if (failure !is CancellationException) { - _viewEvents.post(FTUEViewEvents.Failure(failure)) + _viewEvents.post(OnboardingViewEvents.Failure(failure)) } null } @@ -303,13 +303,13 @@ class FTUEViewModel @AssistedInject constructor( } } - private fun handleAddThreePid(action: FTUEAction.AddThreePid) { + private fun handleAddThreePid(action: OnboardingAction.AddThreePid) { setState { copy(asyncRegistration = Loading()) } currentJob = viewModelScope.launch { try { registrationWizard?.addThreePid(action.threePid) } catch (failure: Throwable) { - _viewEvents.post(FTUEViewEvents.Failure(failure)) + _viewEvents.post(OnboardingViewEvents.Failure(failure)) } setState { copy( @@ -325,7 +325,7 @@ class FTUEViewModel @AssistedInject constructor( try { registrationWizard?.sendAgainThreePid() } catch (failure: Throwable) { - _viewEvents.post(FTUEViewEvents.Failure(failure)) + _viewEvents.post(OnboardingViewEvents.Failure(failure)) } setState { copy( @@ -347,7 +347,7 @@ class FTUEViewModel @AssistedInject constructor( } } - private fun handleRegisterWith(action: FTUEAction.LoginOrRegister) { + private fun handleRegisterWith(action: OnboardingAction.LoginOrRegister) { reAuthHelper.data = action.password currentJob = executeRegistrationStep { it.createAccount( @@ -358,25 +358,25 @@ class FTUEViewModel @AssistedInject constructor( } } - private fun handleCaptchaDone(action: FTUEAction.CaptchaDone) { + private fun handleCaptchaDone(action: OnboardingAction.CaptchaDone) { currentJob = executeRegistrationStep { it.performReCaptcha(action.captchaResponse) } } - private fun handleResetAction(action: FTUEAction.ResetAction) { + private fun handleResetAction(action: OnboardingAction.ResetAction) { // Cancel any request currentJob = null when (action) { - FTUEAction.ResetHomeServerType -> { + OnboardingAction.ResetHomeServerType -> { setState { copy( serverType = ServerType.Unknown ) } } - FTUEAction.ResetHomeServerUrl -> { + OnboardingAction.ResetHomeServerUrl -> { viewModelScope.launch { authenticationService.reset() setState { @@ -391,7 +391,7 @@ class FTUEViewModel @AssistedInject constructor( } } } - FTUEAction.ResetSignMode -> { + OnboardingAction.ResetSignMode -> { setState { copy( asyncHomeServerLoginFlowRequest = Uninitialized, @@ -401,7 +401,7 @@ class FTUEViewModel @AssistedInject constructor( ) } } - FTUEAction.ResetLogin -> { + OnboardingAction.ResetLogin -> { viewModelScope.launch { authenticationService.cancelPendingLoginOrRegistration() setState { @@ -412,7 +412,7 @@ class FTUEViewModel @AssistedInject constructor( } } } - FTUEAction.ResetResetPassword -> { + OnboardingAction.ResetResetPassword -> { setState { copy( asyncResetPassword = Uninitialized, @@ -424,7 +424,7 @@ class FTUEViewModel @AssistedInject constructor( } } - private fun handleUpdateSignMode(action: FTUEAction.UpdateSignMode) { + private fun handleUpdateSignMode(action: OnboardingAction.UpdateSignMode) { setState { copy( signMode = action.signMode @@ -434,12 +434,12 @@ class FTUEViewModel @AssistedInject constructor( when (action.signMode) { SignMode.SignUp -> startRegistrationFlow() SignMode.SignIn -> startAuthenticationFlow() - SignMode.SignInWithMatrixId -> _viewEvents.post(FTUEViewEvents.OnSignModeSelected(SignMode.SignInWithMatrixId)) + SignMode.SignInWithMatrixId -> _viewEvents.post(OnboardingViewEvents.OnSignModeSelected(SignMode.SignInWithMatrixId)) SignMode.Unknown -> Unit } } - private fun handleUpdateServerType(action: FTUEAction.UpdateServerType) { + private fun handleUpdateServerType(action: OnboardingAction.UpdateServerType) { setState { copy( serverType = action.serverType @@ -450,20 +450,20 @@ class FTUEViewModel @AssistedInject constructor( ServerType.Unknown -> Unit /* Should not happen */ ServerType.MatrixOrg -> // Request login flow here - handle(FTUEAction.UpdateHomeServer(matrixOrgUrl)) + handle(OnboardingAction.UpdateHomeServer(matrixOrgUrl)) ServerType.EMS, - ServerType.Other -> _viewEvents.post(FTUEViewEvents.OnServerSelectionDone(action.serverType)) + ServerType.Other -> _viewEvents.post(OnboardingViewEvents.OnServerSelectionDone(action.serverType)) }.exhaustive } - private fun handleInitWith(action: FTUEAction.InitWith) { + private fun handleInitWith(action: OnboardingAction.InitWith) { loginConfig = action.loginConfig // If there is a pending email validation continue on this step try { if (registrationWizard?.isRegistrationStarted == true) { currentThreePid?.let { - handle(FTUEAction.PostViewEvent(FTUEViewEvents.OnSendEmailSuccess(it))) + handle(OnboardingAction.PostViewEvent(OnboardingViewEvents.OnSendEmailSuccess(it))) } } } catch (e: Throwable) { @@ -472,7 +472,7 @@ class FTUEViewModel @AssistedInject constructor( } } - private fun handleResetPassword(action: FTUEAction.ResetPassword) { + private fun handleResetPassword(action: OnboardingAction.ResetPassword) { val safeLoginWizard = loginWizard if (safeLoginWizard == null) { @@ -509,7 +509,7 @@ class FTUEViewModel @AssistedInject constructor( ) } - _viewEvents.post(FTUEViewEvents.OnResetPasswordSendThreePidDone) + _viewEvents.post(OnboardingViewEvents.OnResetPasswordSendThreePidDone) } } } @@ -550,12 +550,12 @@ class FTUEViewModel @AssistedInject constructor( ) } - _viewEvents.post(FTUEViewEvents.OnResetPasswordMailConfirmationSuccess) + _viewEvents.post(OnboardingViewEvents.OnResetPasswordMailConfirmationSuccess) } } } - private fun handleLoginOrRegister(action: FTUEAction.LoginOrRegister) = withState { state -> + private fun handleLoginOrRegister(action: OnboardingAction.LoginOrRegister) = withState { state -> when (state.signMode) { SignMode.Unknown -> error("Developer error, invalid sign mode") SignMode.SignIn -> handleLogin(action) @@ -564,7 +564,7 @@ class FTUEViewModel @AssistedInject constructor( }.exhaustive } - private fun handleDirectLogin(action: FTUEAction.LoginOrRegister, homeServerConnectionConfig: HomeServerConnectionConfig?) { + private fun handleDirectLogin(action: OnboardingAction.LoginOrRegister, homeServerConnectionConfig: HomeServerConnectionConfig?) { setState { copy( asyncLoginAction = Loading() @@ -601,10 +601,10 @@ class FTUEViewModel @AssistedInject constructor( asyncLoginAction = Uninitialized ) } - _viewEvents.post(FTUEViewEvents.Failure(Exception(stringProvider.getString(R.string.autodiscover_well_known_error)))) + _viewEvents.post(OnboardingViewEvents.Failure(Exception(stringProvider.getString(R.string.autodiscover_well_known_error)))) } - private suspend fun onWellknownSuccess(action: FTUEAction.LoginOrRegister, + private suspend fun onWellknownSuccess(action: OnboardingAction.LoginOrRegister, wellKnownPrompt: WellknownResult.Prompt, homeServerConnectionConfig: HomeServerConnectionConfig?) { val alteredHomeServerConnectionConfig = homeServerConnectionConfig @@ -636,7 +636,7 @@ class FTUEViewModel @AssistedInject constructor( is MatrixIdFailure.InvalidMatrixId, is Failure.UnrecognizedCertificateFailure -> { // Display this error in a dialog - _viewEvents.post(FTUEViewEvents.Failure(failure)) + _viewEvents.post(OnboardingViewEvents.Failure(failure)) setState { copy( asyncLoginAction = Uninitialized @@ -653,7 +653,7 @@ class FTUEViewModel @AssistedInject constructor( } } - private fun handleLogin(action: FTUEAction.LoginOrRegister) { + private fun handleLogin(action: OnboardingAction.LoginOrRegister) { val safeLoginWizard = loginWizard if (safeLoginWizard == null) { @@ -702,7 +702,7 @@ class FTUEViewModel @AssistedInject constructor( // Ensure Wizard is ready loginWizard - _viewEvents.post(FTUEViewEvents.OnSignModeSelected(SignMode.SignIn)) + _viewEvents.post(OnboardingViewEvents.OnSignModeSelected(SignMode.SignIn)) } private fun onFlowResponse(flowResult: FlowResult) { @@ -712,7 +712,7 @@ class FTUEViewModel @AssistedInject constructor( handleRegisterDummy() } else { // Notify the user - _viewEvents.post(FTUEViewEvents.RegistrationFlowResult(flowResult, isRegistrationStarted)) + _viewEvents.post(OnboardingViewEvents.RegistrationFlowResult(flowResult, isRegistrationStarted)) } } @@ -728,7 +728,7 @@ class FTUEViewModel @AssistedInject constructor( } } - private fun handleWebLoginSuccess(action: FTUEAction.WebLoginSuccess) = withState { state -> + private fun handleWebLoginSuccess(action: OnboardingAction.WebLoginSuccess) = withState { state -> val homeServerConnectionConfigFinal = homeServerConnectionConfigFactory.create(state.homeServerUrl) if (homeServerConnectionConfigFinal == null) { @@ -749,11 +749,11 @@ class FTUEViewModel @AssistedInject constructor( } } - private fun handleUpdateHomeserver(action: FTUEAction.UpdateHomeServer) { + private fun handleUpdateHomeserver(action: OnboardingAction.UpdateHomeServer) { val homeServerConnectionConfig = homeServerConnectionConfigFactory.create(action.homeServerUrl) if (homeServerConnectionConfig == null) { // This is invalid - _viewEvents.post(FTUEViewEvents.Failure(Throwable("Unable to create a HomeServerConnectionConfig"))) + _viewEvents.post(OnboardingViewEvents.Failure(Throwable("Unable to create a HomeServerConnectionConfig"))) } else { getLoginFlow(homeServerConnectionConfig) } @@ -782,7 +782,7 @@ class FTUEViewModel @AssistedInject constructor( val data = try { authenticationService.getLoginFlow(homeServerConnectionConfig) } catch (failure: Throwable) { - _viewEvents.post(FTUEViewEvents.Failure(failure)) + _viewEvents.post(OnboardingViewEvents.Failure(failure)) setState { copy( asyncHomeServerLoginFlowRequest = Uninitialized, @@ -820,9 +820,9 @@ class FTUEViewModel @AssistedInject constructor( if ((loginMode == LoginMode.Password && !data.isLoginAndRegistrationSupported) || data.isOutdatedHomeserver) { // Notify the UI - _viewEvents.post(FTUEViewEvents.OutdatedHomeserver) + _viewEvents.post(OnboardingViewEvents.OutdatedHomeserver) } - _viewEvents.post(FTUEViewEvents.OnLoginFlowRetrieved) + _viewEvents.post(OnboardingViewEvents.OnLoginFlowRetrieved) } } diff --git a/vector/src/main/java/im/vector/app/features/ftue/FTUEViewState.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewState.kt similarity index 97% rename from vector/src/main/java/im/vector/app/features/ftue/FTUEViewState.kt rename to vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewState.kt index 9f34ed3782..7a6537f433 100644 --- a/vector/src/main/java/im/vector/app/features/ftue/FTUEViewState.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewState.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package im.vector.app.features.ftue +package im.vector.app.features.onboarding import com.airbnb.mvrx.Async import com.airbnb.mvrx.Loading @@ -26,7 +26,7 @@ import im.vector.app.features.login.LoginMode import im.vector.app.features.login.ServerType import im.vector.app.features.login.SignMode -data class FTUEViewState( +data class OnboardingViewState( val asyncLoginAction: Async = Uninitialized, val asyncHomeServerLoginFlowRequest: Async = Uninitialized, val asyncResetPassword: Async = Uninitialized,