mirror of
https://github.com/Novaloop-AG/element-x-android.git
synced 2026-11-03 06:55:41 +00:00
Merge develop into the PreviewParam rename
The 174 commits develop gained since this branch was cut touch preview data in three ways, and each is resolved so the flipped konsist rule still holds across the merged tree. Two screens the branch had renamed were deleted upstream by "Remove the account provider search screen and homeserver resolver": the changeaccountprovider and searchaccountprovider packages. Their deletion wins, so ChangeAccountProviderStatePreviewParam, SearchAccountProviderStatePreviewParam and the two views that used them go with them rather than coming back under the new name. Two preview classes develop added still carried the old suffix and are renamed here, along with the `@PreviewParameter` reference in each view: GlobalSearchStateProvider -> GlobalSearchStatePreviewParam and SendFailureDialogStateProvider -> SendFailureDialogStatePreviewParam. Everything else merged textually. No production class extending PreviewParameterProvider is left with a `Provider` name, and no reference to any of the 194 renamed names survives outside the CHANGES.md entry that records a past pull request title.
This commit is contained in:
@@ -51,7 +51,7 @@ import io.element.android.compound.colors.SemanticColorsLightDark
|
||||
import io.element.android.features.createroom.api.CreateRoomEntryPoint
|
||||
import io.element.android.features.enterprise.api.EnterpriseService
|
||||
import io.element.android.features.enterprise.api.SessionEnterpriseService
|
||||
import io.element.android.features.enterprise.api.remoteconfig.RemoteEnterpriseConfigProvider
|
||||
import io.element.android.features.enterprise.api.remoteconfig.CustomMapTilerConfigProvider
|
||||
import io.element.android.features.ftue.api.FtueEntryPoint
|
||||
import io.element.android.features.ftue.api.state.FtueService
|
||||
import io.element.android.features.ftue.api.state.FtueState
|
||||
@@ -157,7 +157,7 @@ class LoggedInFlowNode(
|
||||
private val analyticsRoomListStateWatcher: AnalyticsRoomListStateWatcher,
|
||||
private val createRoomEntryPoint: CreateRoomEntryPoint,
|
||||
private val activeLiveLocationShareManager: ActiveLiveLocationShareManager,
|
||||
private val remoteEnterpriseConfigProvider: RemoteEnterpriseConfigProvider,
|
||||
private val customMapTilerConfigProvider: CustomMapTilerConfigProvider,
|
||||
) : BaseFlowNode<LoggedInFlowNode.NavTarget>(
|
||||
backstack = BackStack(
|
||||
initialElement = NavTarget.Placeholder,
|
||||
@@ -335,11 +335,11 @@ class LoggedInFlowNode(
|
||||
}
|
||||
NavTarget.Home -> {
|
||||
val callback = object : HomeEntryPoint.Callback {
|
||||
override fun navigateToRoom(roomId: RoomId, joinedRoom: JoinedRoom?) {
|
||||
override fun navigateToRoom(roomId: RoomId, eventId: EventId?, joinedRoom: JoinedRoom?) {
|
||||
lifecycleScope.launch {
|
||||
attachRoom(
|
||||
roomIdOrAlias = roomId.toRoomIdOrAlias(),
|
||||
initialElement = RoomNavigationTarget.Root(joinedRoom = joinedRoom),
|
||||
initialElement = RoomNavigationTarget.Root(joinedRoom = joinedRoom, eventId = eventId),
|
||||
clearBackstack = false,
|
||||
)
|
||||
}
|
||||
@@ -685,7 +685,7 @@ class LoggedInFlowNode(
|
||||
|
||||
val currentMapTilerConfig = LocalMapTilerConfig.current
|
||||
val updatedMapTilerConfig by produceState(currentMapTilerConfig) {
|
||||
value = remoteEnterpriseConfigProvider.get(matrixClient.sessionId).dataOrNull()?.mapTilerConfig ?: currentMapTilerConfig
|
||||
value = customMapTilerConfigProvider.get().getOrNull() ?: currentMapTilerConfig
|
||||
}
|
||||
|
||||
ElementThemeApp(
|
||||
|
||||
@@ -32,7 +32,6 @@ import dev.zacsweers.metro.Assisted
|
||||
import dev.zacsweers.metro.AssistedInject
|
||||
import im.vector.app.features.analytics.plan.JoinedRoom
|
||||
import io.element.android.annotations.ContributesNode
|
||||
import io.element.android.appnav.di.MatrixSessionCache
|
||||
import io.element.android.appnav.intent.IntentResolver
|
||||
import io.element.android.appnav.intent.ResolvedIntent
|
||||
import io.element.android.appnav.room.RoomFlowNode
|
||||
@@ -40,6 +39,7 @@ import io.element.android.appnav.room.RoomNavigationTarget
|
||||
import io.element.android.appnav.root.RootNavStateFlowFactory
|
||||
import io.element.android.appnav.root.RootPresenter
|
||||
import io.element.android.appnav.root.RootView
|
||||
import io.element.android.appnav.session.MatrixSessionCache
|
||||
import io.element.android.features.announcement.api.AnnouncementService
|
||||
import io.element.android.features.login.api.LoginParams
|
||||
import io.element.android.features.login.api.accesscontrol.AccountProviderAccessControl
|
||||
@@ -109,6 +109,13 @@ class RootFlowNode(
|
||||
buildContext = buildContext,
|
||||
plugins = plugins
|
||||
) {
|
||||
/**
|
||||
* Login params coming from a launch or new [Intent], waiting to be consumed by the not logged in flow.
|
||||
* Kept here so that the root nav target can be computed from both the logged in state and the pending
|
||||
* login params, whatever the order in which the intent and the first nav state emission are processed.
|
||||
*/
|
||||
private var pendingLoginParams: LoginParams? = null
|
||||
|
||||
override fun onBuilt() {
|
||||
analyticsColdStartWatcher.start()
|
||||
appCoroutineScope.launch {
|
||||
@@ -152,7 +159,7 @@ class RootFlowNode(
|
||||
}
|
||||
}
|
||||
LoggedInState.NotLoggedIn -> {
|
||||
switchToNotLoggedInFlow(null)
|
||||
switchToNotLoggedInFlow(pendingLoginParams)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -194,15 +201,18 @@ class RootFlowNode(
|
||||
}
|
||||
|
||||
private fun switchToLoggedInFlow(sessionId: SessionId, navId: Int) {
|
||||
pendingLoginParams = null
|
||||
backstack.safeRoot(NavTarget.LoggedInFlow(sessionId, navId))
|
||||
}
|
||||
|
||||
private fun switchToNotLoggedInFlow(params: LoginParams?) {
|
||||
Timber.d("switchToNotLoggedInFlow, hasLoginParams=${params != null}")
|
||||
matrixSessionCache.removeAll()
|
||||
backstack.safeRoot(NavTarget.NotLoggedInFlow(params))
|
||||
}
|
||||
|
||||
private fun switchToSignedOutFlow(sessionId: SessionId) {
|
||||
pendingLoginParams = null
|
||||
backstack.safeRoot(NavTarget.SignedOutFlow(sessionId))
|
||||
}
|
||||
|
||||
@@ -319,6 +329,7 @@ class RootFlowNode(
|
||||
}
|
||||
|
||||
override fun onDone() {
|
||||
pendingLoginParams = null
|
||||
backstack.pop()
|
||||
}
|
||||
}
|
||||
@@ -418,6 +429,7 @@ class RootFlowNode(
|
||||
Timber.w("Login link ignored, multi account is disabled")
|
||||
}
|
||||
} else {
|
||||
pendingLoginParams = params
|
||||
switchToNotLoggedInFlow(params)
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -76,6 +76,7 @@ class IntentResolver(
|
||||
}
|
||||
|
||||
private fun Intent.canBeIgnored(): Boolean {
|
||||
if (flags and Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY != 0) return true
|
||||
return action == Intent.ACTION_MAIN &&
|
||||
categories?.contains(Intent.CATEGORY_LAUNCHER) == true
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ package io.element.android.appnav.root
|
||||
import com.bumble.appyx.core.state.MutableSavedStateMap
|
||||
import com.bumble.appyx.core.state.SavedStateMap
|
||||
import dev.zacsweers.metro.Inject
|
||||
import io.element.android.appnav.di.MatrixSessionCache
|
||||
import io.element.android.appnav.session.MatrixSessionCache
|
||||
import io.element.android.features.preferences.api.CacheService
|
||||
import io.element.android.libraries.matrix.ui.media.ImageLoaderHolder
|
||||
import io.element.android.libraries.preferences.api.store.SessionPreferencesStoreFactory
|
||||
|
||||
+2
-2
@@ -6,7 +6,7 @@
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.appnav.di
|
||||
package io.element.android.appnav.session
|
||||
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import com.bumble.appyx.core.state.MutableSavedStateMap
|
||||
@@ -115,7 +115,7 @@ class MatrixSessionCache(
|
||||
|
||||
private fun onNewMatrixClient(matrixClient: MatrixClient) {
|
||||
val syncOrchestrator = syncOrchestratorFactory.create(
|
||||
syncService = matrixClient.syncService,
|
||||
matrixClient = matrixClient,
|
||||
sessionCoroutineScope = matrixClient.sessionCoroutineScope,
|
||||
)
|
||||
sessionIdsToMatrixSession[matrixClient.sessionId] = InMemoryMatrixSession(
|
||||
+12
-5
@@ -6,7 +6,7 @@
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.appnav.di
|
||||
package io.element.android.appnav.session
|
||||
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import dev.zacsweers.metro.Assisted
|
||||
@@ -16,7 +16,7 @@ import io.element.android.features.networkmonitor.api.NetworkMonitor
|
||||
import io.element.android.features.networkmonitor.api.NetworkStatus
|
||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||
import io.element.android.libraries.core.coroutine.childScope
|
||||
import io.element.android.libraries.matrix.api.sync.SyncService
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.sync.SyncState
|
||||
import io.element.android.services.analytics.api.AnalyticsService
|
||||
import io.element.android.services.analytics.api.recordTransaction
|
||||
@@ -37,7 +37,7 @@ import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
@AssistedInject
|
||||
class SyncOrchestrator(
|
||||
@Assisted private val syncService: SyncService,
|
||||
@Assisted private val matrixClient: MatrixClient,
|
||||
@Assisted sessionCoroutineScope: CoroutineScope,
|
||||
private val appForegroundStateService: AppForegroundStateService,
|
||||
private val networkMonitor: NetworkMonitor,
|
||||
@@ -47,13 +47,15 @@ class SyncOrchestrator(
|
||||
@AssistedFactory
|
||||
interface Factory {
|
||||
fun create(
|
||||
syncService: SyncService,
|
||||
matrixClient: MatrixClient,
|
||||
sessionCoroutineScope: CoroutineScope,
|
||||
): SyncOrchestrator
|
||||
}
|
||||
|
||||
private val tag = "SyncOrchestrator"
|
||||
|
||||
private val syncService = matrixClient.syncService
|
||||
|
||||
private val coroutineScope = sessionCoroutineScope.childScope(dispatchers.io, tag)
|
||||
|
||||
private val started = AtomicBoolean(false)
|
||||
@@ -94,7 +96,7 @@ class SyncOrchestrator(
|
||||
appForegroundStateService.isInCall,
|
||||
appForegroundStateService.isSyncingNotificationEvent,
|
||||
appForegroundStateService.hasRingingCall,
|
||||
appForegroundStateService.isSharingLiveLocation
|
||||
appForegroundStateService.isSharingLiveLocation,
|
||||
)
|
||||
val isAppActiveFlow = combine(isAppActiveFlows) { actives -> actives.any { it } }
|
||||
combine(
|
||||
@@ -103,6 +105,11 @@ class SyncOrchestrator(
|
||||
networkMonitor.connectivity,
|
||||
isAppActiveFlow,
|
||||
) { syncState, networkState, isAppActive ->
|
||||
if (matrixClient.isShuttingDown) {
|
||||
Timber.tag(tag).d("Matrix client is shutting down, no need to start the sync service again")
|
||||
return@combine SyncStateAction.NoOp
|
||||
}
|
||||
|
||||
val isNetworkAvailable = networkState == NetworkStatus.Connected
|
||||
|
||||
Timber.tag(tag).d("isAppActive=$isAppActive, isNetworkAvailable=$isNetworkAvailable")
|
||||
@@ -0,0 +1,189 @@
|
||||
/*
|
||||
* Copyright (c) 2026 Element Creations Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
@file:OptIn(ExperimentalCoroutinesApi::class)
|
||||
|
||||
package io.element.android.appnav
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
|
||||
import com.bumble.appyx.core.modality.AncestryInfo
|
||||
import com.bumble.appyx.core.modality.BuildContext
|
||||
import com.bumble.appyx.core.node.node
|
||||
import com.bumble.appyx.navmodel.backstack.activeElement
|
||||
import com.bumble.appyx.testing.junit4.util.MainDispatcherRule
|
||||
import com.bumble.appyx.testing.unit.common.helper.parentNodeTestHelper
|
||||
import com.bumble.appyx.utils.customisations.NodeCustomisationDirectoryImpl
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.appnav.intent.IntentResolver
|
||||
import io.element.android.appnav.root.RootNavStateFlowFactory
|
||||
import io.element.android.appnav.root.RootPresenter
|
||||
import io.element.android.appnav.session.FakeSyncOrchestratorFactory
|
||||
import io.element.android.appnav.session.MatrixSessionCache
|
||||
import io.element.android.features.login.api.LoginEntryPoint
|
||||
import io.element.android.features.login.api.LoginParams
|
||||
import io.element.android.features.login.test.FakeLoginEntryPoint
|
||||
import io.element.android.features.login.test.FakeLoginIntentResolver
|
||||
import io.element.android.features.login.test.accesscontrol.FakeAccountProviderAccessControl
|
||||
import io.element.android.features.preferences.test.FakeCacheService
|
||||
import io.element.android.features.rageshake.test.FakeBugReportEntryPoint
|
||||
import io.element.android.features.rageshake.test.logs.FakeAnnouncementService
|
||||
import io.element.android.features.share.test.FakeShareIntentHandler
|
||||
import io.element.android.features.signedout.test.FakeSignedOutEntryPoint
|
||||
import io.element.android.libraries.accountselect.test.FakeAccountSelectEntryPoint
|
||||
import io.element.android.libraries.architecture.AssistedNodeFactory
|
||||
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
|
||||
import io.element.android.libraries.matrix.test.FakeSdkMetadata
|
||||
import io.element.android.libraries.matrix.test.auth.FakeMatrixAuthenticationService
|
||||
import io.element.android.libraries.matrix.test.permalink.FakePermalinkParser
|
||||
import io.element.android.libraries.matrix.ui.media.test.FakeImageLoaderHolder
|
||||
import io.element.android.libraries.oauth.test.FakeOAuthActionFlow
|
||||
import io.element.android.libraries.oauth.test.FakeOAuthIntentResolver
|
||||
import io.element.android.libraries.preferences.test.FakeSessionPreferencesStoreFactory
|
||||
import io.element.android.libraries.sessionstorage.api.SessionStore
|
||||
import io.element.android.libraries.sessionstorage.test.InMemorySessionStore
|
||||
import io.element.android.services.analytics.test.FakeAnalyticsService
|
||||
import io.element.android.services.analytics.test.watchers.FakeAnalyticsColdStartWatcher
|
||||
import io.element.android.services.apperror.test.FakeAppErrorStateService
|
||||
import io.element.android.tests.testutils.node.FakeNodeFactoriesBindings
|
||||
import io.element.android.tests.testutils.node.FakeParentNode
|
||||
import io.element.android.tests.testutils.presenter.NotUsedPresenter
|
||||
import io.element.android.tests.testutils.robolectric.RobolectricTest
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
import kotlinx.coroutines.test.runCurrent
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
||||
private const val A_LOGIN_LINK = "https://mobile.element.io/element/?account_provider=example.com&login_hint=mxid:@alice:example.com"
|
||||
|
||||
private val A_LOGIN_PARAMS = LoginParams(
|
||||
accountProvider = "example.com",
|
||||
loginHint = "mxid:@alice:example.com",
|
||||
)
|
||||
|
||||
private val A_LOGIN_ENTRY_POINT_PARAMS = LoginEntryPoint.Params(
|
||||
accountProvider = A_LOGIN_PARAMS.accountProvider,
|
||||
loginHint = A_LOGIN_PARAMS.loginHint,
|
||||
)
|
||||
|
||||
class RootFlowNodeTest : RobolectricTest() {
|
||||
@get:Rule
|
||||
val instantTaskExecutorRule = InstantTaskExecutorRule()
|
||||
|
||||
@get:Rule
|
||||
val mainDispatcherRule = MainDispatcherRule()
|
||||
|
||||
@Test
|
||||
fun `given no session, when a login link is handled before the nav state is observed, then the login params are kept`() = runTest {
|
||||
var loginEntryPointParams: LoginEntryPoint.Params? = null
|
||||
val rootFlowNode = createRootFlowNode(
|
||||
loginEntryPoint = FakeLoginEntryPoint { buildContext, params ->
|
||||
loginEntryPointParams = params
|
||||
node(buildContext) {}
|
||||
},
|
||||
)
|
||||
rootFlowNode.parentNodeTestHelper()
|
||||
// The intent is handled first, this is what happens when the deep link cold starts the app.
|
||||
rootFlowNode.handleIntent(aLoginIntent())
|
||||
assertThat(rootFlowNode.backstack.activeElement).isEqualTo(RootFlowNode.NavTarget.NotLoggedInFlow(A_LOGIN_PARAMS))
|
||||
// Then the first nav state emission lands, it must not override the login params.
|
||||
runCurrent()
|
||||
assertThat(rootFlowNode.backstack.activeElement).isEqualTo(RootFlowNode.NavTarget.NotLoggedInFlow(A_LOGIN_PARAMS))
|
||||
assertThat(loginEntryPointParams).isEqualTo(A_LOGIN_ENTRY_POINT_PARAMS)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given no session, when a login link is handled after the nav state is observed, then the login params are applied`() = runTest {
|
||||
var loginEntryPointParams: LoginEntryPoint.Params? = null
|
||||
val rootFlowNode = createRootFlowNode(
|
||||
loginEntryPoint = FakeLoginEntryPoint { buildContext, params ->
|
||||
loginEntryPointParams = params
|
||||
node(buildContext) {}
|
||||
},
|
||||
)
|
||||
rootFlowNode.parentNodeTestHelper()
|
||||
// The nav state is observed first, this is what happens when the app is already running.
|
||||
runCurrent()
|
||||
assertThat(rootFlowNode.backstack.activeElement).isEqualTo(RootFlowNode.NavTarget.NotLoggedInFlow(null))
|
||||
rootFlowNode.handleIntent(aLoginIntent())
|
||||
runCurrent()
|
||||
assertThat(rootFlowNode.backstack.activeElement).isEqualTo(RootFlowNode.NavTarget.NotLoggedInFlow(A_LOGIN_PARAMS))
|
||||
assertThat(loginEntryPointParams).isEqualTo(A_LOGIN_ENTRY_POINT_PARAMS)
|
||||
}
|
||||
|
||||
private fun aLoginIntent() = Intent(Intent.ACTION_VIEW, Uri.parse(A_LOGIN_LINK))
|
||||
|
||||
private fun TestScope.createRootFlowNode(
|
||||
loginEntryPoint: LoginEntryPoint,
|
||||
sessionStore: SessionStore = InMemorySessionStore(),
|
||||
): RootFlowNode {
|
||||
val matrixSessionCache = MatrixSessionCache(
|
||||
authenticationService = FakeMatrixAuthenticationService(),
|
||||
syncOrchestratorFactory = FakeSyncOrchestratorFactory(),
|
||||
analyticsService = FakeAnalyticsService(),
|
||||
)
|
||||
val parentNode = FakeParentNode(
|
||||
graph = FakeNodeFactoriesBindings(
|
||||
mapOf(
|
||||
NotLoggedInFlowNode::class to AssistedNodeFactory { buildContext, plugins ->
|
||||
NotLoggedInFlowNode(
|
||||
buildContext = buildContext,
|
||||
plugins = plugins,
|
||||
loginEntryPoint = loginEntryPoint,
|
||||
imageLoaderHolder = FakeImageLoaderHolder(),
|
||||
analyticsColdStartWatcher = FakeAnalyticsColdStartWatcher(),
|
||||
)
|
||||
}
|
||||
)
|
||||
)
|
||||
)
|
||||
return RootFlowNode(
|
||||
buildContext = BuildContext(
|
||||
ancestryInfo = AncestryInfo.Child(anchor = parentNode),
|
||||
savedStateMap = null,
|
||||
customisations = NodeCustomisationDirectoryImpl(),
|
||||
),
|
||||
plugins = emptyList(),
|
||||
sessionStore = sessionStore,
|
||||
accountProviderAccessControl = FakeAccountProviderAccessControl { true },
|
||||
navStateFlowFactory = RootNavStateFlowFactory(
|
||||
sessionStore = sessionStore,
|
||||
cacheService = FakeCacheService(),
|
||||
matrixSessionCache = matrixSessionCache,
|
||||
imageLoaderHolder = FakeImageLoaderHolder(),
|
||||
sessionPreferencesStoreFactory = FakeSessionPreferencesStoreFactory(),
|
||||
),
|
||||
matrixSessionCache = matrixSessionCache,
|
||||
presenter = RootPresenter(
|
||||
crashDetectionPresenter = NotUsedPresenter(),
|
||||
rageshakeDetectionPresenter = NotUsedPresenter(),
|
||||
appErrorStateService = FakeAppErrorStateService(),
|
||||
analyticsService = FakeAnalyticsService(),
|
||||
sdkMetadata = FakeSdkMetadata("sha"),
|
||||
),
|
||||
bugReportEntryPoint = FakeBugReportEntryPoint(),
|
||||
signedOutEntryPoint = FakeSignedOutEntryPoint(),
|
||||
accountSelectEntryPoint = FakeAccountSelectEntryPoint(),
|
||||
intentResolver = IntentResolver(
|
||||
deeplinkParser = { null },
|
||||
loginIntentResolver = FakeLoginIntentResolver { A_LOGIN_PARAMS },
|
||||
oAuthIntentResolver = FakeOAuthIntentResolver { null },
|
||||
permalinkParser = FakePermalinkParser(),
|
||||
shareIntentHandler = FakeShareIntentHandler(),
|
||||
),
|
||||
oAuthActionFlow = FakeOAuthActionFlow(),
|
||||
featureFlagService = FakeFeatureFlagService(),
|
||||
announcementService = FakeAnnouncementService(),
|
||||
analyticsService = FakeAnalyticsService(),
|
||||
analyticsColdStartWatcher = FakeAnalyticsColdStartWatcher(),
|
||||
appCoroutineScope = backgroundScope,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -8,10 +8,11 @@
|
||||
|
||||
package io.element.android.appnav
|
||||
|
||||
import io.element.android.appnav.di.SyncOrchestrator
|
||||
import io.element.android.appnav.session.SyncOrchestrator
|
||||
import io.element.android.features.networkmonitor.api.NetworkStatus
|
||||
import io.element.android.features.networkmonitor.test.FakeNetworkMonitor
|
||||
import io.element.android.libraries.matrix.api.sync.SyncState
|
||||
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||
import io.element.android.libraries.matrix.test.sync.FakeSyncService
|
||||
import io.element.android.services.analytics.test.FakeAnalyticsService
|
||||
import io.element.android.services.appnavstate.test.FakeAppForegroundStateService
|
||||
@@ -40,7 +41,7 @@ class SyncOrchestratorTest {
|
||||
}
|
||||
val networkMonitor = FakeNetworkMonitor(initialStatus = NetworkStatus.Disconnected)
|
||||
val syncOrchestrator = createSyncOrchestrator(
|
||||
syncService = syncService,
|
||||
matrixClient = FakeMatrixClient(syncService = syncService),
|
||||
networkMonitor = networkMonitor,
|
||||
)
|
||||
|
||||
@@ -60,7 +61,7 @@ class SyncOrchestratorTest {
|
||||
}
|
||||
val networkMonitor = FakeNetworkMonitor(initialStatus = NetworkStatus.Connected)
|
||||
val syncOrchestrator = createSyncOrchestrator(
|
||||
syncService = syncService,
|
||||
matrixClient = FakeMatrixClient(syncService = syncService),
|
||||
networkMonitor = networkMonitor,
|
||||
)
|
||||
|
||||
@@ -85,7 +86,7 @@ class SyncOrchestratorTest {
|
||||
val networkMonitor = FakeNetworkMonitor(initialStatus = NetworkStatus.Connected)
|
||||
val appForegroundStateService = FakeAppForegroundStateService(initialForegroundValue = true)
|
||||
val syncOrchestrator = createSyncOrchestrator(
|
||||
syncService = syncService,
|
||||
matrixClient = FakeMatrixClient(syncService = syncService),
|
||||
networkMonitor = networkMonitor,
|
||||
appForegroundStateService = appForegroundStateService,
|
||||
)
|
||||
@@ -117,7 +118,7 @@ class SyncOrchestratorTest {
|
||||
val networkMonitor = FakeNetworkMonitor(initialStatus = NetworkStatus.Connected)
|
||||
val appForegroundStateService = FakeAppForegroundStateService(initialForegroundValue = true)
|
||||
val syncOrchestrator = createSyncOrchestrator(
|
||||
syncService = syncService,
|
||||
matrixClient = FakeMatrixClient(syncService = syncService),
|
||||
networkMonitor = networkMonitor,
|
||||
appForegroundStateService = appForegroundStateService,
|
||||
)
|
||||
@@ -165,7 +166,7 @@ class SyncOrchestratorTest {
|
||||
initialIsSyncingNotificationEventValue = false,
|
||||
)
|
||||
val syncOrchestrator = createSyncOrchestrator(
|
||||
syncService = syncService,
|
||||
matrixClient = FakeMatrixClient(syncService = syncService),
|
||||
networkMonitor = networkMonitor,
|
||||
appForegroundStateService = appForegroundStateService,
|
||||
)
|
||||
@@ -208,7 +209,7 @@ class SyncOrchestratorTest {
|
||||
initialIsSyncingNotificationEventValue = false,
|
||||
)
|
||||
val syncOrchestrator = createSyncOrchestrator(
|
||||
syncService = syncService,
|
||||
matrixClient = FakeMatrixClient(syncService = syncService),
|
||||
networkMonitor = networkMonitor,
|
||||
appForegroundStateService = appForegroundStateService,
|
||||
)
|
||||
@@ -252,7 +253,7 @@ class SyncOrchestratorTest {
|
||||
initialHasRingingCall = false,
|
||||
)
|
||||
val syncOrchestrator = createSyncOrchestrator(
|
||||
syncService = syncService,
|
||||
matrixClient = FakeMatrixClient(syncService = syncService),
|
||||
networkMonitor = networkMonitor,
|
||||
appForegroundStateService = appForegroundStateService,
|
||||
)
|
||||
@@ -296,7 +297,7 @@ class SyncOrchestratorTest {
|
||||
initialIsInCallValue = true,
|
||||
)
|
||||
val syncOrchestrator = createSyncOrchestrator(
|
||||
syncService = syncService,
|
||||
matrixClient = FakeMatrixClient(syncService = syncService),
|
||||
networkMonitor = networkMonitor,
|
||||
appForegroundStateService = appForegroundStateService,
|
||||
)
|
||||
@@ -339,7 +340,7 @@ class SyncOrchestratorTest {
|
||||
initialIsInCallValue = false,
|
||||
)
|
||||
val syncOrchestrator = createSyncOrchestrator(
|
||||
syncService = syncService,
|
||||
matrixClient = FakeMatrixClient(syncService = syncService),
|
||||
networkMonitor = networkMonitor,
|
||||
appForegroundStateService = appForegroundStateService,
|
||||
)
|
||||
@@ -369,7 +370,7 @@ class SyncOrchestratorTest {
|
||||
}
|
||||
val networkMonitor = FakeNetworkMonitor(initialStatus = NetworkStatus.Disconnected)
|
||||
val syncOrchestrator = createSyncOrchestrator(
|
||||
syncService = syncService,
|
||||
matrixClient = FakeMatrixClient(syncService = syncService),
|
||||
networkMonitor = networkMonitor,
|
||||
)
|
||||
|
||||
@@ -381,12 +382,44 @@ class SyncOrchestratorTest {
|
||||
startSyncRecorder.assertions().isNeverCalled()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when the client is shutting down, the orchestrator does nothing`() = runTest {
|
||||
val networkMonitor = FakeNetworkMonitor(initialStatus = NetworkStatus.Disconnected)
|
||||
val appForegroundStateService = FakeAppForegroundStateService(
|
||||
initialForegroundValue = false,
|
||||
initialIsSyncingNotificationEventValue = false,
|
||||
initialIsInCallValue = false,
|
||||
)
|
||||
|
||||
val startSyncRecorder = lambdaRecorder<Result<Unit>> { Result.success(Unit) }
|
||||
val syncService = FakeSyncService(initialSyncState = SyncState.Idle).apply {
|
||||
startSyncLambda = startSyncRecorder
|
||||
}
|
||||
val syncOrchestrator = createSyncOrchestrator(
|
||||
matrixClient = FakeMatrixClient(syncService = syncService, isShuttingDownResult = { true }),
|
||||
networkMonitor = networkMonitor,
|
||||
appForegroundStateService = appForegroundStateService,
|
||||
)
|
||||
|
||||
// We start observing
|
||||
syncOrchestrator.observeStates()
|
||||
|
||||
// These should still not trigger a sync, sync the client is shutting down
|
||||
networkMonitor.givenNetworkBlocked(false)
|
||||
appForegroundStateService.updateIsInCallState(true)
|
||||
appForegroundStateService.isInForeground.value = true
|
||||
|
||||
advanceTimeBy(10.seconds)
|
||||
|
||||
startSyncRecorder.assertions().isNeverCalled()
|
||||
}
|
||||
|
||||
private fun TestScope.createSyncOrchestrator(
|
||||
syncService: FakeSyncService = FakeSyncService(),
|
||||
matrixClient: FakeMatrixClient = FakeMatrixClient(),
|
||||
networkMonitor: FakeNetworkMonitor = FakeNetworkMonitor(),
|
||||
appForegroundStateService: FakeAppForegroundStateService = FakeAppForegroundStateService(),
|
||||
) = SyncOrchestrator(
|
||||
syncService = syncService,
|
||||
matrixClient = matrixClient,
|
||||
sessionCoroutineScope = backgroundScope,
|
||||
networkMonitor = networkMonitor,
|
||||
appForegroundStateService = appForegroundStateService,
|
||||
|
||||
@@ -45,6 +45,24 @@ class IntentResolverTest : RobolectricTest() {
|
||||
assertThat(result).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `resolve intent replayed from the history should return null`() {
|
||||
val sut = createIntentResolver(
|
||||
deeplinkParserResult = DeeplinkData.Room(
|
||||
sessionId = A_SESSION_ID,
|
||||
roomId = A_ROOM_ID,
|
||||
threadId = null,
|
||||
eventId = null,
|
||||
)
|
||||
)
|
||||
val intent = Intent(RuntimeEnvironment.getApplication(), Activity::class.java).apply {
|
||||
action = Intent.ACTION_VIEW
|
||||
addFlags(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY)
|
||||
}
|
||||
val result = sut.resolve(intent)
|
||||
assertThat(result).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test resolve navigation intent root`() {
|
||||
val sut = createIntentResolver(
|
||||
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright (c) 2026 Element Creations 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.appnav.session
|
||||
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.tests.testutils.lambda.lambdaError
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
|
||||
class FakeSyncOrchestratorFactory : SyncOrchestrator.Factory {
|
||||
override fun create(
|
||||
matrixClient: MatrixClient,
|
||||
sessionCoroutineScope: CoroutineScope,
|
||||
): SyncOrchestrator = lambdaError()
|
||||
}
|
||||
+4
-4
@@ -6,12 +6,12 @@
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.appnav.di
|
||||
package io.element.android.appnav.session
|
||||
|
||||
import com.bumble.appyx.core.state.MutableSavedStateMapImpl
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.features.networkmonitor.test.FakeNetworkMonitor
|
||||
import io.element.android.libraries.matrix.api.sync.SyncService
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.test.A_SESSION_ID
|
||||
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||
import io.element.android.libraries.matrix.test.auth.FakeMatrixAuthenticationService
|
||||
@@ -138,11 +138,11 @@ class MatrixSessionCacheTest {
|
||||
|
||||
return object : SyncOrchestrator.Factory {
|
||||
override fun create(
|
||||
syncService: SyncService,
|
||||
matrixClient: MatrixClient,
|
||||
sessionCoroutineScope: CoroutineScope,
|
||||
): SyncOrchestrator {
|
||||
return SyncOrchestrator(
|
||||
syncService = syncService,
|
||||
matrixClient = matrixClient,
|
||||
sessionCoroutineScope = sessionCoroutineScope,
|
||||
appForegroundStateService = FakeAppForegroundStateService(),
|
||||
networkMonitor = FakeNetworkMonitor(),
|
||||
Reference in New Issue
Block a user