diff --git a/vector/src/main/java/im/vector/riotx/features/login/LoginActivity.kt b/vector/src/main/java/im/vector/riotx/features/login/LoginActivity.kt index f934ebf27f..e05a8d2d5e 100644 --- a/vector/src/main/java/im/vector/riotx/features/login/LoginActivity.kt +++ b/vector/src/main/java/im/vector/riotx/features/login/LoginActivity.kt @@ -23,6 +23,8 @@ import androidx.core.view.isVisible import androidx.fragment.app.FragmentManager import com.airbnb.mvrx.viewModel import com.airbnb.mvrx.withState +import im.vector.matrix.android.api.auth.registration.FlowResult +import im.vector.matrix.android.api.auth.registration.Stage import im.vector.riotx.R import im.vector.riotx.core.di.ScreenComponent import im.vector.riotx.core.extensions.addFragment @@ -85,6 +87,34 @@ class LoginActivity : VectorBaseActivity() { updateWithState(it) } .disposeOnDestroy() + + loginViewModel.viewEvents + .observe() + .subscribe { + handleLoginViewEvents(it) + } + .disposeOnDestroy() + } + + private fun handleLoginViewEvents(loginViewEvents: LoginViewEvents) { + when (loginViewEvents) { + is LoginViewEvents.RegistrationFlowResult -> { + // Check that all flows are supported by the application + if (loginViewEvents.flowResult.missingStages.any { it is Stage.Other }) { + // Display a popup to propose use web fallback + // TODO + } else { + // Go on with registration flow + // loginSharedActionViewModel.post(LoginNavigation.OnSignModeSelected) + if (loginViewModel.isPasswordSent) { + handleRegistrationNavigation(loginViewEvents.flowResult) + } else { + // First ask for login and password + addFragmentToBackstack(R.id.loginFragmentContainer, LoginFragment::class.java) + } + } + } + } } private fun onLoginFlowRetrieved() { @@ -152,6 +182,38 @@ class LoginActivity : VectorBaseActivity() { .show() } + private fun handleRegistrationNavigation(flowResult: FlowResult) { + // Complete all mandatory stage first + val mandatoryStages = flowResult.missingStages.filter { it.mandatory } + + if (mandatoryStages.isEmpty()) { + // Consider optional stages + val optionalStages = flowResult.missingStages.filter { !it.mandatory } + if (optionalStages.isEmpty()) { + // Should not happen... + } else { + doStage(optionalStages.first()) + } + } else { + doStage(mandatoryStages.first()) + } + } + + private fun doStage(stage: Stage) { + when (stage) { + is Stage.ReCaptcha -> addFragmentToBackstack(R.id.loginFragmentContainer, LoginCaptchaFragment::class.java) + is Stage.Email -> addFragmentToBackstack(R.id.loginFragmentContainer, + LoginGenericTextInputFormFragment::class.java, + LoginGenericTextInputFormFragmentArgument(TextInputFormFragmentMode.SetEmail, stage.mandatory)) + is Stage.Msisdn + -> addFragmentToBackstack(R.id.loginFragmentContainer, LoginGenericTextInputFormFragment::class.java, + LoginGenericTextInputFormFragmentArgument(TextInputFormFragmentMode.SetMsisdn, stage.mandatory)) + is Stage.Terms + -> TODO() + } + } + + override fun onResume() { super.onResume() diff --git a/vector/src/main/java/im/vector/riotx/features/login/LoginGenericTextInputFormFragment.kt b/vector/src/main/java/im/vector/riotx/features/login/LoginGenericTextInputFormFragment.kt index 98c30c685f..4e25769740 100644 --- a/vector/src/main/java/im/vector/riotx/features/login/LoginGenericTextInputFormFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/login/LoginGenericTextInputFormFragment.kt @@ -30,16 +30,15 @@ import kotlinx.android.synthetic.main.fragment_login_generic_text_input_form.* import javax.inject.Inject enum class TextInputFormFragmentMode { - SetEmailMandatory, - SetEmailOptional, - SetMsisdnMandatory, - SetMsisdnOptional, + SetEmail, + SetMsisdn, ConfirmMsisdn } @Parcelize data class LoginGenericTextInputFormFragmentArgument( - val mode: TextInputFormFragmentMode + val mode: TextInputFormFragmentMode, + val mandatory: Boolean ) : Parcelable /** @@ -60,39 +59,23 @@ class LoginGenericTextInputFormFragment @Inject constructor() : AbstractLoginFra private fun setupUi() { when (params.mode) { - TextInputFormFragmentMode.SetEmailMandatory -> { + TextInputFormFragmentMode.SetEmail -> { loginGenericTextInputFormTitle.text = getString(R.string.login_set_email_title) loginGenericTextInputFormNotice.text = getString(R.string.login_set_email_notice) - loginGenericTextInputFormTil.hint = getString(R.string.login_set_email_mandatory_hint) + loginGenericTextInputFormTil.hint = getString(if (params.mandatory) R.string.login_set_email_mandatory_hint else R.string.login_set_email_optional_hint) loginGenericTextInputFormTextInput.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS loginGenericTextInputFormOtherButton.isVisible = false loginGenericTextInputFormSubmit.text = getString(R.string.login_set_email_submit) } - TextInputFormFragmentMode.SetEmailOptional -> { - loginGenericTextInputFormTitle.text = getString(R.string.login_set_email_title) - loginGenericTextInputFormNotice.text = getString(R.string.login_set_email_notice) - loginGenericTextInputFormTil.hint = getString(R.string.login_set_email_optional_hint) - loginGenericTextInputFormTextInput.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS - loginGenericTextInputFormOtherButton.isVisible = false - loginGenericTextInputFormSubmit.text = getString(R.string.login_set_email_submit) - } - TextInputFormFragmentMode.SetMsisdnMandatory -> { + TextInputFormFragmentMode.SetMsisdn -> { loginGenericTextInputFormTitle.text = getString(R.string.login_set_msisdn_title) loginGenericTextInputFormNotice.text = getString(R.string.login_set_msisdn_notice) - loginGenericTextInputFormTil.hint = getString(R.string.login_set_msisdn_mandatory_hint) + loginGenericTextInputFormTil.hint = getString(if (params.mandatory) R.string.login_set_msisdn_mandatory_hint else R.string.login_set_msisdn_optional_hint) loginGenericTextInputFormTextInput.inputType = InputType.TYPE_CLASS_PHONE loginGenericTextInputFormOtherButton.isVisible = false loginGenericTextInputFormSubmit.text = getString(R.string.login_set_msisdn_submit) } - TextInputFormFragmentMode.SetMsisdnOptional -> { - loginGenericTextInputFormTitle.text = getString(R.string.login_set_msisdn_title) - loginGenericTextInputFormNotice.text = getString(R.string.login_set_msisdn_notice) - loginGenericTextInputFormTil.hint = getString(R.string.login_set_msisdn_optional_hint) - loginGenericTextInputFormTextInput.inputType = InputType.TYPE_CLASS_PHONE - loginGenericTextInputFormOtherButton.isVisible = false - loginGenericTextInputFormSubmit.text = getString(R.string.login_set_msisdn_submit) - } - TextInputFormFragmentMode.ConfirmMsisdn -> { + TextInputFormFragmentMode.ConfirmMsisdn -> { loginGenericTextInputFormTitle.text = getString(R.string.login_msisdn_confirm_title) loginGenericTextInputFormNotice.text = getString(R.string.login_msisdn_confirm_notice) loginGenericTextInputFormTil.hint = getString(R.string.login_msisdn_confirm_hint) @@ -115,22 +98,16 @@ class LoginGenericTextInputFormFragment @Inject constructor() : AbstractLoginFra } private fun setupSubmitButton() { - when (params.mode) { - TextInputFormFragmentMode.SetEmailMandatory, - TextInputFormFragmentMode.SetMsisdnMandatory, - TextInputFormFragmentMode.ConfirmMsisdn -> { - loginGenericTextInputFormSubmit.isEnabled = false - loginGenericTextInputFormTextInput.textChanges() - .subscribe { - // TODO Better check for email format, etc? - loginGenericTextInputFormSubmit.isEnabled = it.isNotBlank() - } - .disposeOnDestroyView() - } - TextInputFormFragmentMode.SetEmailOptional, - TextInputFormFragmentMode.SetMsisdnOptional -> { - loginGenericTextInputFormSubmit.isEnabled = true - } + if (params.mandatory) { + loginGenericTextInputFormSubmit.isEnabled = false + loginGenericTextInputFormTextInput.textChanges() + .subscribe { + // TODO Better check for email format, etc? + loginGenericTextInputFormSubmit.isEnabled = it.isNotBlank() + } + .disposeOnDestroyView() + } else { + loginGenericTextInputFormSubmit.isEnabled = true } } diff --git a/vector/src/main/java/im/vector/riotx/features/login/LoginSignUpSignInSelectionFragment.kt b/vector/src/main/java/im/vector/riotx/features/login/LoginSignUpSignInSelectionFragment.kt index 08872606bb..4425632fd1 100644 --- a/vector/src/main/java/im/vector/riotx/features/login/LoginSignUpSignInSelectionFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/login/LoginSignUpSignInSelectionFragment.kt @@ -21,11 +21,7 @@ import android.view.View import androidx.core.view.isVisible import butterknife.OnClick import com.airbnb.mvrx.Fail -import com.airbnb.mvrx.Success import com.airbnb.mvrx.withState -import im.vector.matrix.android.api.auth.registration.FlowResult -import im.vector.matrix.android.api.auth.registration.RegistrationResult -import im.vector.matrix.android.api.auth.registration.Stage import im.vector.riotx.R import kotlinx.android.synthetic.main.fragment_login_signup_signin_selection.* import javax.inject.Inject @@ -83,32 +79,12 @@ class LoginSignUpSignInSelectionFragment @Inject constructor() : AbstractLoginFr override fun invalidate() = withState(loginViewModel) { when (it.asyncRegistration) { - is Success -> { - when (val res = it.asyncRegistration()) { - is RegistrationResult.Success -> - // Should not happen - Unit - is RegistrationResult.FlowResponse -> handleFlowResult(res.flowResult) - } - } - is Fail -> { - // TODO Registration disabled, etc + is Fail -> { + // TODO Registration disabled, (move to Activity?) when (it.asyncRegistration.error) { } } } } - - private fun handleFlowResult(flowResult: FlowResult) { - // Check that all flows are supported by the application - if (flowResult.missingStages.any { it is Stage.Other }) { - // Display a popup to propose use web fallback - // TODO - } else { - // Go on with registration flow - loginSharedActionViewModel.post(LoginNavigation.OnSignModeSelected) - } - } - } diff --git a/vector/src/main/java/im/vector/riotx/features/login/LoginViewEvents.kt b/vector/src/main/java/im/vector/riotx/features/login/LoginViewEvents.kt new file mode 100644 index 0000000000..b8b7965c77 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/login/LoginViewEvents.kt @@ -0,0 +1,27 @@ +/* + * 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.riotx.features.login + +import im.vector.matrix.android.api.auth.registration.FlowResult + +/** + * Transient events for Login + */ +sealed class LoginViewEvents { + data class RegistrationFlowResult(val flowResult: FlowResult) : LoginViewEvents() +} diff --git a/vector/src/main/java/im/vector/riotx/features/login/LoginViewModel.kt b/vector/src/main/java/im/vector/riotx/features/login/LoginViewModel.kt index f0d872ce48..362a126e8c 100644 --- a/vector/src/main/java/im/vector/riotx/features/login/LoginViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/login/LoginViewModel.kt @@ -34,6 +34,8 @@ import im.vector.matrix.android.internal.auth.data.LoginFlowResponse import im.vector.riotx.core.di.ActiveSessionHolder import im.vector.riotx.core.extensions.configureAndStart import im.vector.riotx.core.platform.VectorViewModel +import im.vector.riotx.core.utils.DataSource +import im.vector.riotx.core.utils.PublishDataSource import im.vector.riotx.features.notifications.PushRuleTriggerListener import im.vector.riotx.features.session.SessionListener import timber.log.Timber @@ -60,6 +62,9 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi } } + var isPasswordSent: Boolean = false + private set + private var registrationWizard: RegistrationWizard? = null var serverType: ServerType = ServerType.MatrixOrg @@ -74,23 +79,8 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi private var homeServerConnectionConfig: HomeServerConnectionConfig? = null private var currentTask: Cancelable? = null - private val registrationCallback = object : MatrixCallback { - override fun onSuccess(data: RegistrationResult) { - when (data) { - is RegistrationResult.Success -> onSessionCreated(data.session) - is RegistrationResult.FlowResponse -> onFlowResponse(data.flowResult) - } - } - - override fun onFailure(failure: Throwable) { - // TODO Handled JobCancellationException - setState { - copy( - asyncRegistration = Fail(failure) - ) - } - } - } + private val _viewEvents = PublishDataSource() + val viewEvents: DataSource = _viewEvents override fun handle(action: LoginAction) { when (action) { @@ -109,6 +99,7 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi private fun handleRegisterAction(action: LoginAction.RegisterAction) { when (action) { is LoginAction.RegisterWith -> handleRegisterWith(action) + // TODO Add other actions here } } @@ -119,7 +110,25 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi ) } - currentTask = registrationWizard?.createAccount(action.username, action.password, null /* TODO InitialDisplayName */, registrationCallback) + currentTask = registrationWizard?.createAccount(action.username, action.password, null /* TODO InitialDisplayName */, object : MatrixCallback { + override fun onSuccess(data: RegistrationResult) { + isPasswordSent = true + + when (data) { + is RegistrationResult.Success -> onSessionCreated(data.session) + is RegistrationResult.FlowResponse -> onFlowResponse(data.flowResult) + } + } + + override fun onFailure(failure: Throwable) { + // TODO Handled JobCancellationException + setState { + copy( + asyncRegistration = Fail(failure) + ) + } + } + }) } private fun handleResetAction(action: LoginAction.ResetAction) { @@ -129,6 +138,8 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi when (action) { LoginAction.ResetLogin -> { + isPasswordSent = false + setState { copy( asyncLoginAction = Uninitialized, @@ -292,9 +303,12 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi } private fun onFlowResponse(flowResult: FlowResult) { + // Notify the user + _viewEvents.post(LoginViewEvents.RegistrationFlowResult(flowResult)) + setState { copy( - asyncRegistration = Success(RegistrationResult.FlowResponse(flowResult)) + asyncRegistration = Uninitialized ) } }