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() }
|
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 (~),
|
* 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.
|
* 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
|
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 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 android.util.Patterns
|
||||||
import com.google.i18n.phonenumbers.NumberParseException
|
import com.google.i18n.phonenumbers.NumberParseException
|
||||||
import com.google.i18n.phonenumbers.PhoneNumberUtil
|
import com.google.i18n.phonenumbers.PhoneNumberUtil
|
||||||
|
import org.matrix.android.sdk.api.MatrixPatterns
|
||||||
import org.matrix.android.sdk.api.extensions.ensurePrefix
|
import org.matrix.android.sdk.api.extensions.ensurePrefix
|
||||||
|
|
||||||
fun Boolean.toOnOff() = if (this) "ON" else "OFF"
|
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.isEmail() = Patterns.EMAIL_ADDRESS.matcher(this).matches()
|
||||||
|
|
||||||
|
fun CharSequence.isMatrixId() = MatrixPatterns.isUserId(this.toString())
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return empty CharSequence if the CharSequence is null.
|
* 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.hasContent() = !editText().text.isNullOrEmpty()
|
||||||
|
|
||||||
fun TextInputLayout.clearErrorOnChange(lifecycleOwner: LifecycleOwner) {
|
fun TextInputLayout.clearErrorOnChange(lifecycleOwner: LifecycleOwner) {
|
||||||
|
onTextChange(lifecycleOwner) {
|
||||||
|
error = null
|
||||||
|
isErrorEnabled = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun TextInputLayout.onTextChange(lifecycleOwner: LifecycleOwner, action: (CharSequence) -> Unit) {
|
||||||
editText().textChanges()
|
editText().textChanges()
|
||||||
.onEach { error = null }
|
.onEach(action)
|
||||||
.launchIn(lifecycleOwner.lifecycleScope)
|
.launchIn(lifecycleOwner.lifecycleScope)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,9 +52,13 @@ sealed interface OnboardingAction : VectorViewModelAction {
|
|||||||
object ResendResetPassword : OnboardingAction
|
object ResendResetPassword : OnboardingAction
|
||||||
object ResetPasswordMailConfirmed : 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 {
|
sealed interface AuthenticateAction : OnboardingAction {
|
||||||
data class Register(val username: String, val password: String, val initialDeviceName: String) : AuthenticateAction
|
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 Login(val username: String, val password: String, val initialDeviceName: String) : AuthenticateAction
|
||||||
data class LoginDirect(val matrixId: 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 ResetSignMode : ResetAction
|
||||||
object ResetAuthenticationAttempt : ResetAction
|
object ResetAuthenticationAttempt : ResetAction
|
||||||
object ResetResetPassword : ResetAction
|
object ResetResetPassword : ResetAction
|
||||||
|
object ResetSelectedRegistrationUserName : ResetAction
|
||||||
|
|
||||||
// Homeserver history
|
// Homeserver history
|
||||||
object ClearHomeServerHistory : OnboardingAction
|
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.cancelCurrentOnSet
|
||||||
import im.vector.app.core.extensions.configureAndStart
|
import im.vector.app.core.extensions.configureAndStart
|
||||||
import im.vector.app.core.extensions.inferNoConnectivity
|
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.extensions.vectorStore
|
||||||
import im.vector.app.core.platform.VectorViewModel
|
import im.vector.app.core.platform.VectorViewModel
|
||||||
import im.vector.app.core.resources.BuildMeta
|
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.HomeServerConnectionConfig
|
||||||
import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider
|
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.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.auth.registration.RegistrationWizard
|
||||||
import org.matrix.android.sdk.api.failure.isHomeserverUnavailable
|
import org.matrix.android.sdk.api.failure.isHomeserverUnavailable
|
||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
@ -144,7 +147,7 @@ class OnboardingViewModel @AssistedInject constructor(
|
|||||||
is OnboardingAction.UpdateSignMode -> handleUpdateSignMode(action)
|
is OnboardingAction.UpdateSignMode -> handleUpdateSignMode(action)
|
||||||
is OnboardingAction.InitWith -> handleInitWith(action)
|
is OnboardingAction.InitWith -> handleInitWith(action)
|
||||||
is OnboardingAction.HomeServerChange -> withAction(action) { handleHomeserverChange(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 AuthenticateAction -> withAction(action) { handleAuthenticateAction(action) }
|
||||||
is OnboardingAction.LoginWithToken -> handleLoginWithToken(action)
|
is OnboardingAction.LoginWithToken -> handleLoginWithToken(action)
|
||||||
is OnboardingAction.WebLoginSuccess -> handleWebLoginSuccess(action)
|
is OnboardingAction.WebLoginSuccess -> handleWebLoginSuccess(action)
|
||||||
@ -167,13 +170,47 @@ class OnboardingViewModel @AssistedInject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleMaybeUpdateHomeserver(action: OnboardingAction.MaybeUpdateHomeserverFromMatrixId) {
|
private fun handleUserNameEntered(action: OnboardingAction.UserNameEnteredAction) {
|
||||||
val isFullMatrixId = MatrixPatterns.isUserId(action.userId)
|
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) {
|
if (isFullMatrixId) {
|
||||||
val domain = action.userId.getServerName().substringBeforeLast(":").ensureProtocol()
|
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 {
|
} 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) {
|
private fun handleAuthenticateAction(action: AuthenticateAction) {
|
||||||
when (action) {
|
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.Login -> handleLogin(action)
|
||||||
is AuthenticateAction.LoginDirect -> handleDirectLogin(action, homeServerConnectionConfig = null)
|
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 {
|
setState {
|
||||||
val authDescription = AuthenticationDescription.Register(AuthenticationDescription.AuthenticationType.Password)
|
val authDescription = AuthenticationDescription.Register(AuthenticationDescription.AuthenticationType.Password)
|
||||||
copy(selectedAuthenticationState = SelectedAuthenticationState(authDescription))
|
copy(selectedAuthenticationState = SelectedAuthenticationState(authDescription))
|
||||||
}
|
}
|
||||||
reAuthHelper.data = action.password
|
reAuthHelper.data = password
|
||||||
handleRegisterAction(
|
handleRegisterAction(
|
||||||
RegisterAction.CreateAccount(
|
RegisterAction.CreateAccount(
|
||||||
action.username,
|
userName,
|
||||||
action.password,
|
password,
|
||||||
action.initialDeviceName
|
initialDeviceName
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -368,7 +410,12 @@ class OnboardingViewModel @AssistedInject constructor(
|
|||||||
OnboardingAction.ResetAuthenticationAttempt -> {
|
OnboardingAction.ResetAuthenticationAttempt -> {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
authenticationService.cancelPendingLoginOrRegistration()
|
authenticationService.cancelPendingLoginOrRegistration()
|
||||||
setState { copy(isLoading = false) }
|
setState {
|
||||||
|
copy(
|
||||||
|
isLoading = false,
|
||||||
|
registrationState = RegistrationState(),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
OnboardingAction.ResetResetPassword -> {
|
OnboardingAction.ResetResetPassword -> {
|
||||||
@ -380,6 +427,11 @@ class OnboardingViewModel @AssistedInject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
OnboardingAction.ResetDeeplinkConfig -> loginConfig = null
|
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)
|
val homeServerConnectionConfig = homeServerConnectionConfigFactory.create(action.homeServerUrl)
|
||||||
if (homeServerConnectionConfig == null) {
|
if (homeServerConnectionConfig == null) {
|
||||||
// This is invalid
|
// This is invalid
|
||||||
_viewEvents.post(OnboardingViewEvents.Failure(Throwable("Unable to create a HomeServerConnectionConfig")))
|
_viewEvents.post(OnboardingViewEvents.Failure(Throwable("Unable to create a HomeServerConnectionConfig")))
|
||||||
} else {
|
} else {
|
||||||
startAuthenticationFlow(action, homeServerConnectionConfig, serverTypeOverride)
|
startAuthenticationFlow(action, homeServerConnectionConfig, serverTypeOverride, postAction)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun startAuthenticationFlow(
|
private fun startAuthenticationFlow(
|
||||||
trigger: OnboardingAction.HomeServerChange,
|
trigger: OnboardingAction.HomeServerChange,
|
||||||
homeServerConnectionConfig: HomeServerConnectionConfig,
|
homeServerConnectionConfig: HomeServerConnectionConfig,
|
||||||
serverTypeOverride: ServerType?
|
serverTypeOverride: ServerType?,
|
||||||
|
postAction: suspend () -> Unit = {},
|
||||||
) {
|
) {
|
||||||
currentHomeServerConnectionConfig = homeServerConnectionConfig
|
currentHomeServerConnectionConfig = homeServerConnectionConfig
|
||||||
|
|
||||||
currentJob = viewModelScope.launch {
|
currentJob = viewModelScope.launch {
|
||||||
setState { copy(isLoading = true) }
|
setState { copy(isLoading = true) }
|
||||||
runCatching { startAuthenticationFlowUseCase.execute(homeServerConnectionConfig) }.fold(
|
runCatching { startAuthenticationFlowUseCase.execute(homeServerConnectionConfig) }.fold(
|
||||||
onSuccess = { onAuthenticationStartedSuccess(trigger, homeServerConnectionConfig, it, serverTypeOverride) },
|
onSuccess = {
|
||||||
|
onAuthenticationStartedSuccess(trigger, homeServerConnectionConfig, it, serverTypeOverride)
|
||||||
|
postAction()
|
||||||
|
},
|
||||||
onFailure = { onAuthenticationStartError(it, trigger) }
|
onFailure = { onAuthenticationStartError(it, trigger) }
|
||||||
)
|
)
|
||||||
setState { copy(isLoading = false) }
|
setState { copy(isLoading = false) }
|
||||||
|
@ -48,6 +48,9 @@ data class OnboardingViewState(
|
|||||||
val knownCustomHomeServersUrls: List<String> = emptyList(),
|
val knownCustomHomeServersUrls: List<String> = emptyList(),
|
||||||
val isForceLoginFallbackEnabled: Boolean = false,
|
val isForceLoginFallbackEnabled: Boolean = false,
|
||||||
|
|
||||||
|
@PersistState
|
||||||
|
val registrationState: RegistrationState = RegistrationState(),
|
||||||
|
|
||||||
@PersistState
|
@PersistState
|
||||||
val selectedHomeserver: SelectedHomeserverState = SelectedHomeserverState(),
|
val selectedHomeserver: SelectedHomeserverState = SelectedHomeserverState(),
|
||||||
|
|
||||||
@ -66,7 +69,6 @@ enum class OnboardingFlow {
|
|||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class SelectedHomeserverState(
|
data class SelectedHomeserverState(
|
||||||
val description: String? = null,
|
|
||||||
val userFacingUrl: String? = null,
|
val userFacingUrl: String? = null,
|
||||||
val upstreamUrl: String? = null,
|
val upstreamUrl: String? = null,
|
||||||
val preferredLoginMode: LoginMode = LoginMode.Unknown,
|
val preferredLoginMode: LoginMode = LoginMode.Unknown,
|
||||||
@ -96,3 +98,9 @@ data class ResetState(
|
|||||||
data class SelectedAuthenticationState(
|
data class SelectedAuthenticationState(
|
||||||
val description: AuthenticationDescription? = null,
|
val description: AuthenticationDescription? = null,
|
||||||
) : Parcelable
|
) : Parcelable
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
data class RegistrationState(
|
||||||
|
val isUserNameAvailable: Boolean = false,
|
||||||
|
val selectedMatrixId: String? = null,
|
||||||
|
) : Parcelable
|
||||||
|
@ -16,10 +16,7 @@
|
|||||||
|
|
||||||
package im.vector.app.features.onboarding
|
package im.vector.app.features.onboarding
|
||||||
|
|
||||||
import im.vector.app.R
|
|
||||||
import im.vector.app.core.extensions.containsAllItems
|
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 im.vector.app.features.login.LoginMode
|
||||||
import org.matrix.android.sdk.api.auth.AuthenticationService
|
import org.matrix.android.sdk.api.auth.AuthenticationService
|
||||||
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
|
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
|
||||||
@ -29,7 +26,6 @@ import javax.inject.Inject
|
|||||||
|
|
||||||
class StartAuthenticationFlowUseCase @Inject constructor(
|
class StartAuthenticationFlowUseCase @Inject constructor(
|
||||||
private val authenticationService: AuthenticationService,
|
private val authenticationService: AuthenticationService,
|
||||||
private val stringProvider: StringProvider
|
|
||||||
) {
|
) {
|
||||||
|
|
||||||
suspend fun execute(config: HomeServerConnectionConfig): StartAuthenticationResult {
|
suspend fun execute(config: HomeServerConnectionConfig): StartAuthenticationResult {
|
||||||
@ -46,10 +42,6 @@ class StartAuthenticationFlowUseCase @Inject constructor(
|
|||||||
config: HomeServerConnectionConfig,
|
config: HomeServerConnectionConfig,
|
||||||
preferredLoginMode: LoginMode
|
preferredLoginMode: LoginMode
|
||||||
) = SelectedHomeserverState(
|
) = 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(),
|
userFacingUrl = config.homeServerUri.toString(),
|
||||||
upstreamUrl = authFlow.homeServerUrl,
|
upstreamUrl = authFlow.homeServerUrl,
|
||||||
preferredLoginMode = preferredLoginMode,
|
preferredLoginMode = preferredLoginMode,
|
||||||
@ -57,8 +49,6 @@ class StartAuthenticationFlowUseCase @Inject constructor(
|
|||||||
isLogoutDevicesSupported = authFlow.isLogoutDevicesSupported
|
isLogoutDevicesSupported = authFlow.isLogoutDevicesSupported
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun matrixOrgUrl() = stringProvider.getString(R.string.matrix_org_server_url).ensureTrailingSlash()
|
|
||||||
|
|
||||||
private fun LoginFlowResult.findPreferredLoginMode() = when {
|
private fun LoginFlowResult.findPreferredLoginMode() = when {
|
||||||
supportedLoginTypes.containsAllItems(LoginFlowTypes.SSO, LoginFlowTypes.PASSWORD) -> LoginMode.SsoAndPassword(ssoIdentityProviders)
|
supportedLoginTypes.containsAllItems(LoginFlowTypes.SSO, LoginFlowTypes.PASSWORD) -> LoginMode.SsoAndPassword(ssoIdentityProviders)
|
||||||
supportedLoginTypes.contains(LoginFlowTypes.SSO) -> LoginMode.Sso(ssoIdentityProviders)
|
supportedLoginTypes.contains(LoginFlowTypes.SSO) -> LoginMode.Sso(ssoIdentityProviders)
|
||||||
|
@ -25,6 +25,7 @@ import androidx.autofill.HintConstants
|
|||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import im.vector.app.R
|
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.content
|
||||||
import im.vector.app.core.extensions.editText
|
import im.vector.app.core.extensions.editText
|
||||||
import im.vector.app.core.extensions.hideKeyboard
|
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.OnboardingAction
|
||||||
import im.vector.app.features.onboarding.OnboardingViewEvents
|
import im.vector.app.features.onboarding.OnboardingViewEvents
|
||||||
import im.vector.app.features.onboarding.OnboardingViewState
|
import im.vector.app.features.onboarding.OnboardingViewState
|
||||||
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider
|
import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider
|
||||||
|
import reactivecircus.flowbinding.android.widget.textChanges
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class FtueAuthCombinedLoginFragment @Inject constructor(
|
class FtueAuthCombinedLoginFragment @Inject constructor(
|
||||||
@ -60,14 +63,18 @@ class FtueAuthCombinedLoginFragment @Inject constructor(
|
|||||||
views.loginRoot.realignPercentagesToParent()
|
views.loginRoot.realignPercentagesToParent()
|
||||||
views.editServerButton.debouncedClicks { viewModel.handle(OnboardingAction.PostViewEvent(OnboardingViewEvents.EditServerSelection)) }
|
views.editServerButton.debouncedClicks { viewModel.handle(OnboardingAction.PostViewEvent(OnboardingViewEvents.EditServerSelection)) }
|
||||||
views.loginPasswordInput.setOnImeDoneListener { submit() }
|
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)) }
|
views.loginForgotPassword.debouncedClicks { viewModel.handle(OnboardingAction.PostViewEvent(OnboardingViewEvents.OnForgetPasswordClicked)) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupSubmitButton() {
|
private fun setupSubmitButton() {
|
||||||
views.loginSubmit.setOnClickListener { submit() }
|
views.loginSubmit.setOnClickListener { submit() }
|
||||||
observeContentChangesAndResetErrors(views.loginInput, views.loginPasswordInput, views.loginSubmit)
|
views.loginInput.clearErrorOnChange(viewLifecycleOwner)
|
||||||
.launchIn(viewLifecycleOwner.lifecycleScope)
|
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() {
|
private fun submit() {
|
||||||
@ -105,7 +112,6 @@ class FtueAuthCombinedLoginFragment @Inject constructor(
|
|||||||
setupAutoFill()
|
setupAutoFill()
|
||||||
|
|
||||||
views.selectedServerName.text = state.selectedHomeserver.userFacingUrl.toReducedUrl()
|
views.selectedServerName.text = state.selectedHomeserver.userFacingUrl.toReducedUrl()
|
||||||
views.selectedServerDescription.text = state.selectedHomeserver.description
|
|
||||||
|
|
||||||
if (state.isLoading) {
|
if (state.isLoading) {
|
||||||
// Ensure password is hidden
|
// Ensure password is hidden
|
||||||
|
@ -28,11 +28,14 @@ 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 im.vector.app.R
|
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.content
|
||||||
import im.vector.app.core.extensions.editText
|
import im.vector.app.core.extensions.editText
|
||||||
import im.vector.app.core.extensions.hasSurroundingSpaces
|
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.isMatrixId
|
||||||
|
import im.vector.app.core.extensions.onTextChange
|
||||||
import im.vector.app.core.extensions.realignPercentagesToParent
|
import im.vector.app.core.extensions.realignPercentagesToParent
|
||||||
import im.vector.app.core.extensions.setOnFocusLostListener
|
import im.vector.app.core.extensions.setOnFocusLostListener
|
||||||
import im.vector.app.core.extensions.setOnImeDoneListener
|
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.OnboardingAction.AuthenticateAction
|
||||||
import im.vector.app.features.onboarding.OnboardingViewEvents
|
import im.vector.app.features.onboarding.OnboardingViewEvents
|
||||||
import im.vector.app.features.onboarding.OnboardingViewState
|
import im.vector.app.features.onboarding.OnboardingViewState
|
||||||
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider
|
import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider
|
||||||
import org.matrix.android.sdk.api.failure.isHomeserverUnavailable
|
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.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
|
||||||
|
|
||||||
|
private const val MINIMUM_PASSWORD_LENGTH = 8
|
||||||
|
|
||||||
class FtueAuthCombinedRegisterFragment @Inject constructor() : AbstractSSOFtueAuthFragment<FragmentFtueCombinedRegisterBinding>() {
|
class FtueAuthCombinedRegisterFragment @Inject constructor() : AbstractSSOFtueAuthFragment<FragmentFtueCombinedRegisterBinding>() {
|
||||||
|
|
||||||
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentFtueCombinedRegisterBinding {
|
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentFtueCombinedRegisterBinding {
|
||||||
@ -69,15 +76,27 @@ class FtueAuthCombinedRegisterFragment @Inject constructor() : AbstractSSOFtueAu
|
|||||||
views.createAccountRoot.realignPercentagesToParent()
|
views.createAccountRoot.realignPercentagesToParent()
|
||||||
views.editServerButton.debouncedClicks { viewModel.handle(OnboardingAction.PostViewEvent(OnboardingViewEvents.EditServerSelection)) }
|
views.editServerButton.debouncedClicks { viewModel.handle(OnboardingAction.PostViewEvent(OnboardingViewEvents.EditServerSelection)) }
|
||||||
views.createAccountPasswordInput.setOnImeDoneListener { submit() }
|
views.createAccountPasswordInput.setOnImeDoneListener { submit() }
|
||||||
|
|
||||||
|
views.createAccountInput.onTextChange(viewLifecycleOwner) {
|
||||||
|
viewModel.handle(OnboardingAction.ResetSelectedRegistrationUserName)
|
||||||
|
views.createAccountEntryFooter.text = ""
|
||||||
|
}
|
||||||
|
|
||||||
views.createAccountInput.setOnFocusLostListener {
|
views.createAccountInput.setOnFocusLostListener {
|
||||||
viewModel.handle(OnboardingAction.MaybeUpdateHomeserverFromMatrixId(views.createAccountInput.content()))
|
viewModel.handle(OnboardingAction.UserNameEnteredAction.Registration(views.createAccountInput.content()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupSubmitButton() {
|
private fun setupSubmitButton() {
|
||||||
views.createAccountSubmit.setOnClickListener { submit() }
|
views.createAccountSubmit.setOnClickListener { submit() }
|
||||||
observeContentChangesAndResetErrors(views.createAccountInput, views.createAccountPasswordInput, views.createAccountSubmit)
|
views.createAccountInput.clearErrorOnChange(viewLifecycleOwner)
|
||||||
.launchIn(viewLifecycleOwner.lifecycleScope)
|
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() {
|
private fun submit() {
|
||||||
@ -103,7 +122,12 @@ class FtueAuthCombinedRegisterFragment @Inject constructor() : AbstractSSOFtueAu
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (error == 0) {
|
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) {
|
override fun updateWithState(state: OnboardingViewState) {
|
||||||
setupUi(state)
|
setupUi(state)
|
||||||
setupAutoFill()
|
setupAutoFill()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupUi(state: OnboardingViewState) {
|
||||||
views.selectedServerName.text = state.selectedHomeserver.userFacingUrl.toReducedUrl()
|
views.selectedServerName.text = state.selectedHomeserver.userFacingUrl.toReducedUrl()
|
||||||
views.selectedServerDescription.text = state.selectedHomeserver.description
|
|
||||||
|
|
||||||
if (state.isLoading) {
|
if (state.isLoading) {
|
||||||
// Ensure password is hidden
|
// Ensure password is hidden
|
||||||
views.createAccountPasswordInput.editText().hidePassword()
|
views.createAccountPasswordInput.editText().hidePassword()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
views.createAccountEntryFooter.text = when {
|
||||||
|
state.registrationState.isUserNameAvailable -> getString(
|
||||||
|
R.string.ftue_auth_create_account_username_entry_footer,
|
||||||
|
state.registrationState.selectedMatrixId
|
||||||
|
)
|
||||||
|
|
||||||
|
else -> ""
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupUi(state: OnboardingViewState) {
|
|
||||||
when (state.selectedHomeserver.preferredLoginMode) {
|
when (state.selectedHomeserver.preferredLoginMode) {
|
||||||
is LoginMode.SsoAndPassword -> renderSsoProviders(state.deviceId, state.selectedHomeserver.preferredLoginMode.ssoIdentityProviders)
|
is LoginMode.SsoAndPassword -> renderSsoProviders(state.deviceId, state.selectedHomeserver.preferredLoginMode.ssoIdentityProviders)
|
||||||
else -> hideSsoProviders()
|
else -> hideSsoProviders()
|
||||||
|
@ -16,16 +16,10 @@
|
|||||||
|
|
||||||
package im.vector.app.features.onboarding.ftueauth
|
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.R
|
||||||
import im.vector.app.core.extensions.hasContentFlow
|
|
||||||
import im.vector.app.features.login.SignMode
|
import im.vector.app.features.login.SignMode
|
||||||
import im.vector.app.features.onboarding.OnboardingAction
|
import im.vector.app.features.onboarding.OnboardingAction
|
||||||
import im.vector.app.features.themes.ThemeProvider
|
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 {
|
fun SignMode.toAuthenticateAction(login: String, password: String, initialDeviceName: String): OnboardingAction.AuthenticateAction {
|
||||||
return when (this) {
|
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()) {
|
fun ThemeProvider.ftueBreakerBackground() = when (isLightTheme()) {
|
||||||
true -> R.drawable.bg_gradient_ftue_breaker
|
true -> R.drawable.bg_gradient_ftue_breaker
|
||||||
false -> R.drawable.bg_color_background
|
false -> R.drawable.bg_color_background
|
||||||
|
@ -34,8 +34,8 @@
|
|||||||
android:layout_height="52dp"
|
android:layout_height="52dp"
|
||||||
app:layout_constraintBottom_toTopOf="@id/createAccountHeaderIcon"
|
app:layout_constraintBottom_toTopOf="@id/createAccountHeaderIcon"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
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
|
<ImageView
|
||||||
android:id="@+id/createAccountHeaderIcon"
|
android:id="@+id/createAccountHeaderIcon"
|
||||||
@ -62,24 +62,10 @@
|
|||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:text="@string/ftue_auth_create_account_title"
|
android:text="@string/ftue_auth_create_account_title"
|
||||||
android:textColor="?vctr_content_primary"
|
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_constraintBottom_toTopOf="@id/titleContentSpacing"
|
||||||
app:layout_constraintEnd_toEndOf="@id/createAccountGutterEnd"
|
app:layout_constraintEnd_toEndOf="@id/createAccountGutterEnd"
|
||||||
app:layout_constraintStart_toStartOf="@id/createAccountGutterStart"
|
app:layout_constraintStart_toStartOf="@id/createAccountGutterStart"
|
||||||
app:layout_constraintTop_toBottomOf="@id/createAccountHeaderTitle" />
|
app:layout_constraintTop_toBottomOf="@id/createAccountHeaderIcon" />
|
||||||
|
|
||||||
<Space
|
<Space
|
||||||
android:id="@+id/titleContentSpacing"
|
android:id="@+id/titleContentSpacing"
|
||||||
@ -87,7 +73,7 @@
|
|||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
app:layout_constraintBottom_toTopOf="@id/chooseYourServerHeader"
|
app:layout_constraintBottom_toTopOf="@id/chooseYourServerHeader"
|
||||||
app:layout_constraintHeight_percent="0.03"
|
app:layout_constraintHeight_percent="0.03"
|
||||||
app:layout_constraintTop_toBottomOf="@id/createAccountHeaderSubtitle" />
|
app:layout_constraintTop_toBottomOf="@id/createAccountHeaderTitle" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/chooseYourServerHeader"
|
android:id="@+id/chooseYourServerHeader"
|
||||||
@ -110,22 +96,11 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="12dp"
|
android:layout_marginStart="12dp"
|
||||||
android:textColor="?vctr_content_primary"
|
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_constraintBottom_toTopOf="@id/serverSelectionSpacing"
|
||||||
app:layout_constraintEnd_toStartOf="@id/editServerButton"
|
app:layout_constraintEnd_toStartOf="@id/editServerButton"
|
||||||
app:layout_constraintStart_toStartOf="@id/createAccountGutterStart"
|
app:layout_constraintStart_toStartOf="@id/createAccountGutterStart"
|
||||||
app:layout_constraintTop_toBottomOf="@id/selectedServerName" />
|
app:layout_constraintTop_toBottomOf="@id/chooseYourServerHeader"
|
||||||
|
tools:text="matrix.org" />
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/editServerButton"
|
android:id="@+id/editServerButton"
|
||||||
@ -137,7 +112,7 @@
|
|||||||
android:paddingEnd="12dp"
|
android:paddingEnd="12dp"
|
||||||
android:text="@string/ftue_auth_create_account_edit_server_selection"
|
android:text="@string/ftue_auth_create_account_edit_server_selection"
|
||||||
android:textAllCaps="true"
|
android:textAllCaps="true"
|
||||||
app:layout_constraintBottom_toBottomOf="@id/selectedServerDescription"
|
app:layout_constraintBottom_toBottomOf="@id/selectedServerName"
|
||||||
app:layout_constraintEnd_toEndOf="@id/createAccountGutterEnd"
|
app:layout_constraintEnd_toEndOf="@id/createAccountGutterEnd"
|
||||||
app:layout_constraintTop_toTopOf="@id/chooseYourServerHeader" />
|
app:layout_constraintTop_toTopOf="@id/chooseYourServerHeader" />
|
||||||
|
|
||||||
@ -147,7 +122,7 @@
|
|||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
app:layout_constraintBottom_toTopOf="@id/createAccountInput"
|
app:layout_constraintBottom_toTopOf="@id/createAccountInput"
|
||||||
app:layout_constraintHeight_percent="0.05"
|
app:layout_constraintHeight_percent="0.05"
|
||||||
app:layout_constraintTop_toBottomOf="@id/selectedServerDescription" />
|
app:layout_constraintTop_toBottomOf="@id/selectedServerName" />
|
||||||
|
|
||||||
<View
|
<View
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
@ -185,18 +160,18 @@
|
|||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="4dp"
|
android:layout_marginTop="4dp"
|
||||||
android:text="@string/ftue_auth_create_account_username_entry_footer"
|
|
||||||
app:layout_constraintBottom_toTopOf="@id/entrySpacing"
|
app:layout_constraintBottom_toTopOf="@id/entrySpacing"
|
||||||
app:layout_constraintEnd_toEndOf="@id/createAccountGutterEnd"
|
app:layout_constraintEnd_toEndOf="@id/createAccountGutterEnd"
|
||||||
app:layout_constraintStart_toStartOf="@id/createAccountGutterStart"
|
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
|
<Space
|
||||||
android:id="@+id/entrySpacing"
|
android:id="@+id/entrySpacing"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
app:layout_constraintBottom_toTopOf="@id/createAccountPasswordInput"
|
app:layout_constraintBottom_toTopOf="@id/createAccountPasswordInput"
|
||||||
app:layout_constraintHeight_percent="0.03"
|
app:layout_constraintHeight_percent="0.02"
|
||||||
app:layout_constraintTop_toBottomOf="@id/createAccountEntryFooter" />
|
app:layout_constraintTop_toBottomOf="@id/createAccountEntryFooter" />
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
@ -11,12 +11,11 @@
|
|||||||
|
|
||||||
<!-- WIP -->
|
<!-- WIP -->
|
||||||
<string name="ftue_auth_create_account_title">Create your account</string>
|
<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>
|
<!-- 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">You can\'t change this later</string>
|
<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_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_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_create_account_edit_server_selection">Edit</string>
|
||||||
|
|
||||||
<string name="ftue_auth_welcome_back_title">Welcome back!</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.FakeHomeServerHistoryService
|
||||||
import im.vector.app.test.fakes.FakeLoginWizard
|
import im.vector.app.test.fakes.FakeLoginWizard
|
||||||
import im.vector.app.test.fakes.FakeRegistrationActionHandler
|
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.FakeSession
|
||||||
import im.vector.app.test.fakes.FakeStartAuthenticationFlowUseCase
|
import im.vector.app.test.fakes.FakeStartAuthenticationFlowUseCase
|
||||||
import im.vector.app.test.fakes.FakeStringProvider
|
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.FakeVectorFeatures
|
||||||
import im.vector.app.test.fakes.FakeVectorOverrides
|
import im.vector.app.test.fakes.FakeVectorOverrides
|
||||||
import im.vector.app.test.fakes.toTestString
|
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.aBuildMeta
|
||||||
import im.vector.app.test.fixtures.aHomeServerCapabilities
|
import im.vector.app.test.fixtures.aHomeServerCapabilities
|
||||||
import im.vector.app.test.test
|
import im.vector.app.test.test
|
||||||
@ -50,11 +52,13 @@ import org.junit.Rule
|
|||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
|
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
|
||||||
import org.matrix.android.sdk.api.auth.registration.Stage
|
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.Session
|
||||||
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities
|
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities
|
||||||
|
|
||||||
private const val A_DISPLAY_NAME = "a display name"
|
private const val A_DISPLAY_NAME = "a display name"
|
||||||
private const val A_PICTURE_FILENAME = "a-picture.png"
|
private const val A_PICTURE_FILENAME = "a-picture.png"
|
||||||
|
private val A_SERVER_ERROR = a401ServerError()
|
||||||
private val AN_ERROR = RuntimeException("an error!")
|
private val AN_ERROR = RuntimeException("an error!")
|
||||||
private val A_LOADABLE_REGISTER_ACTION = RegisterAction.StartRegistration
|
private val A_LOADABLE_REGISTER_ACTION = RegisterAction.StartRegistration
|
||||||
private val A_NON_LOADABLE_REGISTER_ACTION = RegisterAction.CheckIfEmailHasBeenValidated(delayMillis = -1L)
|
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 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 const val A_HOMESERVER_URL = "https://edited-homeserver.org"
|
||||||
private val A_HOMESERVER_CONFIG = HomeServerConnectionConfig(FakeUri().instance)
|
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 val SELECTED_HOMESERVER_STATE_SUPPORTED_LOGOUT_DEVICES = SelectedHomeserverState(isLogoutDevicesSupported = true)
|
||||||
private const val AN_EMAIL = "hello@example.com"
|
private const val AN_EMAIL = "hello@example.com"
|
||||||
private const val A_PASSWORD = "a-password"
|
private const val A_PASSWORD = "a-password"
|
||||||
@ -290,13 +294,13 @@ class OnboardingViewModelTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `given a full matrix id, when maybe updating homeserver, then updates selected homeserver state and emits edited event`() = runTest {
|
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.SignUp))
|
viewModelWith(initialState.copy(onboardingFlow = OnboardingFlow.SignIn))
|
||||||
givenCanSuccessfullyUpdateHomeserver(A_HOMESERVER_URL, SELECTED_HOMESERVER_STATE)
|
givenCanSuccessfullyUpdateHomeserver(A_HOMESERVER_URL, SELECTED_HOMESERVER_STATE)
|
||||||
val test = viewModel.test()
|
val test = viewModel.test()
|
||||||
val fullMatrixId = "@a-user:${A_HOMESERVER_URL.removePrefix("https://")}"
|
val fullMatrixId = "@a-user:${A_HOMESERVER_URL.removePrefix("https://")}"
|
||||||
|
|
||||||
viewModel.handle(OnboardingAction.MaybeUpdateHomeserverFromMatrixId(fullMatrixId))
|
viewModel.handle(OnboardingAction.UserNameEnteredAction.Login(fullMatrixId))
|
||||||
|
|
||||||
test
|
test
|
||||||
.assertStatesChanges(
|
.assertStatesChanges(
|
||||||
@ -311,12 +315,11 @@ class OnboardingViewModelTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `given a username, when maybe updating homeserver, then does nothing`() = runTest {
|
fun `given a username, when a login username is entered, then does nothing`() = runTest {
|
||||||
viewModelWith(initialState.copy(onboardingFlow = OnboardingFlow.SignUp))
|
|
||||||
val test = viewModel.test()
|
val test = viewModel.test()
|
||||||
val onlyUsername = "a-username"
|
val onlyUsername = "a-username"
|
||||||
|
|
||||||
viewModel.handle(OnboardingAction.MaybeUpdateHomeserverFromMatrixId(onlyUsername))
|
viewModel.handle(OnboardingAction.UserNameEnteredAction.Login(onlyUsername))
|
||||||
|
|
||||||
test
|
test
|
||||||
.assertStates(initialState)
|
.assertStates(initialState)
|
||||||
@ -324,6 +327,93 @@ class OnboardingViewModelTest {
|
|||||||
.finish()
|
.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
|
@Test
|
||||||
fun `given in the sign up flow, when editing homeserver errors, then does not update the selected homeserver state and emits error`() = runTest {
|
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))
|
viewModelWith(initialState.copy(onboardingFlow = OnboardingFlow.SignUp))
|
||||||
@ -640,6 +730,14 @@ class OnboardingViewModelTest {
|
|||||||
givenRegistrationResultFor(RegisterAction.StartRegistration, RegistrationActionHandler.Result.Error(error))
|
givenRegistrationResultFor(RegisterAction.StartRegistration, RegistrationActionHandler.Result.Error(error))
|
||||||
fakeHomeServerHistoryService.expectUrlToBeAdded(A_HOMESERVER_CONFIG.homeServerUri.toString())
|
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(
|
private fun HomeServerCapabilities.toPersonalisationState() = PersonalizationState(
|
||||||
|
@ -16,13 +16,10 @@
|
|||||||
|
|
||||||
package im.vector.app.features.onboarding
|
package im.vector.app.features.onboarding
|
||||||
|
|
||||||
import im.vector.app.R
|
|
||||||
import im.vector.app.features.login.LoginMode
|
import im.vector.app.features.login.LoginMode
|
||||||
import im.vector.app.features.onboarding.StartAuthenticationFlowUseCase.StartAuthenticationResult
|
import im.vector.app.features.onboarding.StartAuthenticationFlowUseCase.StartAuthenticationResult
|
||||||
import im.vector.app.test.fakes.FakeAuthenticationService
|
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.FakeUri
|
||||||
import im.vector.app.test.fakes.toTestString
|
|
||||||
import io.mockk.coVerifyOrder
|
import io.mockk.coVerifyOrder
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.amshove.kluent.shouldBeEqualTo
|
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.LoginFlowTypes
|
||||||
import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider
|
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 const val A_DECLARED_HOMESERVER_URL = "https://foo.bar"
|
||||||
private val A_HOMESERVER_CONFIG = HomeServerConnectionConfig(homeServerUri = FakeUri().instance)
|
private val A_HOMESERVER_CONFIG = HomeServerConnectionConfig(homeServerUri = FakeUri().instance)
|
||||||
private val SSO_IDENTITY_PROVIDERS = emptyList<SsoIdentityProvider>()
|
private val SSO_IDENTITY_PROVIDERS = emptyList<SsoIdentityProvider>()
|
||||||
@ -41,9 +37,8 @@ private val SSO_IDENTITY_PROVIDERS = emptyList<SsoIdentityProvider>()
|
|||||||
class StartAuthenticationFlowUseCaseTest {
|
class StartAuthenticationFlowUseCaseTest {
|
||||||
|
|
||||||
private val fakeAuthenticationService = FakeAuthenticationService()
|
private val fakeAuthenticationService = FakeAuthenticationService()
|
||||||
private val fakeStringProvider = FakeStringProvider()
|
|
||||||
|
|
||||||
private val useCase = StartAuthenticationFlowUseCase(fakeAuthenticationService, fakeStringProvider.instance)
|
private val useCase = StartAuthenticationFlowUseCase(fakeAuthenticationService)
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun setUp() {
|
fun setUp() {
|
||||||
@ -106,21 +101,6 @@ class StartAuthenticationFlowUseCaseTest {
|
|||||||
verifyClearsAndThenStartsLogin(A_HOMESERVER_CONFIG)
|
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(
|
private fun aLoginResult(
|
||||||
supportedLoginTypes: List<String> = emptyList()
|
supportedLoginTypes: List<String> = emptyList()
|
||||||
) = LoginFlowResult(
|
) = LoginFlowResult(
|
||||||
@ -134,14 +114,12 @@ class StartAuthenticationFlowUseCaseTest {
|
|||||||
|
|
||||||
private fun expectedResult(
|
private fun expectedResult(
|
||||||
isHomeserverOutdated: Boolean = false,
|
isHomeserverOutdated: Boolean = false,
|
||||||
description: String? = null,
|
|
||||||
preferredLoginMode: LoginMode = LoginMode.Unsupported,
|
preferredLoginMode: LoginMode = LoginMode.Unsupported,
|
||||||
supportedLoginTypes: List<String> = emptyList(),
|
supportedLoginTypes: List<String> = emptyList(),
|
||||||
homeserverSourceUrl: String = A_HOMESERVER_CONFIG.homeServerUri.toString()
|
homeserverSourceUrl: String = A_HOMESERVER_CONFIG.homeServerUri.toString()
|
||||||
) = StartAuthenticationResult(
|
) = StartAuthenticationResult(
|
||||||
isHomeserverOutdated,
|
isHomeserverOutdated,
|
||||||
SelectedHomeserverState(
|
SelectedHomeserverState(
|
||||||
description = description,
|
|
||||||
userFacingUrl = homeserverSourceUrl,
|
userFacingUrl = homeserverSourceUrl,
|
||||||
upstreamUrl = A_DECLARED_HOMESERVER_URL,
|
upstreamUrl = A_DECLARED_HOMESERVER_URL,
|
||||||
preferredLoginMode = preferredLoginMode,
|
preferredLoginMode = preferredLoginMode,
|
||||||
|
@ -20,8 +20,10 @@ import io.mockk.coEvery
|
|||||||
import io.mockk.coVerify
|
import io.mockk.coVerify
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
import org.matrix.android.sdk.api.auth.registration.RegisterThreePid
|
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.RegistrationResult
|
||||||
import org.matrix.android.sdk.api.auth.registration.RegistrationWizard
|
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
|
import org.matrix.android.sdk.api.session.Session
|
||||||
|
|
||||||
class FakeRegistrationWizard : RegistrationWizard by mockk(relaxed = false) {
|
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) {
|
fun verifyCheckedEmailedVerification(times: Int) {
|
||||||
coVerify(exactly = times) { checkIfEmailHasBeenValidated(any()) }
|
coVerify(exactly = times) { checkIfEmailHasBeenValidated(any()) }
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user