mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2025-03-17 11:48:51 +03:00
checking user name is available at the point of user name entry during the registration flow
This commit is contained in:
parent
b8d4ff552f
commit
1062bfe039
7 changed files with 117 additions and 33 deletions
|
@ -58,6 +58,7 @@ sealed interface OnboardingAction : VectorViewModelAction {
|
|||
}
|
||||
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
|
||||
}
|
||||
|
@ -74,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
|
||||
|
@ -168,19 +171,46 @@ class OnboardingViewModel @AssistedInject constructor(
|
|||
}
|
||||
|
||||
private fun handleUserNameEntered(action: OnboardingAction.UserNameEnteredAction) {
|
||||
when(action) {
|
||||
when (action) {
|
||||
is OnboardingAction.UserNameEnteredAction.Login -> maybeUpdateHomeserver(action.userId)
|
||||
is OnboardingAction.UserNameEnteredAction.Registration -> maybeUpdateHomeserver(action.userId)
|
||||
is OnboardingAction.UserNameEnteredAction.Registration -> maybeUpdateHomeserver(action.userId, continuation = { userName ->
|
||||
checkUserNameAvailability(userName)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private fun maybeUpdateHomeserver(userNameOrMatrixId: String) {
|
||||
private fun maybeUpdateHomeserver(userNameOrMatrixId: String, continuation: suspend (String) -> Unit = {}) {
|
||||
val isFullMatrixId = MatrixPatterns.isUserId(userNameOrMatrixId)
|
||||
if (isFullMatrixId) {
|
||||
val domain = userNameOrMatrixId.getServerName().substringBeforeLast(":").ensureProtocol()
|
||||
handleHomeserverChange(OnboardingAction.HomeServerChange.EditHomeServer(domain))
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -191,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)
|
||||
}
|
||||
|
@ -329,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
|
||||
)
|
||||
)
|
||||
}
|
||||
|
@ -375,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 -> {
|
||||
|
@ -387,6 +427,11 @@ class OnboardingViewModel @AssistedInject constructor(
|
|||
}
|
||||
}
|
||||
OnboardingAction.ResetDeeplinkConfig -> loginConfig = null
|
||||
OnboardingAction.ResetSelectedRegistrationUserName -> {
|
||||
setState {
|
||||
copy(registrationState = RegistrationState())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -626,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(),
|
||||
|
||||
|
@ -95,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
|
||||
|
|
|
@ -33,6 +33,8 @@ 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
|
||||
|
@ -47,6 +49,7 @@ 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.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider
|
||||
import org.matrix.android.sdk.api.failure.isHomeserverUnavailable
|
||||
import org.matrix.android.sdk.api.failure.isInvalidPassword
|
||||
|
@ -55,6 +58,7 @@ 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 FtueAuthCombinedRegisterFragment @Inject constructor() : AbstractSSOFtueAuthFragment<FragmentFtueCombinedRegisterBinding>() {
|
||||
|
@ -69,6 +73,12 @@ 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.UserNameEnteredAction.Registration(views.createAccountInput.content()))
|
||||
}
|
||||
|
@ -103,7 +113,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,16 +168,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()
|
||||
|
||||
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()
|
||||
|
|
|
@ -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,7 +62,7 @@
|
|||
android:gravity="center"
|
||||
android:text="@string/ftue_auth_create_account_title"
|
||||
android:textColor="?vctr_content_primary"
|
||||
app:layout_constraintBottom_toTopOf="@id/createAccountHeaderTitle"
|
||||
app:layout_constraintBottom_toTopOf="@id/titleContentSpacing"
|
||||
app:layout_constraintEnd_toEndOf="@id/createAccountGutterEnd"
|
||||
app:layout_constraintStart_toStartOf="@id/createAccountGutterStart"
|
||||
app:layout_constraintTop_toBottomOf="@id/createAccountHeaderIcon" />
|
||||
|
@ -160,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,9 +11,10 @@
|
|||
|
||||
<!-- WIP -->
|
||||
<string name="ftue_auth_create_account_title">Create your account</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>
|
||||
|
|
|
@ -290,13 +290,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.UserNameEnteredAction.Registration(fullMatrixId))
|
||||
viewModel.handle(OnboardingAction.UserNameEnteredAction.Login(fullMatrixId))
|
||||
|
||||
test
|
||||
.assertStatesChanges(
|
||||
|
@ -311,12 +311,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.UserNameEnteredAction.Registration(onlyUsername))
|
||||
viewModel.handle(OnboardingAction.UserNameEnteredAction.Login(onlyUsername))
|
||||
|
||||
test
|
||||
.assertStates(initialState)
|
||||
|
|
Loading…
Add table
Reference in a new issue