Ensure that the current account provider is set when using a login deeplink. Fixes #5004

This commit is contained in:
Benoit Marty 2025-11-06 17:00:32 +01:00 committed by Benoit Marty
parent 3e567d9b5a
commit 8012dfb01c
4 changed files with 66 additions and 21 deletions

View file

@ -21,28 +21,34 @@ import kotlinx.coroutines.flow.asStateFlow
class AccountProviderDataSource( class AccountProviderDataSource(
enterpriseService: EnterpriseService, enterpriseService: EnterpriseService,
) { ) {
private val defaultAccountProvider = private val defaultAccountProvider = createAccountProvider(
(enterpriseService.defaultHomeserverList().firstOrNull { it != EnterpriseService.ANY_ACCOUNT_PROVIDER } ?: AuthenticationConfig.MATRIX_ORG_URL) url = enterpriseService.defaultHomeserverList()
.let { url -> .firstOrNull { it != EnterpriseService.ANY_ACCOUNT_PROVIDER }
AccountProvider( ?: AuthenticationConfig.MATRIX_ORG_URL
url = url,
subtitle = null,
isPublic = url == AuthenticationConfig.MATRIX_ORG_URL,
isMatrixOrg = url == AuthenticationConfig.MATRIX_ORG_URL,
)
}
private val accountProvider: MutableStateFlow<AccountProvider> = MutableStateFlow(
defaultAccountProvider
) )
private val accountProvider: MutableStateFlow<AccountProvider> = MutableStateFlow(defaultAccountProvider)
val flow: StateFlow<AccountProvider> = accountProvider.asStateFlow() val flow: StateFlow<AccountProvider> = accountProvider.asStateFlow()
fun reset() { fun reset() {
accountProvider.tryEmit(defaultAccountProvider) accountProvider.tryEmit(defaultAccountProvider)
} }
fun setUrl(url: String) {
userSelection(createAccountProvider(url))
}
fun userSelection(data: AccountProvider) { fun userSelection(data: AccountProvider) {
accountProvider.tryEmit(data) accountProvider.tryEmit(data)
} }
private fun createAccountProvider(url: String): AccountProvider {
return AccountProvider(
url = url,
subtitle = null,
isPublic = url == AuthenticationConfig.MATRIX_ORG_URL,
isMatrixOrg = url == AuthenticationConfig.MATRIX_ORG_URL,
)
}
} }

View file

@ -23,6 +23,7 @@ import io.element.android.appconfig.OnBoardingConfig
import io.element.android.features.enterprise.api.EnterpriseService import io.element.android.features.enterprise.api.EnterpriseService
import io.element.android.features.enterprise.api.canConnectToAnyHomeserver import io.element.android.features.enterprise.api.canConnectToAnyHomeserver
import io.element.android.features.login.impl.accesscontrol.DefaultAccountProviderAccessControl import io.element.android.features.login.impl.accesscontrol.DefaultAccountProviderAccessControl
import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource
import io.element.android.features.login.impl.login.LoginHelper import io.element.android.features.login.impl.login.LoginHelper
import io.element.android.features.rageshake.api.RageshakeFeatureAvailability import io.element.android.features.rageshake.api.RageshakeFeatureAvailability
import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.Presenter
@ -40,6 +41,7 @@ class OnBoardingPresenter(
private val loginHelper: LoginHelper, private val loginHelper: LoginHelper,
private val onBoardingLogoResIdProvider: OnBoardingLogoResIdProvider, private val onBoardingLogoResIdProvider: OnBoardingLogoResIdProvider,
private val sessionStore: SessionStore, private val sessionStore: SessionStore,
private val accountProviderDataSource: AccountProviderDataSource,
) : Presenter<OnBoardingState> { ) : Presenter<OnBoardingState> {
@AssistedFactory @AssistedFactory
interface Factory { interface Factory {
@ -97,12 +99,16 @@ class OnBoardingPresenter(
fun handleEvent(event: OnBoardingEvents) { fun handleEvent(event: OnBoardingEvents) {
when (event) { when (event) {
is OnBoardingEvents.OnSignIn -> loginHelper.submit( is OnBoardingEvents.OnSignIn -> {
coroutineScope = localCoroutineScope, // Ensure that the current account provider is set
isAccountCreation = false, accountProviderDataSource.setUrl(event.defaultAccountProvider)
homeserverUrl = event.defaultAccountProvider, loginHelper.submit(
loginHint = params.loginHint?.takeIf { forcedAccountProvider == null }, coroutineScope = localCoroutineScope,
) isAccountCreation = false,
homeserverUrl = event.defaultAccountProvider,
loginHint = params.loginHint?.takeIf { forcedAccountProvider == null },
)
}
OnBoardingEvents.ClearError -> loginHelper.clearError() OnBoardingEvents.ClearError -> loginHelper.clearError()
OnBoardingEvents.OnVersionClick -> { OnBoardingEvents.OnVersionClick -> {
if (canReportBug) { if (canReportBug) {

View file

@ -102,4 +102,27 @@ class AccountProviderDataSourceTest {
assertThat(resetState.url).isEqualTo(AuthenticationConfig.MATRIX_ORG_URL) assertThat(resetState.url).isEqualTo(AuthenticationConfig.MATRIX_ORG_URL)
} }
} }
@Test
fun `present - set url and reset`() = runTest {
val sut = AccountProviderDataSource(FakeEnterpriseService())
sut.flow.test {
val initialState = awaitItem()
assertThat(initialState.url).isEqualTo(AuthenticationConfig.MATRIX_ORG_URL)
sut.setUrl(url = "https://example.com")
val changedState = awaitItem()
assertThat(changedState).isEqualTo(
AccountProvider(
url = "https://example.com",
title = "example.com",
subtitle = null,
isPublic = false,
isMatrixOrg = false,
)
)
sut.reset()
val resetState = awaitItem()
assertThat(resetState.url).isEqualTo(AuthenticationConfig.MATRIX_ORG_URL)
}
}
} }

View file

@ -8,10 +8,12 @@
package io.element.android.features.login.impl.screens.onboarding package io.element.android.features.login.impl.screens.onboarding
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import io.element.android.appconfig.AuthenticationConfig
import io.element.android.appconfig.OnBoardingConfig import io.element.android.appconfig.OnBoardingConfig
import io.element.android.features.enterprise.api.EnterpriseService import io.element.android.features.enterprise.api.EnterpriseService
import io.element.android.features.enterprise.test.FakeEnterpriseService import io.element.android.features.enterprise.test.FakeEnterpriseService
import io.element.android.features.login.impl.accesscontrol.DefaultAccountProviderAccessControl import io.element.android.features.login.impl.accesscontrol.DefaultAccountProviderAccessControl
import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource
import io.element.android.features.login.impl.login.LoginHelper import io.element.android.features.login.impl.login.LoginHelper
import io.element.android.features.login.impl.web.FakeWebClientUrlForAuthenticationRetriever import io.element.android.features.login.impl.web.FakeWebClientUrlForAuthenticationRetriever
import io.element.android.features.login.impl.web.WebClientUrlForAuthenticationRetriever import io.element.android.features.login.impl.web.WebClientUrlForAuthenticationRetriever
@ -24,6 +26,7 @@ import io.element.android.libraries.matrix.test.AN_ACCOUNT_PROVIDER_2
import io.element.android.libraries.matrix.test.AN_ACCOUNT_PROVIDER_3 import io.element.android.libraries.matrix.test.AN_ACCOUNT_PROVIDER_3
import io.element.android.libraries.matrix.test.AN_EXCEPTION import io.element.android.libraries.matrix.test.AN_EXCEPTION
import io.element.android.libraries.matrix.test.A_HOMESERVER_URL import io.element.android.libraries.matrix.test.A_HOMESERVER_URL
import io.element.android.libraries.matrix.test.A_HOMESERVER_URL_2
import io.element.android.libraries.matrix.test.A_LOGIN_HINT import io.element.android.libraries.matrix.test.A_LOGIN_HINT
import io.element.android.libraries.matrix.test.auth.FakeMatrixAuthenticationService import io.element.android.libraries.matrix.test.auth.FakeMatrixAuthenticationService
import io.element.android.libraries.matrix.test.core.aBuildMeta import io.element.android.libraries.matrix.test.core.aBuildMeta
@ -36,6 +39,7 @@ import io.element.android.libraries.wellknown.api.WellknownRetriever
import io.element.android.tests.testutils.WarmUpRule import io.element.android.tests.testutils.WarmUpRule
import io.element.android.tests.testutils.test import io.element.android.tests.testutils.test
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import org.junit.Rule import org.junit.Rule
@ -219,6 +223,7 @@ class OnBoardingPresenterTest {
Result.failure(AN_EXCEPTION) Result.failure(AN_EXCEPTION)
}, },
) )
val accountProviderDataSource = AccountProviderDataSource(FakeEnterpriseService())
val presenter = createPresenter( val presenter = createPresenter(
params = OnBoardingNode.Params( params = OnBoardingNode.Params(
accountProvider = A_HOMESERVER_URL, accountProvider = A_HOMESERVER_URL,
@ -230,14 +235,17 @@ class OnBoardingPresenterTest {
loginHelper = createLoginHelper( loginHelper = createLoginHelper(
authenticationService = authenticationService, authenticationService = authenticationService,
), ),
accountProviderDataSource = accountProviderDataSource,
) )
presenter.test { presenter.test {
skipItems(3) skipItems(3)
awaitItem().also { awaitItem().also {
assertThat(it.defaultAccountProvider).isEqualTo(A_HOMESERVER_URL) assertThat(it.defaultAccountProvider).isEqualTo(A_HOMESERVER_URL)
it.eventSink(OnBoardingEvents.OnSignIn(A_HOMESERVER_URL)) assertThat(accountProviderDataSource.flow.first().url).isEqualTo(AuthenticationConfig.MATRIX_ORG_URL)
it.eventSink(OnBoardingEvents.OnSignIn(A_HOMESERVER_URL_2))
skipItems(1) // Loading skipItems(1) // Loading
// Account data source has been updated
assertThat(accountProviderDataSource.flow.first().url).isEqualTo(A_HOMESERVER_URL_2)
// Check an error was returned // Check an error was returned
val submittedState = awaitItem() val submittedState = awaitItem()
assertThat(submittedState.loginMode).isInstanceOf(AsyncData.Failure::class.java) assertThat(submittedState.loginMode).isInstanceOf(AsyncData.Failure::class.java)
@ -260,6 +268,7 @@ private fun createPresenter(
loginHelper: LoginHelper = createLoginHelper(), loginHelper: LoginHelper = createLoginHelper(),
onBoardingLogoResIdProvider: OnBoardingLogoResIdProvider = OnBoardingLogoResIdProvider { null }, onBoardingLogoResIdProvider: OnBoardingLogoResIdProvider = OnBoardingLogoResIdProvider { null },
sessionStore: SessionStore = InMemorySessionStore(), sessionStore: SessionStore = InMemorySessionStore(),
accountProviderDataSource: AccountProviderDataSource = AccountProviderDataSource(FakeEnterpriseService()),
) = OnBoardingPresenter( ) = OnBoardingPresenter(
params = params, params = params,
buildMeta = buildMeta, buildMeta = buildMeta,
@ -272,6 +281,7 @@ private fun createPresenter(
loginHelper = loginHelper, loginHelper = loginHelper,
onBoardingLogoResIdProvider = onBoardingLogoResIdProvider, onBoardingLogoResIdProvider = onBoardingLogoResIdProvider,
sessionStore = sessionStore, sessionStore = sessionStore,
accountProviderDataSource = accountProviderDataSource,
) )
fun createLoginHelper( fun createLoginHelper(