extracting common textinputlayer interactions to their own extensions and providing a dedicated register action
This commit is contained in:
parent
11983443fb
commit
585ac4bf1f
|
@ -0,0 +1,32 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2022 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.core.extensions
|
||||||
|
|
||||||
|
import com.google.android.material.textfield.TextInputLayout
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import reactivecircus.flowbinding.android.widget.textChanges
|
||||||
|
|
||||||
|
fun TextInputLayout.editText() = this.editText!!
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detect if a field starts or ends with spaces
|
||||||
|
*/
|
||||||
|
fun TextInputLayout.hasSurroundingSpaces() = editText().text.toString().let { it.trim() != it }
|
||||||
|
|
||||||
|
fun TextInputLayout.hasContentFlow(mapper: (CharSequence) -> CharSequence = { it }) = editText().textChanges().map { mapper(it).isNotEmpty() }
|
||||||
|
|
||||||
|
fun TextInputLayout.content() = editText().text.toString()
|
|
@ -41,6 +41,7 @@ sealed interface OnboardingAction : VectorViewModelAction {
|
||||||
|
|
||||||
// Login or Register, depending on the signMode
|
// Login or Register, depending on the signMode
|
||||||
data class LoginOrRegister(val username: String, val password: String, val initialDeviceName: String) : OnboardingAction
|
data class LoginOrRegister(val username: String, val password: String, val initialDeviceName: String) : OnboardingAction
|
||||||
|
data class Register(val username: String, val password: String, val initialDeviceName: String) : OnboardingAction
|
||||||
object StopEmailValidationCheck : OnboardingAction
|
object StopEmailValidationCheck : OnboardingAction
|
||||||
|
|
||||||
data class PostRegisterAction(val registerAction: RegisterAction) : OnboardingAction
|
data class PostRegisterAction(val registerAction: RegisterAction) : OnboardingAction
|
||||||
|
|
|
@ -141,6 +141,7 @@ class OnboardingViewModel @AssistedInject constructor(
|
||||||
is OnboardingAction.InitWith -> handleInitWith(action)
|
is OnboardingAction.InitWith -> handleInitWith(action)
|
||||||
is OnboardingAction.UpdateHomeServer -> handleUpdateHomeserver(action).also { lastAction = action }
|
is OnboardingAction.UpdateHomeServer -> handleUpdateHomeserver(action).also { lastAction = action }
|
||||||
is OnboardingAction.LoginOrRegister -> handleLoginOrRegister(action).also { lastAction = action }
|
is OnboardingAction.LoginOrRegister -> handleLoginOrRegister(action).also { lastAction = action }
|
||||||
|
is OnboardingAction.Register -> handleRegisterWith(action).also { lastAction = action }
|
||||||
is OnboardingAction.LoginWithToken -> handleLoginWithToken(action)
|
is OnboardingAction.LoginWithToken -> handleLoginWithToken(action)
|
||||||
is OnboardingAction.WebLoginSuccess -> handleWebLoginSuccess(action)
|
is OnboardingAction.WebLoginSuccess -> handleWebLoginSuccess(action)
|
||||||
is OnboardingAction.ResetPassword -> handleResetPassword(action)
|
is OnboardingAction.ResetPassword -> handleResetPassword(action)
|
||||||
|
@ -276,7 +277,7 @@ class OnboardingViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleRegisterWith(action: OnboardingAction.LoginOrRegister) {
|
private fun handleRegisterWith(action: OnboardingAction.Register) {
|
||||||
reAuthHelper.data = action.password
|
reAuthHelper.data = action.password
|
||||||
handleRegisterAction(RegisterAction.CreateAccount(
|
handleRegisterAction(RegisterAction.CreateAccount(
|
||||||
action.username,
|
action.username,
|
||||||
|
@ -465,7 +466,7 @@ class OnboardingViewModel @AssistedInject constructor(
|
||||||
when (state.signMode) {
|
when (state.signMode) {
|
||||||
SignMode.Unknown -> error("Developer error, invalid sign mode")
|
SignMode.Unknown -> error("Developer error, invalid sign mode")
|
||||||
SignMode.SignIn -> handleLogin(action)
|
SignMode.SignIn -> handleLogin(action)
|
||||||
SignMode.SignUp -> handleRegisterWith(action)
|
SignMode.SignUp -> handleRegisterWith(OnboardingAction.Register(action.username, action.password, action.initialDeviceName))
|
||||||
SignMode.SignInWithMatrixId -> handleDirectLogin(action, null)
|
SignMode.SignInWithMatrixId -> handleDirectLogin(action, null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,8 +28,11 @@ import androidx.core.view.isVisible
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.airbnb.mvrx.withState
|
import com.airbnb.mvrx.withState
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import com.google.android.material.textfield.TextInputLayout
|
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
|
import im.vector.app.core.extensions.content
|
||||||
|
import im.vector.app.core.extensions.editText
|
||||||
|
import im.vector.app.core.extensions.hasContentFlow
|
||||||
|
import im.vector.app.core.extensions.hasSurroundingSpaces
|
||||||
import im.vector.app.core.extensions.hideKeyboard
|
import im.vector.app.core.extensions.hideKeyboard
|
||||||
import im.vector.app.core.extensions.hidePassword
|
import im.vector.app.core.extensions.hidePassword
|
||||||
import im.vector.app.core.extensions.realignPercentagesToParent
|
import im.vector.app.core.extensions.realignPercentagesToParent
|
||||||
|
@ -42,7 +45,6 @@ import im.vector.app.features.onboarding.OnboardingAction
|
||||||
import im.vector.app.features.onboarding.OnboardingViewState
|
import im.vector.app.features.onboarding.OnboardingViewState
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.map
|
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import org.matrix.android.sdk.api.failure.isInvalidPassword
|
import org.matrix.android.sdk.api.failure.isInvalidPassword
|
||||||
import org.matrix.android.sdk.api.failure.isInvalidUsername
|
import org.matrix.android.sdk.api.failure.isInvalidUsername
|
||||||
|
@ -50,7 +52,6 @@ import org.matrix.android.sdk.api.failure.isLoginEmailUnknown
|
||||||
import org.matrix.android.sdk.api.failure.isRegistrationDisabled
|
import org.matrix.android.sdk.api.failure.isRegistrationDisabled
|
||||||
import org.matrix.android.sdk.api.failure.isUsernameInUse
|
import org.matrix.android.sdk.api.failure.isUsernameInUse
|
||||||
import org.matrix.android.sdk.api.failure.isWeakPassword
|
import org.matrix.android.sdk.api.failure.isWeakPassword
|
||||||
import reactivecircus.flowbinding.android.widget.textChanges
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class FtueAuthCombinedSignUpFragment @Inject constructor() : AbstractSSOFtueAuthFragment<FragmentFtueSignUpCombinedBinding>() {
|
class FtueAuthCombinedSignUpFragment @Inject constructor() : AbstractSSOFtueAuthFragment<FragmentFtueSignUpCombinedBinding>() {
|
||||||
|
@ -91,8 +92,8 @@ class FtueAuthCombinedSignUpFragment @Inject constructor() : AbstractSSOFtueAuth
|
||||||
|
|
||||||
cleanupUi()
|
cleanupUi()
|
||||||
|
|
||||||
val login = views.createAccountInput.editText.toString()
|
val login = views.createAccountInput.content()
|
||||||
val password = views.createAccountPasswordInput.editText.toString()
|
val password = views.createAccountPasswordInput.content()
|
||||||
|
|
||||||
// This can be called by the IME action, so deal with empty cases
|
// This can be called by the IME action, so deal with empty cases
|
||||||
var error = 0
|
var error = 0
|
||||||
|
@ -110,7 +111,7 @@ class FtueAuthCombinedSignUpFragment @Inject constructor() : AbstractSSOFtueAuth
|
||||||
}
|
}
|
||||||
|
|
||||||
if (error == 0) {
|
if (error == 0) {
|
||||||
viewModel.handle(OnboardingAction.LoginOrRegister(login, password, getString(R.string.login_default_session_public_name)))
|
viewModel.handle(OnboardingAction.Register(login, password, getString(R.string.login_default_session_public_name)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -143,12 +144,7 @@ class FtueAuthCombinedSignUpFragment @Inject constructor() : AbstractSSOFtueAuth
|
||||||
|
|
||||||
private fun setupSubmitButton() {
|
private fun setupSubmitButton() {
|
||||||
views.createAccountSubmit.setOnClickListener { submit() }
|
views.createAccountSubmit.setOnClickListener { submit() }
|
||||||
combine(
|
observeInputFields()
|
||||||
views.createAccountInput.editText().textChanges().map { it.trim().isNotEmpty() },
|
|
||||||
views.createAccountPasswordInput.editText().textChanges().map { it.isNotEmpty() }
|
|
||||||
) { isLoginNotEmpty, isPasswordNotEmpty ->
|
|
||||||
isLoginNotEmpty && isPasswordNotEmpty
|
|
||||||
}
|
|
||||||
.onEach {
|
.onEach {
|
||||||
views.createAccountPasswordInput.error = null
|
views.createAccountPasswordInput.error = null
|
||||||
views.createAccountInput.error = null
|
views.createAccountInput.error = null
|
||||||
|
@ -157,6 +153,12 @@ class FtueAuthCombinedSignUpFragment @Inject constructor() : AbstractSSOFtueAuth
|
||||||
.launchIn(viewLifecycleOwner.lifecycleScope)
|
.launchIn(viewLifecycleOwner.lifecycleScope)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun observeInputFields() = combine(
|
||||||
|
views.createAccountInput.hasContentFlow { it.trim() },
|
||||||
|
views.createAccountPasswordInput.hasContentFlow(),
|
||||||
|
transform = { isLoginNotEmpty, isPasswordNotEmpty -> isLoginNotEmpty && isPasswordNotEmpty }
|
||||||
|
)
|
||||||
|
|
||||||
override fun resetViewModel() {
|
override fun resetViewModel() {
|
||||||
viewModel.handle(OnboardingAction.ResetLogin)
|
viewModel.handle(OnboardingAction.ResetLogin)
|
||||||
}
|
}
|
||||||
|
@ -165,26 +167,26 @@ class FtueAuthCombinedSignUpFragment @Inject constructor() : AbstractSSOFtueAuth
|
||||||
// Trick to display the error without text.
|
// Trick to display the error without text.
|
||||||
views.createAccountInput.error = " "
|
views.createAccountInput.error = " "
|
||||||
when {
|
when {
|
||||||
throwable.isUsernameInUse() || throwable.isInvalidUsername() -> {
|
throwable.isUsernameInUse() || throwable.isInvalidUsername() -> {
|
||||||
views.createAccountInput.error = errorFormatter.toHumanReadable(throwable)
|
views.createAccountInput.error = errorFormatter.toHumanReadable(throwable)
|
||||||
}
|
}
|
||||||
throwable.isLoginEmailUnknown() -> {
|
throwable.isLoginEmailUnknown() -> {
|
||||||
views.createAccountInput.error = getString(R.string.login_login_with_email_error)
|
views.createAccountInput.error = getString(R.string.login_login_with_email_error)
|
||||||
}
|
}
|
||||||
throwable.isInvalidPassword() && spaceInPassword() -> {
|
throwable.isInvalidPassword() && views.createAccountPasswordInput.hasSurroundingSpaces() -> {
|
||||||
views.createAccountPasswordInput.error = getString(R.string.auth_invalid_login_param_space_in_password)
|
views.createAccountPasswordInput.error = getString(R.string.auth_invalid_login_param_space_in_password)
|
||||||
}
|
}
|
||||||
throwable.isWeakPassword() || throwable.isInvalidPassword() -> {
|
throwable.isWeakPassword() || throwable.isInvalidPassword() -> {
|
||||||
views.createAccountPasswordInput.error = errorFormatter.toHumanReadable(throwable)
|
views.createAccountPasswordInput.error = errorFormatter.toHumanReadable(throwable)
|
||||||
}
|
}
|
||||||
throwable.isRegistrationDisabled() -> {
|
throwable.isRegistrationDisabled() -> {
|
||||||
MaterialAlertDialogBuilder(requireActivity())
|
MaterialAlertDialogBuilder(requireActivity())
|
||||||
.setTitle(R.string.dialog_title_error)
|
.setTitle(R.string.dialog_title_error)
|
||||||
.setMessage(getString(R.string.login_registration_disabled))
|
.setMessage(getString(R.string.login_registration_disabled))
|
||||||
.setPositiveButton(R.string.ok, null)
|
.setPositiveButton(R.string.ok, null)
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
super.onError(throwable)
|
super.onError(throwable)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -200,13 +202,6 @@ class FtueAuthCombinedSignUpFragment @Inject constructor() : AbstractSSOFtueAuth
|
||||||
views.createAccountPasswordInput.editText().hidePassword()
|
views.createAccountPasswordInput.editText().hidePassword()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Detect if password ends or starts with spaces
|
|
||||||
*/
|
|
||||||
private fun spaceInPassword() = views.createAccountPasswordInput.editText().text.toString().let { it.trim() != it }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun OnboardingViewState.isNumericOnlyUserIdForbidden() = serverType == ServerType.MatrixOrg
|
private fun OnboardingViewState.isNumericOnlyUserIdForbidden() = serverType == ServerType.MatrixOrg
|
||||||
|
|
||||||
private fun TextInputLayout.editText() = this.editText!!
|
|
||||||
|
|
Loading…
Reference in New Issue