Login screens: Registration: login/password step

This commit is contained in:
Benoit Marty 2019-11-18 19:08:10 +01:00
parent 381084b2ab
commit 95fc20dca0
5 changed files with 143 additions and 87 deletions

View File

@ -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()

View File

@ -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
}
}

View File

@ -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)
}
}
}

View File

@ -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()
}

View File

@ -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<RegistrationResult> {
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<LoginViewEvents>()
val viewEvents: DataSource<LoginViewEvents> = _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<RegistrationResult> {
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
)
}
}