extracting common textinputlayer interactions to their own extensions and providing a dedicated register action

This commit is contained in:
Adam Brown 2022-03-28 11:21:14 +01:00
parent 11983443fb
commit 585ac4bf1f
4 changed files with 56 additions and 27 deletions

View File

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

View File

@ -41,6 +41,7 @@ sealed interface OnboardingAction : VectorViewModelAction {
// Login or Register, depending on the signMode
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
data class PostRegisterAction(val registerAction: RegisterAction) : OnboardingAction

View File

@ -141,6 +141,7 @@ class OnboardingViewModel @AssistedInject constructor(
is OnboardingAction.InitWith -> handleInitWith(action)
is OnboardingAction.UpdateHomeServer -> handleUpdateHomeserver(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.WebLoginSuccess -> handleWebLoginSuccess(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
handleRegisterAction(RegisterAction.CreateAccount(
action.username,
@ -465,7 +466,7 @@ class OnboardingViewModel @AssistedInject constructor(
when (state.signMode) {
SignMode.Unknown -> error("Developer error, invalid sign mode")
SignMode.SignIn -> handleLogin(action)
SignMode.SignUp -> handleRegisterWith(action)
SignMode.SignUp -> handleRegisterWith(OnboardingAction.Register(action.username, action.password, action.initialDeviceName))
SignMode.SignInWithMatrixId -> handleDirectLogin(action, null)
}
}

View File

@ -28,8 +28,11 @@ import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import com.airbnb.mvrx.withState
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.textfield.TextInputLayout
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.hidePassword
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 kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import org.matrix.android.sdk.api.failure.isInvalidPassword
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.isUsernameInUse
import org.matrix.android.sdk.api.failure.isWeakPassword
import reactivecircus.flowbinding.android.widget.textChanges
import javax.inject.Inject
class FtueAuthCombinedSignUpFragment @Inject constructor() : AbstractSSOFtueAuthFragment<FragmentFtueSignUpCombinedBinding>() {
@ -91,8 +92,8 @@ class FtueAuthCombinedSignUpFragment @Inject constructor() : AbstractSSOFtueAuth
cleanupUi()
val login = views.createAccountInput.editText.toString()
val password = views.createAccountPasswordInput.editText.toString()
val login = views.createAccountInput.content()
val password = views.createAccountPasswordInput.content()
// This can be called by the IME action, so deal with empty cases
var error = 0
@ -110,7 +111,7 @@ class FtueAuthCombinedSignUpFragment @Inject constructor() : AbstractSSOFtueAuth
}
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() {
views.createAccountSubmit.setOnClickListener { submit() }
combine(
views.createAccountInput.editText().textChanges().map { it.trim().isNotEmpty() },
views.createAccountPasswordInput.editText().textChanges().map { it.isNotEmpty() }
) { isLoginNotEmpty, isPasswordNotEmpty ->
isLoginNotEmpty && isPasswordNotEmpty
}
observeInputFields()
.onEach {
views.createAccountPasswordInput.error = null
views.createAccountInput.error = null
@ -157,6 +153,12 @@ class FtueAuthCombinedSignUpFragment @Inject constructor() : AbstractSSOFtueAuth
.launchIn(viewLifecycleOwner.lifecycleScope)
}
private fun observeInputFields() = combine(
views.createAccountInput.hasContentFlow { it.trim() },
views.createAccountPasswordInput.hasContentFlow(),
transform = { isLoginNotEmpty, isPasswordNotEmpty -> isLoginNotEmpty && isPasswordNotEmpty }
)
override fun resetViewModel() {
viewModel.handle(OnboardingAction.ResetLogin)
}
@ -171,7 +173,7 @@ class FtueAuthCombinedSignUpFragment @Inject constructor() : AbstractSSOFtueAuth
throwable.isLoginEmailUnknown() -> {
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)
}
throwable.isWeakPassword() || throwable.isInvalidPassword() -> {
@ -200,13 +202,6 @@ class FtueAuthCombinedSignUpFragment @Inject constructor() : AbstractSSOFtueAuth
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 TextInputLayout.editText() = this.editText!!