Merge pull request #6545 from vector-im/feature/adm/ftue-combined-register-copy-review
FTUE - Combined register copy review
This commit is contained in:
commit
70c8703b2b
1
changelog.d/6546.feature
Normal file
1
changelog.d/6546.feature
Normal file
@ -0,0 +1 @@
|
||||
Updates FTUE registration to include username availability check and update copy
|
@ -156,6 +156,20 @@ object MatrixPatterns {
|
||||
return matrixId?.substringAfter(":", missingDelimiterValue = "")?.takeIf { it.isNotEmpty() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract user name from a matrix id.
|
||||
*
|
||||
* @param matrixId
|
||||
* @return null if the input is not a valid matrixId
|
||||
*/
|
||||
fun extractUserNameFromId(matrixId: String): String? {
|
||||
return if (isUserId(matrixId)) {
|
||||
matrixId.removePrefix("@").substringBefore(":", missingDelimiterValue = "")
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Orders which are not strings, or do not consist solely of ascii characters in the range \x20 (space) to \x7E (~),
|
||||
* or consist of more than 50 characters, are forbidden and the field should be ignored if received.
|
||||
|
@ -35,6 +35,23 @@ class MatrixPatternsTest {
|
||||
MatrixPatterns.isUserId(input) shouldBeEqualTo expected
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given matrix id cases, when extracting userName, then returns expected`() {
|
||||
val cases = listOf(
|
||||
MatrixIdCase("foobar", userName = null),
|
||||
MatrixIdCase("@foobar", userName = null),
|
||||
MatrixIdCase("foobar@matrix.org", userName = null),
|
||||
MatrixIdCase("@foobar: matrix.org", userName = null),
|
||||
MatrixIdCase("foobar:matrix.org", userName = null),
|
||||
MatrixIdCase("@foobar:matrix.org", userName = "foobar"),
|
||||
)
|
||||
|
||||
cases.forEach { (input, expected) ->
|
||||
MatrixPatterns.extractUserNameFromId(input) shouldBeEqualTo expected
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private data class UserIdCase(val input: String, val isUserId: Boolean)
|
||||
private data class MatrixIdCase(val input: String, val userName: String?)
|
||||
|
@ -19,6 +19,7 @@ package im.vector.app.core.extensions
|
||||
import android.util.Patterns
|
||||
import com.google.i18n.phonenumbers.NumberParseException
|
||||
import com.google.i18n.phonenumbers.PhoneNumberUtil
|
||||
import org.matrix.android.sdk.api.MatrixPatterns
|
||||
import org.matrix.android.sdk.api.extensions.ensurePrefix
|
||||
|
||||
fun Boolean.toOnOff() = if (this) "ON" else "OFF"
|
||||
@ -30,6 +31,8 @@ inline fun <T> T.ooi(block: (T) -> Unit): T = also(block)
|
||||
*/
|
||||
fun CharSequence.isEmail() = Patterns.EMAIL_ADDRESS.matcher(this).matches()
|
||||
|
||||
fun CharSequence.isMatrixId() = MatrixPatterns.isUserId(this.toString())
|
||||
|
||||
/**
|
||||
* Return empty CharSequence if the CharSequence is null.
|
||||
*/
|
||||
|
@ -44,8 +44,15 @@ fun TextInputLayout.content() = editText().text.toString()
|
||||
fun TextInputLayout.hasContent() = !editText().text.isNullOrEmpty()
|
||||
|
||||
fun TextInputLayout.clearErrorOnChange(lifecycleOwner: LifecycleOwner) {
|
||||
onTextChange(lifecycleOwner) {
|
||||
error = null
|
||||
isErrorEnabled = false
|
||||
}
|
||||
}
|
||||
|
||||
fun TextInputLayout.onTextChange(lifecycleOwner: LifecycleOwner, action: (CharSequence) -> Unit) {
|
||||
editText().textChanges()
|
||||
.onEach { error = null }
|
||||
.onEach(action)
|
||||
.launchIn(lifecycleOwner.lifecycleScope)
|
||||
}
|
||||
|
||||
|
@ -52,9 +52,13 @@ sealed interface OnboardingAction : VectorViewModelAction {
|
||||
object ResendResetPassword : OnboardingAction
|
||||
object ResetPasswordMailConfirmed : OnboardingAction
|
||||
|
||||
data class MaybeUpdateHomeserverFromMatrixId(val userId: String) : OnboardingAction
|
||||
sealed interface UserNameEnteredAction : OnboardingAction {
|
||||
data class Registration(val userId: String) : UserNameEnteredAction
|
||||
data class Login(val userId: String) : UserNameEnteredAction
|
||||
}
|
||||
sealed interface AuthenticateAction : OnboardingAction {
|
||||
data class Register(val username: String, val password: String, val initialDeviceName: String) : AuthenticateAction
|
||||
data class RegisterWithMatrixId(val matrixId: String, val password: String, val initialDeviceName: String) : AuthenticateAction
|
||||
data class Login(val username: String, val password: String, val initialDeviceName: String) : AuthenticateAction
|
||||
data class LoginDirect(val matrixId: String, val password: String, val initialDeviceName: String) : AuthenticateAction
|
||||
}
|
||||
@ -71,6 +75,7 @@ sealed interface OnboardingAction : VectorViewModelAction {
|
||||
object ResetSignMode : ResetAction
|
||||
object ResetAuthenticationAttempt : ResetAction
|
||||
object ResetResetPassword : ResetAction
|
||||
object ResetSelectedRegistrationUserName : ResetAction
|
||||
|
||||
// Homeserver history
|
||||
object ClearHomeServerHistory : OnboardingAction
|
||||
|
@ -28,6 +28,8 @@ import im.vector.app.core.di.hiltMavericksViewModelFactory
|
||||
import im.vector.app.core.extensions.cancelCurrentOnSet
|
||||
import im.vector.app.core.extensions.configureAndStart
|
||||
import im.vector.app.core.extensions.inferNoConnectivity
|
||||
import im.vector.app.core.extensions.isMatrixId
|
||||
import im.vector.app.core.extensions.toReducedUrl
|
||||
import im.vector.app.core.extensions.vectorStore
|
||||
import im.vector.app.core.platform.VectorViewModel
|
||||
import im.vector.app.core.resources.BuildMeta
|
||||
@ -57,6 +59,7 @@ 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.SsoIdentityProvider
|
||||
import org.matrix.android.sdk.api.auth.login.LoginWizard
|
||||
import org.matrix.android.sdk.api.auth.registration.RegistrationAvailability
|
||||
import org.matrix.android.sdk.api.auth.registration.RegistrationWizard
|
||||
import org.matrix.android.sdk.api.failure.isHomeserverUnavailable
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
@ -144,7 +147,7 @@ class OnboardingViewModel @AssistedInject constructor(
|
||||
is OnboardingAction.UpdateSignMode -> handleUpdateSignMode(action)
|
||||
is OnboardingAction.InitWith -> handleInitWith(action)
|
||||
is OnboardingAction.HomeServerChange -> withAction(action) { handleHomeserverChange(action) }
|
||||
is OnboardingAction.MaybeUpdateHomeserverFromMatrixId -> handleMaybeUpdateHomeserver(action)
|
||||
is OnboardingAction.UserNameEnteredAction -> handleUserNameEntered(action)
|
||||
is AuthenticateAction -> withAction(action) { handleAuthenticateAction(action) }
|
||||
is OnboardingAction.LoginWithToken -> handleLoginWithToken(action)
|
||||
is OnboardingAction.WebLoginSuccess -> handleWebLoginSuccess(action)
|
||||
@ -167,13 +170,47 @@ class OnboardingViewModel @AssistedInject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleMaybeUpdateHomeserver(action: OnboardingAction.MaybeUpdateHomeserverFromMatrixId) {
|
||||
val isFullMatrixId = MatrixPatterns.isUserId(action.userId)
|
||||
private fun handleUserNameEntered(action: OnboardingAction.UserNameEnteredAction) {
|
||||
when (action) {
|
||||
is OnboardingAction.UserNameEnteredAction.Login -> maybeUpdateHomeserver(action.userId)
|
||||
is OnboardingAction.UserNameEnteredAction.Registration -> maybeUpdateHomeserver(action.userId, continuation = { userName ->
|
||||
checkUserNameAvailability(userName)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private fun maybeUpdateHomeserver(userNameOrMatrixId: String, continuation: suspend (String) -> Unit = {}) {
|
||||
val isFullMatrixId = MatrixPatterns.isUserId(userNameOrMatrixId)
|
||||
if (isFullMatrixId) {
|
||||
val domain = action.userId.getServerName().substringBeforeLast(":").ensureProtocol()
|
||||
handleHomeserverChange(OnboardingAction.HomeServerChange.EditHomeServer(domain))
|
||||
val domain = userNameOrMatrixId.getServerName().substringBeforeLast(":").ensureProtocol()
|
||||
handleHomeserverChange(OnboardingAction.HomeServerChange.EditHomeServer(domain), postAction = {
|
||||
val userName = MatrixPatterns.extractUserNameFromId(userNameOrMatrixId) ?: throw IllegalStateException("unexpected non matrix id")
|
||||
continuation(userName)
|
||||
})
|
||||
} else {
|
||||
// ignore the action
|
||||
currentJob = viewModelScope.launch { continuation(userNameOrMatrixId) }
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun checkUserNameAvailability(userName: String) {
|
||||
when (val result = registrationWizard.registrationAvailable(userName)) {
|
||||
RegistrationAvailability.Available -> {
|
||||
setState {
|
||||
copy(
|
||||
registrationState = RegistrationState(
|
||||
isUserNameAvailable = true,
|
||||
selectedMatrixId = when {
|
||||
userName.isMatrixId() -> userName
|
||||
else -> "@$userName:${selectedHomeserver.userFacingUrl.toReducedUrl()}"
|
||||
},
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
is RegistrationAvailability.NotAvailable -> {
|
||||
_viewEvents.post(OnboardingViewEvents.Failure(result.failure))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -184,7 +221,12 @@ class OnboardingViewModel @AssistedInject constructor(
|
||||
|
||||
private fun handleAuthenticateAction(action: AuthenticateAction) {
|
||||
when (action) {
|
||||
is AuthenticateAction.Register -> handleRegisterWith(action)
|
||||
is AuthenticateAction.Register -> handleRegisterWith(action.username, action.password, action.initialDeviceName)
|
||||
is AuthenticateAction.RegisterWithMatrixId -> handleRegisterWith(
|
||||
MatrixPatterns.extractUserNameFromId(action.matrixId) ?: throw IllegalStateException("unexpected non matrix id"),
|
||||
action.password,
|
||||
action.initialDeviceName
|
||||
)
|
||||
is AuthenticateAction.Login -> handleLogin(action)
|
||||
is AuthenticateAction.LoginDirect -> handleDirectLogin(action, homeServerConnectionConfig = null)
|
||||
}
|
||||
@ -322,17 +364,17 @@ class OnboardingViewModel @AssistedInject constructor(
|
||||
)
|
||||
}
|
||||
|
||||
private fun handleRegisterWith(action: AuthenticateAction.Register) {
|
||||
private fun handleRegisterWith(userName: String, password: String, initialDeviceName: String) {
|
||||
setState {
|
||||
val authDescription = AuthenticationDescription.Register(AuthenticationDescription.AuthenticationType.Password)
|
||||
copy(selectedAuthenticationState = SelectedAuthenticationState(authDescription))
|
||||
}
|
||||
reAuthHelper.data = action.password
|
||||
reAuthHelper.data = password
|
||||
handleRegisterAction(
|
||||
RegisterAction.CreateAccount(
|
||||
action.username,
|
||||
action.password,
|
||||
action.initialDeviceName
|
||||
userName,
|
||||
password,
|
||||
initialDeviceName
|
||||
)
|
||||
)
|
||||
}
|
||||
@ -368,7 +410,12 @@ class OnboardingViewModel @AssistedInject constructor(
|
||||
OnboardingAction.ResetAuthenticationAttempt -> {
|
||||
viewModelScope.launch {
|
||||
authenticationService.cancelPendingLoginOrRegistration()
|
||||
setState { copy(isLoading = false) }
|
||||
setState {
|
||||
copy(
|
||||
isLoading = false,
|
||||
registrationState = RegistrationState(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
OnboardingAction.ResetResetPassword -> {
|
||||
@ -380,6 +427,11 @@ class OnboardingViewModel @AssistedInject constructor(
|
||||
}
|
||||
}
|
||||
OnboardingAction.ResetDeeplinkConfig -> loginConfig = null
|
||||
OnboardingAction.ResetSelectedRegistrationUserName -> {
|
||||
setState {
|
||||
copy(registrationState = RegistrationState())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -619,27 +671,31 @@ class OnboardingViewModel @AssistedInject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleHomeserverChange(action: OnboardingAction.HomeServerChange, serverTypeOverride: ServerType? = null) {
|
||||
private fun handleHomeserverChange(action: OnboardingAction.HomeServerChange, serverTypeOverride: ServerType? = null, postAction: suspend () -> Unit = {}) {
|
||||
val homeServerConnectionConfig = homeServerConnectionConfigFactory.create(action.homeServerUrl)
|
||||
if (homeServerConnectionConfig == null) {
|
||||
// This is invalid
|
||||
_viewEvents.post(OnboardingViewEvents.Failure(Throwable("Unable to create a HomeServerConnectionConfig")))
|
||||
} else {
|
||||
startAuthenticationFlow(action, homeServerConnectionConfig, serverTypeOverride)
|
||||
startAuthenticationFlow(action, homeServerConnectionConfig, serverTypeOverride, postAction)
|
||||
}
|
||||
}
|
||||
|
||||
private fun startAuthenticationFlow(
|
||||
trigger: OnboardingAction.HomeServerChange,
|
||||
homeServerConnectionConfig: HomeServerConnectionConfig,
|
||||
serverTypeOverride: ServerType?
|
||||
serverTypeOverride: ServerType?,
|
||||
postAction: suspend () -> Unit = {},
|
||||
) {
|
||||
currentHomeServerConnectionConfig = homeServerConnectionConfig
|
||||
|
||||
currentJob = viewModelScope.launch {
|
||||
setState { copy(isLoading = true) }
|
||||
runCatching { startAuthenticationFlowUseCase.execute(homeServerConnectionConfig) }.fold(
|
||||
onSuccess = { onAuthenticationStartedSuccess(trigger, homeServerConnectionConfig, it, serverTypeOverride) },
|
||||
onSuccess = {
|
||||
onAuthenticationStartedSuccess(trigger, homeServerConnectionConfig, it, serverTypeOverride)
|
||||
postAction()
|
||||
},
|
||||
onFailure = { onAuthenticationStartError(it, trigger) }
|
||||
)
|
||||
setState { copy(isLoading = false) }
|
||||
|
@ -48,6 +48,9 @@ data class OnboardingViewState(
|
||||
val knownCustomHomeServersUrls: List<String> = emptyList(),
|
||||
val isForceLoginFallbackEnabled: Boolean = false,
|
||||
|
||||
@PersistState
|
||||
val registrationState: RegistrationState = RegistrationState(),
|
||||
|
||||
@PersistState
|
||||
val selectedHomeserver: SelectedHomeserverState = SelectedHomeserverState(),
|
||||
|
||||
@ -66,7 +69,6 @@ enum class OnboardingFlow {
|
||||
|
||||
@Parcelize
|
||||
data class SelectedHomeserverState(
|
||||
val description: String? = null,
|
||||
val userFacingUrl: String? = null,
|
||||
val upstreamUrl: String? = null,
|
||||
val preferredLoginMode: LoginMode = LoginMode.Unknown,
|
||||
@ -96,3 +98,9 @@ data class ResetState(
|
||||
data class SelectedAuthenticationState(
|
||||
val description: AuthenticationDescription? = null,
|
||||
) : Parcelable
|
||||
|
||||
@Parcelize
|
||||
data class RegistrationState(
|
||||
val isUserNameAvailable: Boolean = false,
|
||||
val selectedMatrixId: String? = null,
|
||||
) : Parcelable
|
||||
|
@ -16,10 +16,7 @@
|
||||
|
||||
package im.vector.app.features.onboarding
|
||||
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.extensions.containsAllItems
|
||||
import im.vector.app.core.resources.StringProvider
|
||||
import im.vector.app.core.utils.ensureTrailingSlash
|
||||
import im.vector.app.features.login.LoginMode
|
||||
import org.matrix.android.sdk.api.auth.AuthenticationService
|
||||
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
|
||||
@ -29,7 +26,6 @@ import javax.inject.Inject
|
||||
|
||||
class StartAuthenticationFlowUseCase @Inject constructor(
|
||||
private val authenticationService: AuthenticationService,
|
||||
private val stringProvider: StringProvider
|
||||
) {
|
||||
|
||||
suspend fun execute(config: HomeServerConnectionConfig): StartAuthenticationResult {
|
||||
@ -46,10 +42,6 @@ class StartAuthenticationFlowUseCase @Inject constructor(
|
||||
config: HomeServerConnectionConfig,
|
||||
preferredLoginMode: LoginMode
|
||||
) = SelectedHomeserverState(
|
||||
description = when (config.homeServerUri.toString()) {
|
||||
matrixOrgUrl() -> stringProvider.getString(R.string.ftue_auth_create_account_matrix_dot_org_server_description)
|
||||
else -> null
|
||||
},
|
||||
userFacingUrl = config.homeServerUri.toString(),
|
||||
upstreamUrl = authFlow.homeServerUrl,
|
||||
preferredLoginMode = preferredLoginMode,
|
||||
@ -57,8 +49,6 @@ class StartAuthenticationFlowUseCase @Inject constructor(
|
||||
isLogoutDevicesSupported = authFlow.isLogoutDevicesSupported
|
||||
)
|
||||
|
||||
private fun matrixOrgUrl() = stringProvider.getString(R.string.matrix_org_server_url).ensureTrailingSlash()
|
||||
|
||||
private fun LoginFlowResult.findPreferredLoginMode() = when {
|
||||
supportedLoginTypes.containsAllItems(LoginFlowTypes.SSO, LoginFlowTypes.PASSWORD) -> LoginMode.SsoAndPassword(ssoIdentityProviders)
|
||||
supportedLoginTypes.contains(LoginFlowTypes.SSO) -> LoginMode.Sso(ssoIdentityProviders)
|
||||
|
@ -25,6 +25,7 @@ import androidx.autofill.HintConstants
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.extensions.clearErrorOnChange
|
||||
import im.vector.app.core.extensions.content
|
||||
import im.vector.app.core.extensions.editText
|
||||
import im.vector.app.core.extensions.hideKeyboard
|
||||
@ -41,8 +42,10 @@ import im.vector.app.features.login.render
|
||||
import im.vector.app.features.onboarding.OnboardingAction
|
||||
import im.vector.app.features.onboarding.OnboardingViewEvents
|
||||
import im.vector.app.features.onboarding.OnboardingViewState
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider
|
||||
import reactivecircus.flowbinding.android.widget.textChanges
|
||||
import javax.inject.Inject
|
||||
|
||||
class FtueAuthCombinedLoginFragment @Inject constructor(
|
||||
@ -60,14 +63,18 @@ class FtueAuthCombinedLoginFragment @Inject constructor(
|
||||
views.loginRoot.realignPercentagesToParent()
|
||||
views.editServerButton.debouncedClicks { viewModel.handle(OnboardingAction.PostViewEvent(OnboardingViewEvents.EditServerSelection)) }
|
||||
views.loginPasswordInput.setOnImeDoneListener { submit() }
|
||||
views.loginInput.setOnFocusLostListener { viewModel.handle(OnboardingAction.MaybeUpdateHomeserverFromMatrixId(views.loginInput.content())) }
|
||||
views.loginInput.setOnFocusLostListener { viewModel.handle(OnboardingAction.UserNameEnteredAction.Login(views.loginInput.content())) }
|
||||
views.loginForgotPassword.debouncedClicks { viewModel.handle(OnboardingAction.PostViewEvent(OnboardingViewEvents.OnForgetPasswordClicked)) }
|
||||
}
|
||||
|
||||
private fun setupSubmitButton() {
|
||||
views.loginSubmit.setOnClickListener { submit() }
|
||||
observeContentChangesAndResetErrors(views.loginInput, views.loginPasswordInput, views.loginSubmit)
|
||||
.launchIn(viewLifecycleOwner.lifecycleScope)
|
||||
views.loginInput.clearErrorOnChange(viewLifecycleOwner)
|
||||
views.loginPasswordInput.clearErrorOnChange(viewLifecycleOwner)
|
||||
|
||||
combine(views.loginInput.editText().textChanges(), views.loginPasswordInput.editText().textChanges()) { account, password ->
|
||||
views.loginSubmit.isEnabled = account.isNotEmpty() && password.isNotEmpty()
|
||||
}.launchIn(viewLifecycleOwner.lifecycleScope)
|
||||
}
|
||||
|
||||
private fun submit() {
|
||||
@ -105,7 +112,6 @@ class FtueAuthCombinedLoginFragment @Inject constructor(
|
||||
setupAutoFill()
|
||||
|
||||
views.selectedServerName.text = state.selectedHomeserver.userFacingUrl.toReducedUrl()
|
||||
views.selectedServerDescription.text = state.selectedHomeserver.description
|
||||
|
||||
if (state.isLoading) {
|
||||
// Ensure password is hidden
|
||||
|
@ -28,11 +28,14 @@ import androidx.lifecycle.lifecycleScope
|
||||
import com.airbnb.mvrx.withState
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.extensions.clearErrorOnChange
|
||||
import im.vector.app.core.extensions.content
|
||||
import im.vector.app.core.extensions.editText
|
||||
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.isMatrixId
|
||||
import im.vector.app.core.extensions.onTextChange
|
||||
import im.vector.app.core.extensions.realignPercentagesToParent
|
||||
import im.vector.app.core.extensions.setOnFocusLostListener
|
||||
import im.vector.app.core.extensions.setOnImeDoneListener
|
||||
@ -46,6 +49,7 @@ import im.vector.app.features.onboarding.OnboardingAction
|
||||
import im.vector.app.features.onboarding.OnboardingAction.AuthenticateAction
|
||||
import im.vector.app.features.onboarding.OnboardingViewEvents
|
||||
import im.vector.app.features.onboarding.OnboardingViewState
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider
|
||||
import org.matrix.android.sdk.api.failure.isHomeserverUnavailable
|
||||
@ -55,8 +59,11 @@ 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
|
||||
|
||||
private const val MINIMUM_PASSWORD_LENGTH = 8
|
||||
|
||||
class FtueAuthCombinedRegisterFragment @Inject constructor() : AbstractSSOFtueAuthFragment<FragmentFtueCombinedRegisterBinding>() {
|
||||
|
||||
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentFtueCombinedRegisterBinding {
|
||||
@ -69,15 +76,27 @@ class FtueAuthCombinedRegisterFragment @Inject constructor() : AbstractSSOFtueAu
|
||||
views.createAccountRoot.realignPercentagesToParent()
|
||||
views.editServerButton.debouncedClicks { viewModel.handle(OnboardingAction.PostViewEvent(OnboardingViewEvents.EditServerSelection)) }
|
||||
views.createAccountPasswordInput.setOnImeDoneListener { submit() }
|
||||
|
||||
views.createAccountInput.onTextChange(viewLifecycleOwner) {
|
||||
viewModel.handle(OnboardingAction.ResetSelectedRegistrationUserName)
|
||||
views.createAccountEntryFooter.text = ""
|
||||
}
|
||||
|
||||
views.createAccountInput.setOnFocusLostListener {
|
||||
viewModel.handle(OnboardingAction.MaybeUpdateHomeserverFromMatrixId(views.createAccountInput.content()))
|
||||
viewModel.handle(OnboardingAction.UserNameEnteredAction.Registration(views.createAccountInput.content()))
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupSubmitButton() {
|
||||
views.createAccountSubmit.setOnClickListener { submit() }
|
||||
observeContentChangesAndResetErrors(views.createAccountInput, views.createAccountPasswordInput, views.createAccountSubmit)
|
||||
.launchIn(viewLifecycleOwner.lifecycleScope)
|
||||
views.createAccountInput.clearErrorOnChange(viewLifecycleOwner)
|
||||
views.createAccountPasswordInput.clearErrorOnChange(viewLifecycleOwner)
|
||||
|
||||
combine(views.createAccountInput.editText().textChanges(), views.createAccountPasswordInput.editText().textChanges()) { account, password ->
|
||||
val accountIsValid = account.isNotEmpty()
|
||||
val passwordIsValid = password.length >= MINIMUM_PASSWORD_LENGTH
|
||||
views.createAccountSubmit.isEnabled = accountIsValid && passwordIsValid
|
||||
}.launchIn(viewLifecycleOwner.lifecycleScope)
|
||||
}
|
||||
|
||||
private fun submit() {
|
||||
@ -103,7 +122,12 @@ class FtueAuthCombinedRegisterFragment @Inject constructor() : AbstractSSOFtueAu
|
||||
}
|
||||
|
||||
if (error == 0) {
|
||||
viewModel.handle(AuthenticateAction.Register(login, password, getString(R.string.login_default_session_public_name)))
|
||||
val initialDeviceName = getString(R.string.login_default_session_public_name)
|
||||
val registerAction = when {
|
||||
login.isMatrixId() -> AuthenticateAction.RegisterWithMatrixId(login, password, initialDeviceName)
|
||||
else -> AuthenticateAction.Register(login, password, initialDeviceName)
|
||||
}
|
||||
viewModel.handle(registerAction)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -153,17 +177,25 @@ class FtueAuthCombinedRegisterFragment @Inject constructor() : AbstractSSOFtueAu
|
||||
override fun updateWithState(state: OnboardingViewState) {
|
||||
setupUi(state)
|
||||
setupAutoFill()
|
||||
}
|
||||
|
||||
private fun setupUi(state: OnboardingViewState) {
|
||||
views.selectedServerName.text = state.selectedHomeserver.userFacingUrl.toReducedUrl()
|
||||
views.selectedServerDescription.text = state.selectedHomeserver.description
|
||||
|
||||
if (state.isLoading) {
|
||||
// Ensure password is hidden
|
||||
views.createAccountPasswordInput.editText().hidePassword()
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupUi(state: OnboardingViewState) {
|
||||
views.createAccountEntryFooter.text = when {
|
||||
state.registrationState.isUserNameAvailable -> getString(
|
||||
R.string.ftue_auth_create_account_username_entry_footer,
|
||||
state.registrationState.selectedMatrixId
|
||||
)
|
||||
|
||||
else -> ""
|
||||
}
|
||||
|
||||
when (state.selectedHomeserver.preferredLoginMode) {
|
||||
is LoginMode.SsoAndPassword -> renderSsoProviders(state.deviceId, state.selectedHomeserver.preferredLoginMode.ssoIdentityProviders)
|
||||
else -> hideSsoProviders()
|
||||
|
@ -16,16 +16,10 @@
|
||||
|
||||
package im.vector.app.features.onboarding.ftueauth
|
||||
|
||||
import android.widget.Button
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.extensions.hasContentFlow
|
||||
import im.vector.app.features.login.SignMode
|
||||
import im.vector.app.features.onboarding.OnboardingAction
|
||||
import im.vector.app.features.themes.ThemeProvider
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
|
||||
fun SignMode.toAuthenticateAction(login: String, password: String, initialDeviceName: String): OnboardingAction.AuthenticateAction {
|
||||
return when (this) {
|
||||
@ -36,22 +30,6 @@ fun SignMode.toAuthenticateAction(login: String, password: String, initialDevice
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A flow to monitor content changes from both username/id and password fields,
|
||||
* clearing errors and enabling/disabling the submission button on non empty content changes.
|
||||
*/
|
||||
fun observeContentChangesAndResetErrors(username: TextInputLayout, password: TextInputLayout, submit: Button): Flow<*> {
|
||||
return combine(
|
||||
username.hasContentFlow { it.trim() },
|
||||
password.hasContentFlow(),
|
||||
transform = { usernameHasContent, passwordHasContent -> usernameHasContent && passwordHasContent }
|
||||
).onEach {
|
||||
username.error = null
|
||||
password.error = null
|
||||
submit.isEnabled = it
|
||||
}
|
||||
}
|
||||
|
||||
fun ThemeProvider.ftueBreakerBackground() = when (isLightTheme()) {
|
||||
true -> R.drawable.bg_gradient_ftue_breaker
|
||||
false -> R.drawable.bg_color_background
|
||||
|
@ -34,8 +34,8 @@
|
||||
android:layout_height="52dp"
|
||||
app:layout_constraintBottom_toTopOf="@id/createAccountHeaderIcon"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="0"
|
||||
app:layout_constraintVertical_chainStyle="packed" />
|
||||
app:layout_constraintVertical_chainStyle="packed"
|
||||
app:layout_constraintVertical_bias="0" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/createAccountHeaderIcon"
|
||||
@ -62,24 +62,10 @@
|
||||
android:gravity="center"
|
||||
android:text="@string/ftue_auth_create_account_title"
|
||||
android:textColor="?vctr_content_primary"
|
||||
app:layout_constraintBottom_toTopOf="@id/createAccountHeaderSubtitle"
|
||||
app:layout_constraintEnd_toEndOf="@id/createAccountGutterEnd"
|
||||
app:layout_constraintStart_toStartOf="@id/createAccountGutterStart"
|
||||
app:layout_constraintTop_toBottomOf="@id/createAccountHeaderIcon" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/createAccountHeaderSubtitle"
|
||||
style="@style/Widget.Vector.TextView.Subtitle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:gravity="center"
|
||||
android:text="@string/ftue_auth_create_account_subtitle"
|
||||
android:textColor="?vctr_content_secondary"
|
||||
app:layout_constraintBottom_toTopOf="@id/titleContentSpacing"
|
||||
app:layout_constraintEnd_toEndOf="@id/createAccountGutterEnd"
|
||||
app:layout_constraintStart_toStartOf="@id/createAccountGutterStart"
|
||||
app:layout_constraintTop_toBottomOf="@id/createAccountHeaderTitle" />
|
||||
app:layout_constraintTop_toBottomOf="@id/createAccountHeaderIcon" />
|
||||
|
||||
<Space
|
||||
android:id="@+id/titleContentSpacing"
|
||||
@ -87,7 +73,7 @@
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintBottom_toTopOf="@id/chooseYourServerHeader"
|
||||
app:layout_constraintHeight_percent="0.03"
|
||||
app:layout_constraintTop_toBottomOf="@id/createAccountHeaderSubtitle" />
|
||||
app:layout_constraintTop_toBottomOf="@id/createAccountHeaderTitle" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/chooseYourServerHeader"
|
||||
@ -110,22 +96,11 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="12dp"
|
||||
android:textColor="?vctr_content_primary"
|
||||
app:layout_constraintBottom_toTopOf="@id/selectedServerDescription"
|
||||
app:layout_constraintEnd_toStartOf="@id/editServerButton"
|
||||
app:layout_constraintStart_toStartOf="@id/createAccountGutterStart"
|
||||
app:layout_constraintTop_toBottomOf="@id/chooseYourServerHeader" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/selectedServerDescription"
|
||||
style="@style/Widget.Vector.TextView.Micro"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="12dp"
|
||||
android:textColor="?vctr_content_tertiary"
|
||||
app:layout_constraintBottom_toTopOf="@id/serverSelectionSpacing"
|
||||
app:layout_constraintEnd_toStartOf="@id/editServerButton"
|
||||
app:layout_constraintStart_toStartOf="@id/createAccountGutterStart"
|
||||
app:layout_constraintTop_toBottomOf="@id/selectedServerName" />
|
||||
app:layout_constraintTop_toBottomOf="@id/chooseYourServerHeader"
|
||||
tools:text="matrix.org" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/editServerButton"
|
||||
@ -137,7 +112,7 @@
|
||||
android:paddingEnd="12dp"
|
||||
android:text="@string/ftue_auth_create_account_edit_server_selection"
|
||||
android:textAllCaps="true"
|
||||
app:layout_constraintBottom_toBottomOf="@id/selectedServerDescription"
|
||||
app:layout_constraintBottom_toBottomOf="@id/selectedServerName"
|
||||
app:layout_constraintEnd_toEndOf="@id/createAccountGutterEnd"
|
||||
app:layout_constraintTop_toTopOf="@id/chooseYourServerHeader" />
|
||||
|
||||
@ -147,7 +122,7 @@
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintBottom_toTopOf="@id/createAccountInput"
|
||||
app:layout_constraintHeight_percent="0.05"
|
||||
app:layout_constraintTop_toBottomOf="@id/selectedServerDescription" />
|
||||
app:layout_constraintTop_toBottomOf="@id/selectedServerName" />
|
||||
|
||||
<View
|
||||
android:layout_width="0dp"
|
||||
@ -185,18 +160,18 @@
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:text="@string/ftue_auth_create_account_username_entry_footer"
|
||||
app:layout_constraintBottom_toTopOf="@id/entrySpacing"
|
||||
app:layout_constraintEnd_toEndOf="@id/createAccountGutterEnd"
|
||||
app:layout_constraintStart_toStartOf="@id/createAccountGutterStart"
|
||||
app:layout_constraintTop_toBottomOf="@id/createAccountInput" />
|
||||
app:layout_constraintTop_toBottomOf="@id/createAccountInput"
|
||||
tools:text="Others can discover you %s" />
|
||||
|
||||
<Space
|
||||
android:id="@+id/entrySpacing"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintBottom_toTopOf="@id/createAccountPasswordInput"
|
||||
app:layout_constraintHeight_percent="0.03"
|
||||
app:layout_constraintHeight_percent="0.02"
|
||||
app:layout_constraintTop_toBottomOf="@id/createAccountEntryFooter" />
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
|
@ -11,12 +11,11 @@
|
||||
|
||||
<!-- WIP -->
|
||||
<string name="ftue_auth_create_account_title">Create your account</string>
|
||||
<string name="ftue_auth_create_account_subtitle">We\'ll need some info to get you set up.</string>
|
||||
<string name="ftue_auth_create_account_username_entry_footer">You can\'t change this later</string>
|
||||
<!-- Note for translators, %s is the full matrix of the account being created, eg @hello:matrix.org -->
|
||||
<string name="ftue_auth_create_account_username_entry_footer">Others can discover you %s</string>
|
||||
<string name="ftue_auth_create_account_password_entry_footer">Must be 8 characters or more</string>
|
||||
<string name="ftue_auth_create_account_choose_server_header">Choose your server to store your data</string>
|
||||
<string name="ftue_auth_create_account_choose_server_header">Where your conversations will live</string>
|
||||
<string name="ftue_auth_create_account_sso_section_header">Or</string>
|
||||
<string name="ftue_auth_create_account_matrix_dot_org_server_description">Join millions for free on the largest public server</string>
|
||||
<string name="ftue_auth_create_account_edit_server_selection">Edit</string>
|
||||
|
||||
<string name="ftue_auth_welcome_back_title">Welcome back!</string>
|
||||
|
@ -33,6 +33,7 @@ import im.vector.app.test.fakes.FakeHomeServerConnectionConfigFactory
|
||||
import im.vector.app.test.fakes.FakeHomeServerHistoryService
|
||||
import im.vector.app.test.fakes.FakeLoginWizard
|
||||
import im.vector.app.test.fakes.FakeRegistrationActionHandler
|
||||
import im.vector.app.test.fakes.FakeRegistrationWizard
|
||||
import im.vector.app.test.fakes.FakeSession
|
||||
import im.vector.app.test.fakes.FakeStartAuthenticationFlowUseCase
|
||||
import im.vector.app.test.fakes.FakeStringProvider
|
||||
@ -41,6 +42,7 @@ import im.vector.app.test.fakes.FakeUriFilenameResolver
|
||||
import im.vector.app.test.fakes.FakeVectorFeatures
|
||||
import im.vector.app.test.fakes.FakeVectorOverrides
|
||||
import im.vector.app.test.fakes.toTestString
|
||||
import im.vector.app.test.fixtures.a401ServerError
|
||||
import im.vector.app.test.fixtures.aBuildMeta
|
||||
import im.vector.app.test.fixtures.aHomeServerCapabilities
|
||||
import im.vector.app.test.test
|
||||
@ -50,11 +52,13 @@ import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
|
||||
import org.matrix.android.sdk.api.auth.registration.Stage
|
||||
import org.matrix.android.sdk.api.failure.Failure
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities
|
||||
|
||||
private const val A_DISPLAY_NAME = "a display name"
|
||||
private const val A_PICTURE_FILENAME = "a-picture.png"
|
||||
private val A_SERVER_ERROR = a401ServerError()
|
||||
private val AN_ERROR = RuntimeException("an error!")
|
||||
private val A_LOADABLE_REGISTER_ACTION = RegisterAction.StartRegistration
|
||||
private val A_NON_LOADABLE_REGISTER_ACTION = RegisterAction.CheckIfEmailHasBeenValidated(delayMillis = -1L)
|
||||
@ -64,7 +68,7 @@ private val ANY_CONTINUING_REGISTRATION_RESULT = RegistrationActionHandler.Resul
|
||||
private val A_DIRECT_LOGIN = OnboardingAction.AuthenticateAction.LoginDirect("@a-user:id.org", "a-password", "a-device-name")
|
||||
private const val A_HOMESERVER_URL = "https://edited-homeserver.org"
|
||||
private val A_HOMESERVER_CONFIG = HomeServerConnectionConfig(FakeUri().instance)
|
||||
private val SELECTED_HOMESERVER_STATE = SelectedHomeserverState(preferredLoginMode = LoginMode.Password)
|
||||
private val SELECTED_HOMESERVER_STATE = SelectedHomeserverState(preferredLoginMode = LoginMode.Password, userFacingUrl = A_HOMESERVER_URL)
|
||||
private val SELECTED_HOMESERVER_STATE_SUPPORTED_LOGOUT_DEVICES = SelectedHomeserverState(isLogoutDevicesSupported = true)
|
||||
private const val AN_EMAIL = "hello@example.com"
|
||||
private const val A_PASSWORD = "a-password"
|
||||
@ -290,13 +294,13 @@ class OnboardingViewModelTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given a full matrix id, when maybe updating homeserver, then updates selected homeserver state and emits edited event`() = runTest {
|
||||
viewModelWith(initialState.copy(onboardingFlow = OnboardingFlow.SignUp))
|
||||
fun `given a full matrix id, when a login username is entered, then updates selected homeserver state and emits edited event`() = runTest {
|
||||
viewModelWith(initialState.copy(onboardingFlow = OnboardingFlow.SignIn))
|
||||
givenCanSuccessfullyUpdateHomeserver(A_HOMESERVER_URL, SELECTED_HOMESERVER_STATE)
|
||||
val test = viewModel.test()
|
||||
val fullMatrixId = "@a-user:${A_HOMESERVER_URL.removePrefix("https://")}"
|
||||
|
||||
viewModel.handle(OnboardingAction.MaybeUpdateHomeserverFromMatrixId(fullMatrixId))
|
||||
viewModel.handle(OnboardingAction.UserNameEnteredAction.Login(fullMatrixId))
|
||||
|
||||
test
|
||||
.assertStatesChanges(
|
||||
@ -311,12 +315,11 @@ class OnboardingViewModelTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given a username, when maybe updating homeserver, then does nothing`() = runTest {
|
||||
viewModelWith(initialState.copy(onboardingFlow = OnboardingFlow.SignUp))
|
||||
fun `given a username, when a login username is entered, then does nothing`() = runTest {
|
||||
val test = viewModel.test()
|
||||
val onlyUsername = "a-username"
|
||||
|
||||
viewModel.handle(OnboardingAction.MaybeUpdateHomeserverFromMatrixId(onlyUsername))
|
||||
viewModel.handle(OnboardingAction.UserNameEnteredAction.Login(onlyUsername))
|
||||
|
||||
test
|
||||
.assertStates(initialState)
|
||||
@ -324,6 +327,93 @@ class OnboardingViewModelTest {
|
||||
.finish()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given available username, when a register username is entered, then emits available registration state`() = runTest {
|
||||
viewModelWith(initialRegistrationState(A_HOMESERVER_URL))
|
||||
val onlyUsername = "a-username"
|
||||
givenUserNameIsAvailable(onlyUsername)
|
||||
val test = viewModel.test()
|
||||
|
||||
viewModel.handle(OnboardingAction.UserNameEnteredAction.Registration(onlyUsername))
|
||||
|
||||
test
|
||||
.assertStatesChanges(
|
||||
initialState,
|
||||
{ copy(registrationState = availableRegistrationState(onlyUsername, A_HOMESERVER_URL)) }
|
||||
)
|
||||
.assertNoEvents()
|
||||
.finish()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given unavailable username, when a register username is entered, then emits availability error`() = runTest {
|
||||
viewModelWith(initialRegistrationState(A_HOMESERVER_URL))
|
||||
val onlyUsername = "a-username"
|
||||
givenUserNameIsUnavailable(onlyUsername, A_SERVER_ERROR)
|
||||
val test = viewModel.test()
|
||||
|
||||
viewModel.handle(OnboardingAction.UserNameEnteredAction.Registration(onlyUsername))
|
||||
|
||||
test
|
||||
.assertState(initialState)
|
||||
.assertEvents(OnboardingViewEvents.Failure(A_SERVER_ERROR))
|
||||
.finish()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given available full matrix id, when a register username is entered, then changes homeserver and emits available registration state`() = runTest {
|
||||
viewModelWith(initialRegistrationState("ignored-url"))
|
||||
givenCanSuccessfullyUpdateHomeserver(A_HOMESERVER_URL, SELECTED_HOMESERVER_STATE)
|
||||
val userName = "a-user"
|
||||
val fullMatrixId = "@$userName:${A_HOMESERVER_URL.removePrefix("https://")}"
|
||||
givenUserNameIsAvailable(userName)
|
||||
val test = viewModel.test()
|
||||
|
||||
viewModel.handle(OnboardingAction.UserNameEnteredAction.Registration(fullMatrixId))
|
||||
|
||||
test
|
||||
.assertStatesChanges(
|
||||
initialState,
|
||||
{ copy(isLoading = true) },
|
||||
{ copy(selectedHomeserver = SELECTED_HOMESERVER_STATE) },
|
||||
{ copy(registrationState = availableRegistrationState(userName, A_HOMESERVER_URL)) },
|
||||
{ copy(isLoading = false) },
|
||||
)
|
||||
.assertEvents(OnboardingViewEvents.OnHomeserverEdited)
|
||||
.finish()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given unavailable full matrix id, when a register username is entered, then emits availability error`() = runTest {
|
||||
viewModelWith(initialRegistrationState("ignored-url"))
|
||||
givenCanSuccessfullyUpdateHomeserver(A_HOMESERVER_URL, SELECTED_HOMESERVER_STATE)
|
||||
val userName = "a-user"
|
||||
val fullMatrixId = "@$userName:${A_HOMESERVER_URL.removePrefix("https://")}"
|
||||
givenUserNameIsUnavailable(userName, A_SERVER_ERROR)
|
||||
val test = viewModel.test()
|
||||
|
||||
viewModel.handle(OnboardingAction.UserNameEnteredAction.Registration(fullMatrixId))
|
||||
|
||||
test
|
||||
.assertStatesChanges(
|
||||
initialState,
|
||||
{ copy(isLoading = true) },
|
||||
{ copy(selectedHomeserver = SELECTED_HOMESERVER_STATE) },
|
||||
{ copy(isLoading = false) },
|
||||
)
|
||||
.assertEvents(OnboardingViewEvents.OnHomeserverEdited, OnboardingViewEvents.Failure(A_SERVER_ERROR))
|
||||
.finish()
|
||||
}
|
||||
|
||||
private fun availableRegistrationState(userName: String, homeServerUrl: String) = RegistrationState(
|
||||
isUserNameAvailable = true,
|
||||
selectedMatrixId = "@$userName:${homeServerUrl.removePrefix("https://")}"
|
||||
)
|
||||
|
||||
private fun initialRegistrationState(homeServerUrl: String) = initialState.copy(
|
||||
onboardingFlow = OnboardingFlow.SignUp, selectedHomeserver = SelectedHomeserverState(userFacingUrl = homeServerUrl)
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `given in the sign up flow, when editing homeserver errors, then does not update the selected homeserver state and emits error`() = runTest {
|
||||
viewModelWith(initialState.copy(onboardingFlow = OnboardingFlow.SignUp))
|
||||
@ -640,6 +730,14 @@ class OnboardingViewModelTest {
|
||||
givenRegistrationResultFor(RegisterAction.StartRegistration, RegistrationActionHandler.Result.Error(error))
|
||||
fakeHomeServerHistoryService.expectUrlToBeAdded(A_HOMESERVER_CONFIG.homeServerUri.toString())
|
||||
}
|
||||
|
||||
private fun givenUserNameIsAvailable(userName: String) {
|
||||
fakeAuthenticationService.givenRegistrationWizard(FakeRegistrationWizard().also { it.givenUserNameIsAvailable(userName) })
|
||||
}
|
||||
|
||||
private fun givenUserNameIsUnavailable(userName: String, failure: Failure.ServerError) {
|
||||
fakeAuthenticationService.givenRegistrationWizard(FakeRegistrationWizard().also { it.givenUserNameIsUnavailable(userName, failure) })
|
||||
}
|
||||
}
|
||||
|
||||
private fun HomeServerCapabilities.toPersonalisationState() = PersonalizationState(
|
||||
|
@ -16,13 +16,10 @@
|
||||
|
||||
package im.vector.app.features.onboarding
|
||||
|
||||
import im.vector.app.R
|
||||
import im.vector.app.features.login.LoginMode
|
||||
import im.vector.app.features.onboarding.StartAuthenticationFlowUseCase.StartAuthenticationResult
|
||||
import im.vector.app.test.fakes.FakeAuthenticationService
|
||||
import im.vector.app.test.fakes.FakeStringProvider
|
||||
import im.vector.app.test.fakes.FakeUri
|
||||
import im.vector.app.test.fakes.toTestString
|
||||
import io.mockk.coVerifyOrder
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.amshove.kluent.shouldBeEqualTo
|
||||
@ -33,7 +30,6 @@ import org.matrix.android.sdk.api.auth.data.LoginFlowResult
|
||||
import org.matrix.android.sdk.api.auth.data.LoginFlowTypes
|
||||
import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider
|
||||
|
||||
private const val MATRIX_ORG_URL = "https://any-value.org/"
|
||||
private const val A_DECLARED_HOMESERVER_URL = "https://foo.bar"
|
||||
private val A_HOMESERVER_CONFIG = HomeServerConnectionConfig(homeServerUri = FakeUri().instance)
|
||||
private val SSO_IDENTITY_PROVIDERS = emptyList<SsoIdentityProvider>()
|
||||
@ -41,9 +37,8 @@ private val SSO_IDENTITY_PROVIDERS = emptyList<SsoIdentityProvider>()
|
||||
class StartAuthenticationFlowUseCaseTest {
|
||||
|
||||
private val fakeAuthenticationService = FakeAuthenticationService()
|
||||
private val fakeStringProvider = FakeStringProvider()
|
||||
|
||||
private val useCase = StartAuthenticationFlowUseCase(fakeAuthenticationService, fakeStringProvider.instance)
|
||||
private val useCase = StartAuthenticationFlowUseCase(fakeAuthenticationService)
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
@ -106,21 +101,6 @@ class StartAuthenticationFlowUseCaseTest {
|
||||
verifyClearsAndThenStartsLogin(A_HOMESERVER_CONFIG)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given matrix dot org url when starting authentication flow then provides description`() = runTest {
|
||||
val matrixOrgConfig = HomeServerConnectionConfig(homeServerUri = FakeUri(MATRIX_ORG_URL).instance)
|
||||
fakeStringProvider.given(R.string.matrix_org_server_url, result = MATRIX_ORG_URL)
|
||||
fakeAuthenticationService.givenLoginFlow(matrixOrgConfig, aLoginResult())
|
||||
|
||||
val result = useCase.execute(matrixOrgConfig)
|
||||
|
||||
result shouldBeEqualTo expectedResult(
|
||||
description = R.string.ftue_auth_create_account_matrix_dot_org_server_description.toTestString(),
|
||||
homeserverSourceUrl = MATRIX_ORG_URL
|
||||
)
|
||||
verifyClearsAndThenStartsLogin(matrixOrgConfig)
|
||||
}
|
||||
|
||||
private fun aLoginResult(
|
||||
supportedLoginTypes: List<String> = emptyList()
|
||||
) = LoginFlowResult(
|
||||
@ -134,14 +114,12 @@ class StartAuthenticationFlowUseCaseTest {
|
||||
|
||||
private fun expectedResult(
|
||||
isHomeserverOutdated: Boolean = false,
|
||||
description: String? = null,
|
||||
preferredLoginMode: LoginMode = LoginMode.Unsupported,
|
||||
supportedLoginTypes: List<String> = emptyList(),
|
||||
homeserverSourceUrl: String = A_HOMESERVER_CONFIG.homeServerUri.toString()
|
||||
) = StartAuthenticationResult(
|
||||
isHomeserverOutdated,
|
||||
SelectedHomeserverState(
|
||||
description = description,
|
||||
userFacingUrl = homeserverSourceUrl,
|
||||
upstreamUrl = A_DECLARED_HOMESERVER_URL,
|
||||
preferredLoginMode = preferredLoginMode,
|
||||
|
@ -20,8 +20,10 @@ import io.mockk.coEvery
|
||||
import io.mockk.coVerify
|
||||
import io.mockk.mockk
|
||||
import org.matrix.android.sdk.api.auth.registration.RegisterThreePid
|
||||
import org.matrix.android.sdk.api.auth.registration.RegistrationAvailability
|
||||
import org.matrix.android.sdk.api.auth.registration.RegistrationResult
|
||||
import org.matrix.android.sdk.api.auth.registration.RegistrationWizard
|
||||
import org.matrix.android.sdk.api.failure.Failure
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
|
||||
class FakeRegistrationWizard : RegistrationWizard by mockk(relaxed = false) {
|
||||
@ -43,6 +45,14 @@ class FakeRegistrationWizard : RegistrationWizard by mockk(relaxed = false) {
|
||||
}
|
||||
}
|
||||
|
||||
fun givenUserNameIsAvailable(userName: String) {
|
||||
coEvery { registrationAvailable(userName) } returns RegistrationAvailability.Available
|
||||
}
|
||||
|
||||
fun givenUserNameIsUnavailable(userName: String, failure: Failure.ServerError) {
|
||||
coEvery { registrationAvailable(userName) } returns RegistrationAvailability.NotAvailable(failure)
|
||||
}
|
||||
|
||||
fun verifyCheckedEmailedVerification(times: Int) {
|
||||
coVerify(exactly = times) { checkIfEmailHasBeenValidated(any()) }
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user