mirror of
https://github.com/Novaloop-AG/element-x-android.git
synced 2026-11-02 12:07:28 +00:00
Default the account provider to the most recently used one
This commit is contained in:
@@ -69,6 +69,7 @@ dependencies {
|
||||
implementation(projects.libraries.sessionStorage.api)
|
||||
implementation(projects.libraries.qrcode)
|
||||
implementation(projects.libraries.oauth.api)
|
||||
implementation(projects.libraries.preferences.api)
|
||||
implementation(projects.libraries.uiUtils)
|
||||
implementation(projects.libraries.wellknown.api)
|
||||
implementation(libs.androidx.browser)
|
||||
@@ -80,6 +81,7 @@ dependencies {
|
||||
testImplementation(projects.features.login.test)
|
||||
testImplementation(projects.features.enterprise.test)
|
||||
testImplementation(projects.features.preferences.test)
|
||||
testImplementation(projects.libraries.preferences.test)
|
||||
testImplementation(projects.libraries.matrix.test)
|
||||
testImplementation(projects.libraries.oauth.test)
|
||||
testImplementation(projects.libraries.permissions.test)
|
||||
|
||||
+35
-4
@@ -13,27 +13,58 @@ import dev.zacsweers.metro.Inject
|
||||
import dev.zacsweers.metro.SingleIn
|
||||
import io.element.android.appconfig.AuthenticationConfig
|
||||
import io.element.android.features.enterprise.api.EnterpriseService
|
||||
import io.element.android.features.enterprise.api.canConnectToAnyHomeserver
|
||||
import io.element.android.libraries.di.annotations.AppCoroutineScope
|
||||
import io.element.android.libraries.preferences.api.store.AppPreferencesStore
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@SingleIn(AppScope::class)
|
||||
@Inject
|
||||
class AccountProviderDataSource(
|
||||
enterpriseService: EnterpriseService,
|
||||
private val enterpriseService: EnterpriseService,
|
||||
private val appPreferencesStore: AppPreferencesStore,
|
||||
@AppCoroutineScope private val coroutineScope: CoroutineScope,
|
||||
) {
|
||||
private val defaultAccountProvider = createAccountProvider(
|
||||
// The provider used when the user has not selected one: an enterprise/MDM-configured provider,
|
||||
// else matrix.org. The most recently used provider (from history) can override it, see init.
|
||||
private val configuredAccountProvider = createAccountProvider(
|
||||
url = enterpriseService.defaultHomeserverList()
|
||||
.firstOrNull { it != EnterpriseService.ANY_ACCOUNT_PROVIDER }
|
||||
?: AuthenticationConfig.MATRIX_ORG_URL
|
||||
)
|
||||
|
||||
private val accountProvider: MutableStateFlow<AccountProvider> = MutableStateFlow(defaultAccountProvider)
|
||||
private val accountProvider: MutableStateFlow<AccountProvider> = MutableStateFlow(configuredAccountProvider)
|
||||
|
||||
val flow: StateFlow<AccountProvider> = accountProvider.asStateFlow()
|
||||
|
||||
init {
|
||||
// Seed the default from the last used provider, unless the user has already selected one.
|
||||
coroutineScope.launch {
|
||||
val default = defaultAccountProvider()
|
||||
accountProvider.update { current -> if (current == configuredAccountProvider) default else current }
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun reset() {
|
||||
accountProvider.emit(defaultAccountProvider)
|
||||
accountProvider.emit(defaultAccountProvider())
|
||||
}
|
||||
|
||||
/**
|
||||
* The provider to default to: the most recently used one from history when the user is free to
|
||||
* connect to any provider, otherwise the enterprise/MDM-configured provider.
|
||||
*/
|
||||
private suspend fun defaultAccountProvider(): AccountProvider {
|
||||
if (!enterpriseService.canConnectToAnyHomeserver()) {
|
||||
return configuredAccountProvider
|
||||
}
|
||||
val lastUsedProvider = appPreferencesStore.getHomeserverHistoryFlow().first().firstOrNull()
|
||||
return lastUsedProvider?.let { createAccountProvider(it) } ?: configuredAccountProvider
|
||||
}
|
||||
|
||||
suspend fun setUrl(url: String) {
|
||||
|
||||
+2
-3
@@ -12,9 +12,8 @@ import androidx.arch.core.executor.testing.InstantTaskExecutorRule
|
||||
import com.bumble.appyx.core.modality.BuildContext
|
||||
import com.bumble.appyx.testing.junit4.util.MainDispatcherRule
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.features.enterprise.test.FakeEnterpriseService
|
||||
import io.element.android.features.login.api.LoginEntryPoint
|
||||
import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource
|
||||
import io.element.android.features.login.impl.accountprovider.anAccountProviderDataSource
|
||||
import io.element.android.features.login.impl.classic.FakeElementClassicConnection
|
||||
import io.element.android.features.preferences.test.FakePreferencesEntryPoint
|
||||
import io.element.android.libraries.oauth.test.customtab.FakeOAuthActionFlow
|
||||
@@ -38,7 +37,7 @@ class DefaultLoginEntryPointTest {
|
||||
LoginFlowNode(
|
||||
buildContext = buildContext,
|
||||
plugins = plugins,
|
||||
accountProviderDataSource = AccountProviderDataSource(FakeEnterpriseService()),
|
||||
accountProviderDataSource = anAccountProviderDataSource(),
|
||||
oAuthActionFlow = FakeOAuthActionFlow(),
|
||||
appCoroutineScope = backgroundScope,
|
||||
elementClassicConnection = FakeElementClassicConnection(),
|
||||
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.login.impl.accountprovider
|
||||
|
||||
import io.element.android.features.enterprise.api.EnterpriseService
|
||||
import io.element.android.features.enterprise.test.FakeEnterpriseService
|
||||
import io.element.android.libraries.preferences.api.store.AppPreferencesStore
|
||||
import io.element.android.libraries.preferences.test.InMemoryAppPreferencesStore
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
||||
fun anAccountProviderDataSource(
|
||||
enterpriseService: EnterpriseService = FakeEnterpriseService(),
|
||||
appPreferencesStore: AppPreferencesStore = InMemoryAppPreferencesStore(),
|
||||
coroutineScope: CoroutineScope = CoroutineScope(Dispatchers.Unconfined),
|
||||
) = AccountProviderDataSource(
|
||||
enterpriseService = enterpriseService,
|
||||
appPreferencesStore = appPreferencesStore,
|
||||
coroutineScope = coroutineScope,
|
||||
)
|
||||
+53
-9
@@ -13,6 +13,7 @@ import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.appconfig.AuthenticationConfig
|
||||
import io.element.android.features.enterprise.api.EnterpriseService
|
||||
import io.element.android.features.enterprise.test.FakeEnterpriseService
|
||||
import io.element.android.libraries.preferences.test.InMemoryAppPreferencesStore
|
||||
import io.element.android.tests.testutils.WarmUpRule
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Rule
|
||||
@@ -24,7 +25,7 @@ class AccountProviderDataSourceTest {
|
||||
|
||||
@Test
|
||||
fun `present - initial state`() = runTest {
|
||||
val sut = AccountProviderDataSource(FakeEnterpriseService())
|
||||
val sut = anAccountProviderDataSource()
|
||||
sut.flow.test {
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState).isEqualTo(
|
||||
@@ -41,10 +42,10 @@ class AccountProviderDataSourceTest {
|
||||
|
||||
@Test
|
||||
fun `present - initial state - matrix org`() = runTest {
|
||||
val sut = AccountProviderDataSource(
|
||||
FakeEnterpriseService(
|
||||
val sut = anAccountProviderDataSource(
|
||||
enterpriseService = FakeEnterpriseService(
|
||||
defaultHomeserverListResult = { listOf(AuthenticationConfig.MATRIX_ORG_URL) }
|
||||
)
|
||||
),
|
||||
)
|
||||
sut.flow.test {
|
||||
val initialState = awaitItem()
|
||||
@@ -62,10 +63,10 @@ class AccountProviderDataSourceTest {
|
||||
|
||||
@Test
|
||||
fun `present - ensure that default homeserver is not star char`() = runTest {
|
||||
val sut = AccountProviderDataSource(
|
||||
FakeEnterpriseService(
|
||||
val sut = anAccountProviderDataSource(
|
||||
enterpriseService = FakeEnterpriseService(
|
||||
defaultHomeserverListResult = { listOf(EnterpriseService.ANY_ACCOUNT_PROVIDER, AuthenticationConfig.MATRIX_ORG_URL) }
|
||||
)
|
||||
),
|
||||
)
|
||||
sut.flow.test {
|
||||
val initialState = awaitItem()
|
||||
@@ -83,7 +84,7 @@ class AccountProviderDataSourceTest {
|
||||
|
||||
@Test
|
||||
fun `present - user change and reset`() = runTest {
|
||||
val sut = AccountProviderDataSource(FakeEnterpriseService())
|
||||
val sut = anAccountProviderDataSource()
|
||||
sut.flow.test {
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.url).isEqualTo(AuthenticationConfig.MATRIX_ORG_URL)
|
||||
@@ -106,7 +107,7 @@ class AccountProviderDataSourceTest {
|
||||
|
||||
@Test
|
||||
fun `present - set url and reset`() = runTest {
|
||||
val sut = AccountProviderDataSource(FakeEnterpriseService())
|
||||
val sut = anAccountProviderDataSource()
|
||||
sut.flow.test {
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.url).isEqualTo(AuthenticationConfig.MATRIX_ORG_URL)
|
||||
@@ -126,4 +127,47 @@ class AccountProviderDataSourceTest {
|
||||
assertThat(resetState.url).isEqualTo(AuthenticationConfig.MATRIX_ORG_URL)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - defaults to the most recently used provider from history`() = runTest {
|
||||
val sut = anAccountProviderDataSource(
|
||||
appPreferencesStore = InMemoryAppPreferencesStore(
|
||||
homeserverHistory = listOf("https://example.com", "https://matrix.org"),
|
||||
),
|
||||
)
|
||||
sut.flow.test {
|
||||
assertThat(awaitItem().url).isEqualTo("https://example.com")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - history is ignored when the account provider is enforced`() = runTest {
|
||||
val sut = anAccountProviderDataSource(
|
||||
enterpriseService = FakeEnterpriseService(
|
||||
defaultHomeserverListResult = { listOf("https://enforced.org") }
|
||||
),
|
||||
appPreferencesStore = InMemoryAppPreferencesStore(
|
||||
homeserverHistory = listOf("https://example.com"),
|
||||
),
|
||||
)
|
||||
sut.flow.test {
|
||||
assertThat(awaitItem().url).isEqualTo("https://enforced.org")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - reset returns to the most recently used provider from history`() = runTest {
|
||||
val sut = anAccountProviderDataSource(
|
||||
appPreferencesStore = InMemoryAppPreferencesStore(
|
||||
homeserverHistory = listOf("https://example.com"),
|
||||
),
|
||||
)
|
||||
sut.flow.test {
|
||||
assertThat(awaitItem().url).isEqualTo("https://example.com")
|
||||
sut.setUrl("https://other.com")
|
||||
assertThat(awaitItem().url).isEqualTo("https://other.com")
|
||||
sut.reset()
|
||||
assertThat(awaitItem().url).isEqualTo("https://example.com")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -14,6 +14,7 @@ import io.element.android.features.enterprise.test.FakeEnterpriseService
|
||||
import io.element.android.features.login.impl.accesscontrol.DefaultAccountProviderAccessControl
|
||||
import io.element.android.features.login.impl.accountprovider.AccountProvider
|
||||
import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource
|
||||
import io.element.android.features.login.impl.accountprovider.anAccountProviderDataSource
|
||||
import io.element.android.features.login.impl.error.ChangeServerError
|
||||
import io.element.android.features.login.impl.localnetwork.LocalNetworkPermissionGate
|
||||
import io.element.android.features.wellknown.test.FakeWellknownRetriever
|
||||
@@ -259,7 +260,7 @@ class ChangeServerPresenterTest {
|
||||
|
||||
private fun createPresenter(
|
||||
authenticationService: FakeMatrixAuthenticationService = FakeMatrixAuthenticationService(),
|
||||
accountProviderDataSource: AccountProviderDataSource = AccountProviderDataSource(FakeEnterpriseService()),
|
||||
accountProviderDataSource: AccountProviderDataSource = anAccountProviderDataSource(),
|
||||
enterpriseService: EnterpriseService = FakeEnterpriseService(),
|
||||
wellknownRetriever: WellknownRetriever = FakeWellknownRetriever(),
|
||||
localNetworkPermissionAdvisor: FakeLocalNetworkPermissionAdvisor = FakeLocalNetworkPermissionAdvisor(),
|
||||
|
||||
+2
-2
@@ -8,8 +8,8 @@
|
||||
package io.element.android.features.login.impl.screens.classic.loginwithclassic
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.features.enterprise.test.FakeEnterpriseService
|
||||
import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource
|
||||
import io.element.android.features.login.impl.accountprovider.anAccountProviderDataSource
|
||||
import io.element.android.features.login.impl.classic.ElementClassicConnection
|
||||
import io.element.android.features.login.impl.classic.ElementClassicConnectionState
|
||||
import io.element.android.features.login.impl.classic.FakeElementClassicConnection
|
||||
@@ -249,7 +249,7 @@ private fun createPresenter(
|
||||
navigator: LoginWithClassicNavigator = FakeLoginWithClassicNavigator(),
|
||||
loginModePresenter: LoginModePresenter = createLoginModePresenter(),
|
||||
elementClassicConnection: ElementClassicConnection = FakeElementClassicConnection(),
|
||||
accountProviderDataSource: AccountProviderDataSource = AccountProviderDataSource(FakeEnterpriseService()),
|
||||
accountProviderDataSource: AccountProviderDataSource = anAccountProviderDataSource(),
|
||||
isEnterpriseBuild: Boolean = false,
|
||||
) = LoginWithClassicPresenter(
|
||||
userId = userId,
|
||||
|
||||
+2
-2
@@ -10,8 +10,8 @@ package io.element.android.features.login.impl.screens.confirmaccountprovider
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.appconfig.AuthenticationConfig
|
||||
import io.element.android.features.enterprise.test.FakeEnterpriseService
|
||||
import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource
|
||||
import io.element.android.features.login.impl.accountprovider.anAccountProviderDataSource
|
||||
import io.element.android.features.login.impl.login.LoginMode
|
||||
import io.element.android.features.login.impl.screens.createaccount.AccountCreationNotSupported
|
||||
import io.element.android.features.login.impl.screens.onboarding.createLoginModePresenter
|
||||
@@ -378,7 +378,7 @@ class ConfirmAccountProviderPresenterTest {
|
||||
|
||||
private fun createConfirmAccountProviderPresenter(
|
||||
params: ConfirmAccountProviderPresenter.Params = ConfirmAccountProviderPresenter.Params(isAccountCreation = false),
|
||||
accountProviderDataSource: AccountProviderDataSource = AccountProviderDataSource(FakeEnterpriseService()),
|
||||
accountProviderDataSource: AccountProviderDataSource = anAccountProviderDataSource(),
|
||||
matrixAuthenticationService: MatrixAuthenticationService = FakeMatrixAuthenticationService(),
|
||||
defaultOAuthActionFlow: OAuthActionFlow = FakeOAuthActionFlow(),
|
||||
webClientUrlForAuthenticationRetriever: WebClientUrlForAuthenticationRetriever = FakeWebClientUrlForAuthenticationRetriever(),
|
||||
|
||||
+2
-3
@@ -10,8 +10,7 @@ package io.element.android.features.login.impl.screens.createaccount
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.appconfig.AuthenticationConfig
|
||||
import io.element.android.features.enterprise.test.FakeEnterpriseService
|
||||
import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource
|
||||
import io.element.android.features.login.impl.accountprovider.anAccountProviderDataSource
|
||||
import io.element.android.libraries.androidutils.json.DefaultJsonProvider
|
||||
import io.element.android.libraries.matrix.api.auth.external.ExternalSession
|
||||
import kotlinx.serialization.SerializationException
|
||||
@@ -70,7 +69,7 @@ class DefaultMessageParserTest {
|
||||
|
||||
private fun createDefaultMessageParser(): DefaultMessageParser {
|
||||
return DefaultMessageParser(
|
||||
accountProviderDataSource = AccountProviderDataSource(FakeEnterpriseService()),
|
||||
accountProviderDataSource = anAccountProviderDataSource(),
|
||||
json = DefaultJsonProvider(),
|
||||
)
|
||||
}
|
||||
|
||||
+2
-2
@@ -10,8 +10,8 @@ package io.element.android.features.login.impl.screens.loginpassword
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.appconfig.AuthenticationConfig
|
||||
import io.element.android.features.enterprise.test.FakeEnterpriseService
|
||||
import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource
|
||||
import io.element.android.features.login.impl.accountprovider.anAccountProviderDataSource
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.matrix.test.AN_EXCEPTION
|
||||
@@ -157,7 +157,7 @@ class LoginPasswordPresenterTest {
|
||||
private fun createLoginPasswordPresenter(
|
||||
initialLogin: String = "",
|
||||
authenticationService: FakeMatrixAuthenticationService = FakeMatrixAuthenticationService(),
|
||||
accountProviderDataSource: AccountProviderDataSource = AccountProviderDataSource(FakeEnterpriseService()),
|
||||
accountProviderDataSource: AccountProviderDataSource = anAccountProviderDataSource(),
|
||||
): LoginPasswordPresenter = LoginPasswordPresenter(
|
||||
initialLogin = initialLogin,
|
||||
authenticationService = authenticationService,
|
||||
|
||||
+3
-2
@@ -15,6 +15,7 @@ import io.element.android.features.enterprise.api.EnterpriseService
|
||||
import io.element.android.features.enterprise.test.FakeEnterpriseService
|
||||
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.accountprovider.anAccountProviderDataSource
|
||||
import io.element.android.features.login.impl.localnetwork.LocalNetworkPermissionGate
|
||||
import io.element.android.features.login.impl.login.LoginModePresenter
|
||||
import io.element.android.features.login.impl.web.FakeWebClientUrlForAuthenticationRetriever
|
||||
@@ -247,7 +248,7 @@ class OnBoardingPresenterTest {
|
||||
Result.failure(AN_EXCEPTION)
|
||||
},
|
||||
)
|
||||
val accountProviderDataSource = AccountProviderDataSource(FakeEnterpriseService())
|
||||
val accountProviderDataSource = anAccountProviderDataSource()
|
||||
val presenter = createPresenter(
|
||||
params = OnBoardingNode.Params(
|
||||
accountProvider = A_HOMESERVER_URL,
|
||||
@@ -297,7 +298,7 @@ private fun createPresenter(
|
||||
loginModePresenter: LoginModePresenter = createLoginModePresenter(),
|
||||
onBoardingLogoResIdProvider: OnBoardingLogoResIdProvider = OnBoardingLogoResIdProvider { null },
|
||||
sessionStore: SessionStore = InMemorySessionStore(),
|
||||
accountProviderDataSource: AccountProviderDataSource = AccountProviderDataSource(FakeEnterpriseService()),
|
||||
accountProviderDataSource: AccountProviderDataSource = anAccountProviderDataSource(),
|
||||
) = OnBoardingPresenter(
|
||||
params = params,
|
||||
buildMeta = buildMeta,
|
||||
|
||||
Reference in New Issue
Block a user