Merge pull request #4215 from element-hq/feature/bma/airGappedSdk

Let the SDK provide the "network is available information"
This commit is contained in:
Benoit Marty 2025-02-03 21:24:20 +01:00 committed by GitHub
commit 34862d70ab
20 changed files with 86 additions and 74 deletions

View file

@ -19,8 +19,6 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import im.vector.app.features.analytics.plan.CryptoSessionStateChange import im.vector.app.features.analytics.plan.CryptoSessionStateChange
import im.vector.app.features.analytics.plan.UserProperties import im.vector.app.features.analytics.plan.UserProperties
import io.element.android.features.networkmonitor.api.NetworkMonitor
import io.element.android.features.networkmonitor.api.NetworkStatus
import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.core.log.logger.LoggerTag import io.element.android.libraries.core.log.logger.LoggerTag
@ -29,6 +27,8 @@ import io.element.android.libraries.matrix.api.encryption.EncryptionService
import io.element.android.libraries.matrix.api.encryption.RecoveryState import io.element.android.libraries.matrix.api.encryption.RecoveryState
import io.element.android.libraries.matrix.api.roomlist.RoomListService import io.element.android.libraries.matrix.api.roomlist.RoomListService
import io.element.android.libraries.matrix.api.sync.SlidingSyncVersion import io.element.android.libraries.matrix.api.sync.SlidingSyncVersion
import io.element.android.libraries.matrix.api.sync.SyncService
import io.element.android.libraries.matrix.api.sync.isOnline
import io.element.android.libraries.matrix.api.verification.SessionVerificationService import io.element.android.libraries.matrix.api.verification.SessionVerificationService
import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus
import io.element.android.libraries.preferences.api.store.EnableNativeSlidingSyncUseCase import io.element.android.libraries.preferences.api.store.EnableNativeSlidingSyncUseCase
@ -46,7 +46,7 @@ private val pusherTag = LoggerTag("Pusher", LoggerTag.PushLoggerTag)
class LoggedInPresenter @Inject constructor( class LoggedInPresenter @Inject constructor(
private val matrixClient: MatrixClient, private val matrixClient: MatrixClient,
private val networkMonitor: NetworkMonitor, private val syncService: SyncService,
private val pushService: PushService, private val pushService: PushService,
private val sessionVerificationService: SessionVerificationService, private val sessionVerificationService: SessionVerificationService,
private val analyticsService: AnalyticsService, private val analyticsService: AnalyticsService,
@ -76,10 +76,10 @@ class LoggedInPresenter @Inject constructor(
.launchIn(this) .launchIn(this)
} }
val syncIndicator by matrixClient.roomListService.syncIndicator.collectAsState() val syncIndicator by matrixClient.roomListService.syncIndicator.collectAsState()
val networkStatus by networkMonitor.connectivity.collectAsState() val isOnline by syncService.isOnline().collectAsState()
val showSyncSpinner by remember { val showSyncSpinner by remember {
derivedStateOf { derivedStateOf {
networkStatus == NetworkStatus.Online && syncIndicator == RoomListService.SyncIndicator.Show isOnline && syncIndicator == RoomListService.SyncIndicator.Show
} }
} }
var forceNativeSlidingSyncMigration by remember { mutableStateOf(false) } var forceNativeSlidingSyncMigration by remember { mutableStateOf(false) }

View file

@ -8,11 +8,12 @@
package io.element.android.appnav.loggedin package io.element.android.appnav.loggedin
import androidx.annotation.VisibleForTesting import androidx.annotation.VisibleForTesting
import io.element.android.features.networkmonitor.api.NetworkMonitor
import io.element.android.features.networkmonitor.api.NetworkStatus import io.element.android.features.networkmonitor.api.NetworkStatus
import io.element.android.libraries.di.SessionScope import io.element.android.libraries.di.SessionScope
import io.element.android.libraries.di.SingleIn import io.element.android.libraries.di.SingleIn
import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.sync.SyncService
import io.element.android.libraries.matrix.api.sync.SyncState
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
@ -27,7 +28,7 @@ const val SEND_QUEUES_RETRY_DELAY_MILLIS = 500L
@SingleIn(SessionScope::class) @SingleIn(SessionScope::class)
class SendQueues @Inject constructor( class SendQueues @Inject constructor(
private val matrixClient: MatrixClient, private val matrixClient: MatrixClient,
private val networkMonitor: NetworkMonitor, private val syncService: SyncService,
) { ) {
/** /**
* Launches the send queues retry mechanism in the given [coroutineScope]. * Launches the send queues retry mechanism in the given [coroutineScope].
@ -36,12 +37,12 @@ class SendQueues @Inject constructor(
@OptIn(FlowPreview::class) @OptIn(FlowPreview::class)
fun launchIn(coroutineScope: CoroutineScope) { fun launchIn(coroutineScope: CoroutineScope) {
combine( combine(
networkMonitor.connectivity, syncService.syncState,
matrixClient.sendQueueDisabledFlow(), matrixClient.sendQueueDisabledFlow(),
) { networkStatus, _ -> networkStatus } ) { syncState, _ -> syncState }
.debounce(SEND_QUEUES_RETRY_DELAY_MILLIS) .debounce(SEND_QUEUES_RETRY_DELAY_MILLIS)
.onEach { networkStatus -> .onEach { syncState ->
if (networkStatus == NetworkStatus.Online) { if (syncState == SyncState.Running) {
matrixClient.setAllSendQueuesEnabled(enabled = true) matrixClient.setAllSendQueuesEnabled(enabled = true)
} }
} }

View file

@ -30,8 +30,6 @@ import io.element.android.appnav.room.joined.JoinedRoomLoadedFlowNode
import io.element.android.appnav.room.joined.LoadingRoomNodeView import io.element.android.appnav.room.joined.LoadingRoomNodeView
import io.element.android.appnav.room.joined.LoadingRoomState import io.element.android.appnav.room.joined.LoadingRoomState
import io.element.android.features.joinroom.api.JoinRoomEntryPoint import io.element.android.features.joinroom.api.JoinRoomEntryPoint
import io.element.android.features.networkmonitor.api.NetworkMonitor
import io.element.android.features.networkmonitor.api.NetworkStatus
import io.element.android.features.roomaliasesolver.api.RoomAliasResolverEntryPoint import io.element.android.features.roomaliasesolver.api.RoomAliasResolverEntryPoint
import io.element.android.features.roomdirectory.api.RoomDescription import io.element.android.features.roomdirectory.api.RoomDescription
import io.element.android.libraries.architecture.BackstackView import io.element.android.libraries.architecture.BackstackView
@ -50,6 +48,8 @@ import io.element.android.libraries.matrix.api.getRoomInfoFlow
import io.element.android.libraries.matrix.api.room.CurrentUserMembership import io.element.android.libraries.matrix.api.room.CurrentUserMembership
import io.element.android.libraries.matrix.api.room.RoomMembershipObserver import io.element.android.libraries.matrix.api.room.RoomMembershipObserver
import io.element.android.libraries.matrix.api.room.alias.ResolvedRoomAlias import io.element.android.libraries.matrix.api.room.alias.ResolvedRoomAlias
import io.element.android.libraries.matrix.api.sync.SyncService
import io.element.android.libraries.matrix.api.sync.isOnline
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
@ -68,7 +68,7 @@ class RoomFlowNode @AssistedInject constructor(
private val client: MatrixClient, private val client: MatrixClient,
private val joinRoomEntryPoint: JoinRoomEntryPoint, private val joinRoomEntryPoint: JoinRoomEntryPoint,
private val roomAliasResolverEntryPoint: RoomAliasResolverEntryPoint, private val roomAliasResolverEntryPoint: RoomAliasResolverEntryPoint,
private val networkMonitor: NetworkMonitor, private val syncService: SyncService,
private val membershipObserver: RoomMembershipObserver, private val membershipObserver: RoomMembershipObserver,
) : BaseFlowNode<RoomFlowNode.NavTarget>( ) : BaseFlowNode<RoomFlowNode.NavTarget>(
backstack = BackStack( backstack = BackStack(
@ -211,10 +211,10 @@ class RoomFlowNode @AssistedInject constructor(
} }
private fun loadingNode(buildContext: BuildContext) = node(buildContext) { modifier -> private fun loadingNode(buildContext: BuildContext) = node(buildContext) { modifier ->
val networkStatus by networkMonitor.connectivity.collectAsState() val isOnline by syncService.isOnline().collectAsState()
LoadingRoomNodeView( LoadingRoomNodeView(
state = LoadingRoomState.Loading, state = LoadingRoomState.Loading,
hasNetworkConnection = networkStatus == NetworkStatus.Online, hasNetworkConnection = isOnline,
onBackClick = { navigateUp() }, onBackClick = { navigateUp() },
modifier = modifier, modifier = modifier,
) )

View file

@ -28,8 +28,6 @@ import dagger.assisted.Assisted
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
import io.element.android.anvilannotations.ContributesNode import io.element.android.anvilannotations.ContributesNode
import io.element.android.appnav.room.RoomNavigationTarget import io.element.android.appnav.room.RoomNavigationTarget
import io.element.android.features.networkmonitor.api.NetworkMonitor
import io.element.android.features.networkmonitor.api.NetworkStatus
import io.element.android.libraries.architecture.BackstackView import io.element.android.libraries.architecture.BackstackView
import io.element.android.libraries.architecture.BaseFlowNode import io.element.android.libraries.architecture.BaseFlowNode
import io.element.android.libraries.architecture.NodeInputs import io.element.android.libraries.architecture.NodeInputs
@ -37,6 +35,8 @@ import io.element.android.libraries.architecture.createNode
import io.element.android.libraries.architecture.inputs import io.element.android.libraries.architecture.inputs
import io.element.android.libraries.di.SessionScope import io.element.android.libraries.di.SessionScope
import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.sync.SyncService
import io.element.android.libraries.matrix.api.sync.isOnline
import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
@ -48,7 +48,7 @@ class JoinedRoomFlowNode @AssistedInject constructor(
@Assisted val buildContext: BuildContext, @Assisted val buildContext: BuildContext,
@Assisted plugins: List<Plugin>, @Assisted plugins: List<Plugin>,
loadingRoomStateFlowFactory: LoadingRoomStateFlowFactory, loadingRoomStateFlowFactory: LoadingRoomStateFlowFactory,
private val networkMonitor: NetworkMonitor, private val syncService: SyncService,
) : ) :
BaseFlowNode<JoinedRoomFlowNode.NavTarget>( BaseFlowNode<JoinedRoomFlowNode.NavTarget>(
backstack = BackStack( backstack = BackStack(
@ -114,10 +114,10 @@ class JoinedRoomFlowNode @AssistedInject constructor(
private fun loadingNode(buildContext: BuildContext, onBackClick: () -> Unit) = node(buildContext) { modifier -> private fun loadingNode(buildContext: BuildContext, onBackClick: () -> Unit) = node(buildContext) { modifier ->
val loadingRoomState by loadingRoomStateStateFlow.collectAsState() val loadingRoomState by loadingRoomStateStateFlow.collectAsState()
val networkStatus by networkMonitor.connectivity.collectAsState() val isOnline by syncService.isOnline().collectAsState()
LoadingRoomNodeView( LoadingRoomNodeView(
state = loadingRoomState, state = loadingRoomState,
hasNetworkConnection = networkStatus == NetworkStatus.Online, hasNetworkConnection = isOnline,
modifier = modifier, modifier = modifier,
onBackClick = onBackClick onBackClick = onBackClick
) )

View file

@ -14,14 +14,13 @@ import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import im.vector.app.features.analytics.plan.CryptoSessionStateChange import im.vector.app.features.analytics.plan.CryptoSessionStateChange
import im.vector.app.features.analytics.plan.UserProperties import im.vector.app.features.analytics.plan.UserProperties
import io.element.android.features.networkmonitor.api.NetworkStatus
import io.element.android.features.networkmonitor.test.FakeNetworkMonitor
import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.matrix.api.encryption.EncryptionService import io.element.android.libraries.matrix.api.encryption.EncryptionService
import io.element.android.libraries.matrix.api.encryption.RecoveryState import io.element.android.libraries.matrix.api.encryption.RecoveryState
import io.element.android.libraries.matrix.api.roomlist.RoomListService import io.element.android.libraries.matrix.api.roomlist.RoomListService
import io.element.android.libraries.matrix.api.sync.SlidingSyncVersion import io.element.android.libraries.matrix.api.sync.SlidingSyncVersion
import io.element.android.libraries.matrix.api.sync.SyncState
import io.element.android.libraries.matrix.api.verification.SessionVerificationService import io.element.android.libraries.matrix.api.verification.SessionVerificationService
import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus
import io.element.android.libraries.matrix.test.AN_EXCEPTION import io.element.android.libraries.matrix.test.AN_EXCEPTION
@ -29,6 +28,7 @@ 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.FakeMatrixClient
import io.element.android.libraries.matrix.test.encryption.FakeEncryptionService import io.element.android.libraries.matrix.test.encryption.FakeEncryptionService
import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService
import io.element.android.libraries.matrix.test.sync.FakeSyncService
import io.element.android.libraries.matrix.test.verification.FakeSessionVerificationService import io.element.android.libraries.matrix.test.verification.FakeSessionVerificationService
import io.element.android.libraries.preferences.api.store.EnableNativeSlidingSyncUseCase import io.element.android.libraries.preferences.api.store.EnableNativeSlidingSyncUseCase
import io.element.android.libraries.preferences.test.InMemoryAppPreferencesStore import io.element.android.libraries.preferences.test.InMemoryAppPreferencesStore
@ -73,7 +73,7 @@ class LoggedInPresenterTest {
@Test @Test
fun `present - show sync spinner`() = runTest { fun `present - show sync spinner`() = runTest {
val roomListService = FakeRoomListService() val roomListService = FakeRoomListService()
val presenter = createLoggedInPresenter(roomListService, NetworkStatus.Online) val presenter = createLoggedInPresenter(roomListService, SyncState.Running)
moleculeFlow(RecompositionMode.Immediate) { moleculeFlow(RecompositionMode.Immediate) {
presenter.present() presenter.present()
}.test { }.test {
@ -94,7 +94,7 @@ class LoggedInPresenterTest {
val encryptionService = FakeEncryptionService() val encryptionService = FakeEncryptionService()
val presenter = LoggedInPresenter( val presenter = LoggedInPresenter(
matrixClient = FakeMatrixClient(roomListService = roomListService, encryptionService = encryptionService), matrixClient = FakeMatrixClient(roomListService = roomListService, encryptionService = encryptionService),
networkMonitor = FakeNetworkMonitor(NetworkStatus.Online), syncService = FakeSyncService(initialSyncState = SyncState.Running),
pushService = FakePushService(), pushService = FakePushService(),
sessionVerificationService = verificationService, sessionVerificationService = verificationService,
analyticsService = analyticsService, analyticsService = analyticsService,
@ -574,7 +574,7 @@ class LoggedInPresenterTest {
private fun TestScope.createLoggedInPresenter( private fun TestScope.createLoggedInPresenter(
roomListService: RoomListService = FakeRoomListService(), roomListService: RoomListService = FakeRoomListService(),
networkStatus: NetworkStatus = NetworkStatus.Offline, syncState: SyncState = SyncState.Running,
analyticsService: AnalyticsService = FakeAnalyticsService(), analyticsService: AnalyticsService = FakeAnalyticsService(),
sessionVerificationService: SessionVerificationService = FakeSessionVerificationService(), sessionVerificationService: SessionVerificationService = FakeSessionVerificationService(),
encryptionService: EncryptionService = FakeEncryptionService(), encryptionService: EncryptionService = FakeEncryptionService(),
@ -584,7 +584,7 @@ class LoggedInPresenterTest {
): LoggedInPresenter { ): LoggedInPresenter {
return LoggedInPresenter( return LoggedInPresenter(
matrixClient = matrixClient, matrixClient = matrixClient,
networkMonitor = FakeNetworkMonitor(networkStatus), syncService = FakeSyncService(initialSyncState = syncState),
pushService = pushService, pushService = pushService,
sessionVerificationService = sessionVerificationService, sessionVerificationService = sessionVerificationService,
analyticsService = analyticsService, analyticsService = analyticsService,

View file

@ -7,11 +7,11 @@
package io.element.android.appnav.loggedin package io.element.android.appnav.loggedin
import io.element.android.features.networkmonitor.api.NetworkStatus
import io.element.android.features.networkmonitor.test.FakeNetworkMonitor
import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.sync.SyncState
import io.element.android.libraries.matrix.test.FakeMatrixClient import io.element.android.libraries.matrix.test.FakeMatrixClient
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
import io.element.android.libraries.matrix.test.sync.FakeSyncService
import io.element.android.tests.testutils.lambda.assert import io.element.android.tests.testutils.lambda.assert
import io.element.android.tests.testutils.lambda.lambdaRecorder import io.element.android.tests.testutils.lambda.lambdaRecorder
import io.element.android.tests.testutils.lambda.value import io.element.android.tests.testutils.lambda.value
@ -25,8 +25,8 @@ import org.junit.Test
@OptIn(ExperimentalCoroutinesApi::class) @OptIn(ExperimentalCoroutinesApi::class)
class SendQueuesTest { class SendQueuesTest {
private val matrixClient = FakeMatrixClient() private val matrixClient = FakeMatrixClient()
private val networkMonitor = FakeNetworkMonitor() private val syncService = FakeSyncService(initialSyncState = SyncState.Running)
private val sut = SendQueues(matrixClient, networkMonitor) private val sut = SendQueues(matrixClient, syncService)
@Test @Test
fun `test network status online and sending queue failed`() = runTest { fun `test network status online and sending queue failed`() = runTest {
@ -53,13 +53,13 @@ class SendQueuesTest {
} }
@Test @Test
fun `test network status offline and sending queue failed`() = runTest { fun `test sync state offline and sending queue failed`() = runTest {
val sendQueueDisabledFlow = MutableSharedFlow<RoomId>(replay = 1) val sendQueueDisabledFlow = MutableSharedFlow<RoomId>(replay = 1)
val setAllSendQueuesEnabledLambda = lambdaRecorder { _: Boolean -> } val setAllSendQueuesEnabledLambda = lambdaRecorder { _: Boolean -> }
matrixClient.sendQueueDisabledFlow = sendQueueDisabledFlow matrixClient.sendQueueDisabledFlow = sendQueueDisabledFlow
matrixClient.setAllSendQueuesEnabledLambda = setAllSendQueuesEnabledLambda matrixClient.setAllSendQueuesEnabledLambda = setAllSendQueuesEnabledLambda
networkMonitor.connectivity.value = NetworkStatus.Offline syncService.emitSyncState(SyncState.Offline)
val setRoomSendQueueEnabledLambda = lambdaRecorder { _: Boolean -> } val setRoomSendQueueEnabledLambda = lambdaRecorder { _: Boolean -> }
val room = FakeMatrixRoom( val room = FakeMatrixRoom(
setSendQueueEnabledLambda = setRoomSendQueueEnabledLambda setSendQueueEnabledLambda = setRoomSendQueueEnabledLambda

View file

@ -41,7 +41,6 @@ import io.element.android.tests.testutils.lambda.value
import io.element.android.tests.testutils.testCoroutineDispatchers import io.element.android.tests.testutils.testCoroutineDispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.cancelAndJoin import kotlinx.coroutines.cancelAndJoin
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.TestScope
@ -82,7 +81,7 @@ class CallScreenPresenterTest {
@Test @Test
fun `present - with CallType RoomCall sets call as active, loads URL, runs WidgetDriver and notifies the other clients a call started`() = runTest { fun `present - with CallType RoomCall sets call as active, loads URL, runs WidgetDriver and notifies the other clients a call started`() = runTest {
val sendCallNotificationIfNeededLambda = lambdaRecorder<Result<Unit>> { Result.success(Unit) } val sendCallNotificationIfNeededLambda = lambdaRecorder<Result<Unit>> { Result.success(Unit) }
val syncService = FakeSyncService(MutableStateFlow(SyncState.Running)) val syncService = FakeSyncService(SyncState.Running)
val fakeRoom = FakeMatrixRoom(sendCallNotificationIfNeededResult = sendCallNotificationIfNeededLambda) val fakeRoom = FakeMatrixRoom(sendCallNotificationIfNeededResult = sendCallNotificationIfNeededLambda)
val client = FakeMatrixClient(syncService = syncService).apply { val client = FakeMatrixClient(syncService = syncService).apply {
givenGetRoomResult(A_ROOM_ID, fakeRoom) givenGetRoomResult(A_ROOM_ID, fakeRoom)
@ -247,9 +246,8 @@ class CallScreenPresenterTest {
fun `present - automatically starts the Matrix client sync when on RoomCall`() = runTest { fun `present - automatically starts the Matrix client sync when on RoomCall`() = runTest {
val navigator = FakeCallScreenNavigator() val navigator = FakeCallScreenNavigator()
val widgetDriver = FakeMatrixWidgetDriver() val widgetDriver = FakeMatrixWidgetDriver()
val syncStateFlow = MutableStateFlow(SyncState.Idle)
val startSyncLambda = lambdaRecorder<Result<Unit>> { Result.success(Unit) } val startSyncLambda = lambdaRecorder<Result<Unit>> { Result.success(Unit) }
val syncService = FakeSyncService(syncStateFlow = syncStateFlow).apply { val syncService = FakeSyncService(SyncState.Idle).apply {
this.startSyncLambda = startSyncLambda this.startSyncLambda = startSyncLambda
} }
val matrixClient = FakeMatrixClient(syncService = syncService) val matrixClient = FakeMatrixClient(syncService = syncService)
@ -276,9 +274,8 @@ class CallScreenPresenterTest {
fun `present - automatically stops the Matrix client sync on dispose`() = runTest { fun `present - automatically stops the Matrix client sync on dispose`() = runTest {
val navigator = FakeCallScreenNavigator() val navigator = FakeCallScreenNavigator()
val widgetDriver = FakeMatrixWidgetDriver() val widgetDriver = FakeMatrixWidgetDriver()
val syncStateFlow = MutableStateFlow(SyncState.Running)
val stopSyncLambda = lambdaRecorder<Result<Unit>> { Result.success(Unit) } val stopSyncLambda = lambdaRecorder<Result<Unit>> { Result.success(Unit) }
val syncService = FakeSyncService(syncStateFlow = syncStateFlow).apply { val syncService = FakeSyncService(SyncState.Running).apply {
this.stopSyncLambda = stopSyncLambda this.stopSyncLambda = stopSyncLambda
} }
val matrixClient = FakeMatrixClient(syncService = syncService) val matrixClient = FakeMatrixClient(syncService = syncService)

View file

@ -47,8 +47,6 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextBasedContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextBasedContent
import io.element.android.features.messages.impl.timeline.protection.TimelineProtectionState import io.element.android.features.messages.impl.timeline.protection.TimelineProtectionState
import io.element.android.features.messages.impl.voicemessages.composer.VoiceMessageComposerState import io.element.android.features.messages.impl.voicemessages.composer.VoiceMessageComposerState
import io.element.android.features.networkmonitor.api.NetworkMonitor
import io.element.android.features.networkmonitor.api.NetworkStatus
import io.element.android.features.roomcall.api.RoomCallState import io.element.android.features.roomcall.api.RoomCallState
import io.element.android.libraries.androidutils.clipboard.ClipboardHelper import io.element.android.libraries.androidutils.clipboard.ClipboardHelper
import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.AsyncData
@ -72,6 +70,8 @@ import io.element.android.libraries.matrix.api.room.powerlevels.canPinUnpin
import io.element.android.libraries.matrix.api.room.powerlevels.canRedactOther import io.element.android.libraries.matrix.api.room.powerlevels.canRedactOther
import io.element.android.libraries.matrix.api.room.powerlevels.canRedactOwn import io.element.android.libraries.matrix.api.room.powerlevels.canRedactOwn
import io.element.android.libraries.matrix.api.room.powerlevels.canSendMessage import io.element.android.libraries.matrix.api.room.powerlevels.canSendMessage
import io.element.android.libraries.matrix.api.sync.SyncService
import io.element.android.libraries.matrix.api.sync.isOnline
import io.element.android.libraries.matrix.api.timeline.item.event.EventOrTransactionId import io.element.android.libraries.matrix.api.timeline.item.event.EventOrTransactionId
import io.element.android.libraries.matrix.ui.messages.reply.map import io.element.android.libraries.matrix.ui.messages.reply.map
import io.element.android.libraries.matrix.ui.model.getAvatarData import io.element.android.libraries.matrix.ui.model.getAvatarData
@ -98,7 +98,7 @@ class MessagesPresenter @AssistedInject constructor(
private val readReceiptBottomSheetPresenter: Presenter<ReadReceiptBottomSheetState>, private val readReceiptBottomSheetPresenter: Presenter<ReadReceiptBottomSheetState>,
private val pinnedMessagesBannerPresenter: Presenter<PinnedMessagesBannerState>, private val pinnedMessagesBannerPresenter: Presenter<PinnedMessagesBannerState>,
private val roomCallStatePresenter: Presenter<RoomCallState>, private val roomCallStatePresenter: Presenter<RoomCallState>,
private val networkMonitor: NetworkMonitor, private val syncService: SyncService,
private val snackbarDispatcher: SnackbarDispatcher, private val snackbarDispatcher: SnackbarDispatcher,
private val dispatchers: CoroutineDispatchers, private val dispatchers: CoroutineDispatchers,
private val clipboardHelper: ClipboardHelper, private val clipboardHelper: ClipboardHelper,
@ -170,7 +170,7 @@ class MessagesPresenter @AssistedInject constructor(
showReinvitePrompt = !hasDismissedInviteDialog && composerState.textEditorState.hasFocus() && room.isDm && room.activeMemberCount == 1L showReinvitePrompt = !hasDismissedInviteDialog && composerState.textEditorState.hasFocus() && room.isDm && room.activeMemberCount == 1L
} }
} }
val networkConnectionStatus by networkMonitor.connectivity.collectAsState() val isOnline by syncService.isOnline().collectAsState()
val snackbarMessage by snackbarDispatcher.collectSnackbarMessageAsState() val snackbarMessage by snackbarDispatcher.collectSnackbarMessageAsState()
@ -220,7 +220,7 @@ class MessagesPresenter @AssistedInject constructor(
customReactionState = customReactionState, customReactionState = customReactionState,
reactionSummaryState = reactionSummaryState, reactionSummaryState = reactionSummaryState,
readReceiptBottomSheetState = readReceiptBottomSheetState, readReceiptBottomSheetState = readReceiptBottomSheetState,
hasNetworkConnection = networkConnectionStatus == NetworkStatus.Online, hasNetworkConnection = isOnline,
snackbarMessage = snackbarMessage, snackbarMessage = snackbarMessage,
showReinvitePrompt = showReinvitePrompt, showReinvitePrompt = showReinvitePrompt,
inviteProgress = inviteProgress.value, inviteProgress = inviteProgress.value,

View file

@ -7,7 +7,6 @@
package io.element.android.features.messages.impl.pinned package io.element.android.features.messages.impl.pinned
import io.element.android.features.networkmonitor.api.NetworkMonitor
import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.core.coroutine.mapState import io.element.android.libraries.core.coroutine.mapState
import io.element.android.libraries.di.RoomScope import io.element.android.libraries.di.RoomScope
@ -15,6 +14,7 @@ import io.element.android.libraries.di.SingleIn
import io.element.android.libraries.featureflag.api.FeatureFlagService import io.element.android.libraries.featureflag.api.FeatureFlagService
import io.element.android.libraries.featureflag.api.FeatureFlags import io.element.android.libraries.featureflag.api.FeatureFlags
import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.matrix.api.sync.SyncService
import io.element.android.libraries.matrix.api.timeline.Timeline import io.element.android.libraries.matrix.api.timeline.Timeline
import io.element.android.libraries.matrix.api.timeline.TimelineProvider import io.element.android.libraries.matrix.api.timeline.TimelineProvider
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@ -31,7 +31,7 @@ import javax.inject.Inject
@SingleIn(RoomScope::class) @SingleIn(RoomScope::class)
class PinnedEventsTimelineProvider @Inject constructor( class PinnedEventsTimelineProvider @Inject constructor(
private val room: MatrixRoom, private val room: MatrixRoom,
private val networkMonitor: NetworkMonitor, private val syncService: SyncService,
private val featureFlagService: FeatureFlagService, private val featureFlagService: FeatureFlagService,
) : TimelineProvider { ) : TimelineProvider {
private val _timelineStateFlow: MutableStateFlow<AsyncData<Timeline>> = private val _timelineStateFlow: MutableStateFlow<AsyncData<Timeline>> =
@ -63,9 +63,9 @@ class PinnedEventsTimelineProvider @Inject constructor(
private suspend fun onActive() = coroutineScope { private suspend fun onActive() = coroutineScope {
combine( combine(
featureFlagService.isFeatureEnabledFlow(FeatureFlags.PinnedEvents), featureFlagService.isFeatureEnabledFlow(FeatureFlags.PinnedEvents),
networkMonitor.connectivity syncService.syncState,
) { isEnabled, _ -> ) { isEnabled, _ ->
// do not use connectivity here as data can be loaded from cache, it's just to trigger retry if needed // do not use syncState here as data can be loaded from cache, it's just to trigger retry if needed
isEnabled isEnabled
} }
.onEach { isFeatureEnabled -> .onEach { isFeatureEnabled ->

View file

@ -35,7 +35,6 @@ import io.element.android.features.messages.impl.timeline.model.event.aTimelineI
import io.element.android.features.messages.impl.timeline.protection.aTimelineProtectionState import io.element.android.features.messages.impl.timeline.protection.aTimelineProtectionState
import io.element.android.features.messages.impl.voicemessages.composer.aVoiceMessageComposerState import io.element.android.features.messages.impl.voicemessages.composer.aVoiceMessageComposerState
import io.element.android.features.messages.test.timeline.FakeHtmlConverterProvider import io.element.android.features.messages.test.timeline.FakeHtmlConverterProvider
import io.element.android.features.networkmonitor.test.FakeNetworkMonitor
import io.element.android.features.roomcall.api.aStandByCallState import io.element.android.features.roomcall.api.aStandByCallState
import io.element.android.libraries.androidutils.clipboard.FakeClipboardHelper import io.element.android.libraries.androidutils.clipboard.FakeClipboardHelper
import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.AsyncData
@ -72,6 +71,7 @@ import io.element.android.libraries.matrix.test.permalink.FakePermalinkParser
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
import io.element.android.libraries.matrix.test.room.aRoomInfo import io.element.android.libraries.matrix.test.room.aRoomInfo
import io.element.android.libraries.matrix.test.room.aRoomMember import io.element.android.libraries.matrix.test.room.aRoomMember
import io.element.android.libraries.matrix.test.sync.FakeSyncService
import io.element.android.libraries.matrix.test.timeline.FakeTimeline import io.element.android.libraries.matrix.test.timeline.FakeTimeline
import io.element.android.libraries.matrix.test.timeline.aTimelineItemDebugInfo import io.element.android.libraries.matrix.test.timeline.aTimelineItemDebugInfo
import io.element.android.libraries.matrix.ui.messages.reply.InReplyToDetails import io.element.android.libraries.matrix.ui.messages.reply.InReplyToDetails
@ -1187,7 +1187,7 @@ class MessagesPresenterTest {
identityChangeStatePresenter = { anIdentityChangeState() }, identityChangeStatePresenter = { anIdentityChangeState() },
pinnedMessagesBannerPresenter = { aLoadedPinnedMessagesBannerState() }, pinnedMessagesBannerPresenter = { aLoadedPinnedMessagesBannerState() },
roomCallStatePresenter = { aStandByCallState() }, roomCallStatePresenter = { aStandByCallState() },
networkMonitor = FakeNetworkMonitor(), syncService = FakeSyncService(),
snackbarDispatcher = SnackbarDispatcher(), snackbarDispatcher = SnackbarDispatcher(),
navigator = navigator, navigator = navigator,
clipboardHelper = clipboardHelper, clipboardHelper = clipboardHelper,

View file

@ -9,12 +9,11 @@ package io.element.android.features.messages.impl.pinned.banner
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import io.element.android.features.messages.impl.pinned.PinnedEventsTimelineProvider import io.element.android.features.messages.impl.pinned.PinnedEventsTimelineProvider
import io.element.android.features.networkmonitor.api.NetworkMonitor
import io.element.android.features.networkmonitor.test.FakeNetworkMonitor
import io.element.android.libraries.eventformatter.test.FakePinnedMessagesBannerFormatter import io.element.android.libraries.eventformatter.test.FakePinnedMessagesBannerFormatter
import io.element.android.libraries.featureflag.api.FeatureFlags import io.element.android.libraries.featureflag.api.FeatureFlags
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.matrix.api.sync.SyncService
import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
import io.element.android.libraries.matrix.test.AN_EVENT_ID import io.element.android.libraries.matrix.test.AN_EVENT_ID
import io.element.android.libraries.matrix.test.AN_EVENT_ID_2 import io.element.android.libraries.matrix.test.AN_EVENT_ID_2
@ -22,6 +21,7 @@ import io.element.android.libraries.matrix.test.A_UNIQUE_ID
import io.element.android.libraries.matrix.test.A_UNIQUE_ID_2 import io.element.android.libraries.matrix.test.A_UNIQUE_ID_2
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
import io.element.android.libraries.matrix.test.room.aRoomInfo import io.element.android.libraries.matrix.test.room.aRoomInfo
import io.element.android.libraries.matrix.test.sync.FakeSyncService
import io.element.android.libraries.matrix.test.timeline.FakeTimeline import io.element.android.libraries.matrix.test.timeline.FakeTimeline
import io.element.android.libraries.matrix.test.timeline.aMessageContent import io.element.android.libraries.matrix.test.timeline.aMessageContent
import io.element.android.libraries.matrix.test.timeline.anEventTimelineItem import io.element.android.libraries.matrix.test.timeline.anEventTimelineItem
@ -186,12 +186,12 @@ class PinnedMessagesBannerPresenterTest {
formatLambda = { event -> "${event.content}" } formatLambda = { event -> "${event.content}" }
) )
), ),
networkMonitor: NetworkMonitor = FakeNetworkMonitor(), syncService: SyncService = FakeSyncService(),
isFeatureEnabled: Boolean = true, isFeatureEnabled: Boolean = true,
): PinnedMessagesBannerPresenter { ): PinnedMessagesBannerPresenter {
val timelineProvider = PinnedEventsTimelineProvider( val timelineProvider = PinnedEventsTimelineProvider(
room = room, room = room,
networkMonitor = networkMonitor, syncService = syncService,
featureFlagService = FakeFeatureFlagService( featureFlagService = FakeFeatureFlagService(
initialState = mapOf(FeatureFlags.PinnedEvents.key to isFeatureEnabled) initialState = mapOf(FeatureFlags.PinnedEvents.key to isFeatureEnabled)
) )

View file

@ -15,13 +15,12 @@ import io.element.android.features.messages.impl.fixtures.aTimelineItemsFactoryC
import io.element.android.features.messages.impl.pinned.PinnedEventsTimelineProvider import io.element.android.features.messages.impl.pinned.PinnedEventsTimelineProvider
import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.features.messages.impl.timeline.model.TimelineItem
import io.element.android.features.messages.impl.timeline.protection.aTimelineProtectionState import io.element.android.features.messages.impl.timeline.protection.aTimelineProtectionState
import io.element.android.features.networkmonitor.api.NetworkMonitor
import io.element.android.features.networkmonitor.test.FakeNetworkMonitor
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher
import io.element.android.libraries.featureflag.api.FeatureFlags import io.element.android.libraries.featureflag.api.FeatureFlags
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.matrix.api.sync.SyncService
import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo
import io.element.android.libraries.matrix.test.AN_EVENT_ID import io.element.android.libraries.matrix.test.AN_EVENT_ID
@ -29,6 +28,7 @@ import io.element.android.libraries.matrix.test.A_THROWABLE
import io.element.android.libraries.matrix.test.A_UNIQUE_ID import io.element.android.libraries.matrix.test.A_UNIQUE_ID
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
import io.element.android.libraries.matrix.test.room.aRoomInfo import io.element.android.libraries.matrix.test.room.aRoomInfo
import io.element.android.libraries.matrix.test.sync.FakeSyncService
import io.element.android.libraries.matrix.test.timeline.FakeTimeline import io.element.android.libraries.matrix.test.timeline.FakeTimeline
import io.element.android.libraries.matrix.test.timeline.aMessageContent import io.element.android.libraries.matrix.test.timeline.aMessageContent
import io.element.android.libraries.matrix.test.timeline.anEventTimelineItem import io.element.android.libraries.matrix.test.timeline.anEventTimelineItem
@ -293,13 +293,13 @@ class PinnedMessagesListPresenterTest {
private fun TestScope.createPinnedMessagesListPresenter( private fun TestScope.createPinnedMessagesListPresenter(
navigator: PinnedMessagesListNavigator = FakePinnedMessagesListNavigator(), navigator: PinnedMessagesListNavigator = FakePinnedMessagesListNavigator(),
room: MatrixRoom = FakeMatrixRoom(), room: MatrixRoom = FakeMatrixRoom(),
networkMonitor: NetworkMonitor = FakeNetworkMonitor(), syncService: SyncService = FakeSyncService(),
isFeatureEnabled: Boolean = true, isFeatureEnabled: Boolean = true,
analyticsService: AnalyticsService = FakeAnalyticsService(), analyticsService: AnalyticsService = FakeAnalyticsService(),
): PinnedMessagesListPresenter { ): PinnedMessagesListPresenter {
val timelineProvider = PinnedEventsTimelineProvider( val timelineProvider = PinnedEventsTimelineProvider(
room = room, room = room,
networkMonitor = networkMonitor, syncService = syncService,
featureFlagService = FakeFeatureFlagService( featureFlagService = FakeFeatureFlagService(
initialState = mapOf(FeatureFlags.PinnedEvents.key to isFeatureEnabled) initialState = mapOf(FeatureFlags.PinnedEvents.key to isFeatureEnabled)
) )

View file

@ -30,8 +30,6 @@ import io.element.android.features.invite.api.response.InviteData
import io.element.android.features.leaveroom.api.LeaveRoomEvent import io.element.android.features.leaveroom.api.LeaveRoomEvent
import io.element.android.features.leaveroom.api.LeaveRoomState import io.element.android.features.leaveroom.api.LeaveRoomState
import io.element.android.features.logout.api.direct.DirectLogoutState import io.element.android.features.logout.api.direct.DirectLogoutState
import io.element.android.features.networkmonitor.api.NetworkMonitor
import io.element.android.features.networkmonitor.api.NetworkStatus
import io.element.android.features.roomlist.impl.datasource.RoomListDataSource import io.element.android.features.roomlist.impl.datasource.RoomListDataSource
import io.element.android.features.roomlist.impl.filters.RoomListFiltersState import io.element.android.features.roomlist.impl.filters.RoomListFiltersState
import io.element.android.features.roomlist.impl.model.RoomListRoomSummary import io.element.android.features.roomlist.impl.model.RoomListRoomSummary
@ -51,6 +49,8 @@ import io.element.android.libraries.matrix.api.encryption.EncryptionService
import io.element.android.libraries.matrix.api.encryption.RecoveryState import io.element.android.libraries.matrix.api.encryption.RecoveryState
import io.element.android.libraries.matrix.api.roomlist.RoomList import io.element.android.libraries.matrix.api.roomlist.RoomList
import io.element.android.libraries.matrix.api.sync.SlidingSyncVersion import io.element.android.libraries.matrix.api.sync.SlidingSyncVersion
import io.element.android.libraries.matrix.api.sync.SyncService
import io.element.android.libraries.matrix.api.sync.isOnline
import io.element.android.libraries.matrix.api.timeline.ReceiptType import io.element.android.libraries.matrix.api.timeline.ReceiptType
import io.element.android.libraries.preferences.api.store.SessionPreferencesStore import io.element.android.libraries.preferences.api.store.SessionPreferencesStore
import io.element.android.libraries.push.api.notifications.NotificationCleaner import io.element.android.libraries.push.api.notifications.NotificationCleaner
@ -76,7 +76,7 @@ private const val SUBSCRIBE_TO_VISIBLE_ROOMS_DEBOUNCE_IN_MILLIS = 300L
class RoomListPresenter @Inject constructor( class RoomListPresenter @Inject constructor(
private val client: MatrixClient, private val client: MatrixClient,
private val networkMonitor: NetworkMonitor, private val syncService: SyncService,
private val snackbarDispatcher: SnackbarDispatcher, private val snackbarDispatcher: SnackbarDispatcher,
private val leaveRoomPresenter: Presenter<LeaveRoomState>, private val leaveRoomPresenter: Presenter<LeaveRoomState>,
private val roomListDataSource: RoomListDataSource, private val roomListDataSource: RoomListDataSource,
@ -98,7 +98,7 @@ class RoomListPresenter @Inject constructor(
val coroutineScope = rememberCoroutineScope() val coroutineScope = rememberCoroutineScope()
val leaveRoomState = leaveRoomPresenter.present() val leaveRoomState = leaveRoomPresenter.present()
val matrixUser = client.userProfile.collectAsState() val matrixUser = client.userProfile.collectAsState()
val networkConnectionStatus by networkMonitor.connectivity.collectAsState() val isOnline by syncService.isOnline().collectAsState()
val filtersState = filtersPresenter.present() val filtersState = filtersPresenter.present()
val searchState = searchPresenter.present() val searchState = searchPresenter.present()
val acceptDeclineInviteState = acceptDeclineInvitePresenter.present() val acceptDeclineInviteState = acceptDeclineInvitePresenter.present()
@ -158,7 +158,7 @@ class RoomListPresenter @Inject constructor(
matrixUser = matrixUser.value, matrixUser = matrixUser.value,
showAvatarIndicator = showAvatarIndicator, showAvatarIndicator = showAvatarIndicator,
snackbarMessage = snackbarMessage, snackbarMessage = snackbarMessage,
hasNetworkConnection = networkConnectionStatus == NetworkStatus.Online, hasNetworkConnection = isOnline,
contextMenu = contextMenu.value, contextMenu = contextMenu.value,
leaveRoomState = leaveRoomState, leaveRoomState = leaveRoomState,
filtersState = filtersState, filtersState = filtersState,

View file

@ -19,8 +19,6 @@ import io.element.android.features.leaveroom.api.LeaveRoomEvent
import io.element.android.features.leaveroom.api.LeaveRoomState import io.element.android.features.leaveroom.api.LeaveRoomState
import io.element.android.features.leaveroom.api.aLeaveRoomState import io.element.android.features.leaveroom.api.aLeaveRoomState
import io.element.android.features.logout.api.direct.aDirectLogoutState import io.element.android.features.logout.api.direct.aDirectLogoutState
import io.element.android.features.networkmonitor.api.NetworkMonitor
import io.element.android.features.networkmonitor.test.FakeNetworkMonitor
import io.element.android.features.roomlist.impl.datasource.RoomListDataSource import io.element.android.features.roomlist.impl.datasource.RoomListDataSource
import io.element.android.features.roomlist.impl.datasource.aRoomListRoomSummaryFactory import io.element.android.features.roomlist.impl.datasource.aRoomListRoomSummaryFactory
import io.element.android.features.roomlist.impl.filters.RoomListFiltersState import io.element.android.features.roomlist.impl.filters.RoomListFiltersState
@ -48,6 +46,7 @@ import io.element.android.libraries.matrix.api.encryption.RecoveryState
import io.element.android.libraries.matrix.api.room.CurrentUserMembership import io.element.android.libraries.matrix.api.room.CurrentUserMembership
import io.element.android.libraries.matrix.api.room.RoomNotificationMode import io.element.android.libraries.matrix.api.room.RoomNotificationMode
import io.element.android.libraries.matrix.api.roomlist.RoomList import io.element.android.libraries.matrix.api.roomlist.RoomList
import io.element.android.libraries.matrix.api.sync.SyncService
import io.element.android.libraries.matrix.api.sync.SyncState import io.element.android.libraries.matrix.api.sync.SyncState
import io.element.android.libraries.matrix.api.timeline.ReceiptType import io.element.android.libraries.matrix.api.timeline.ReceiptType
import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.matrix.api.user.MatrixUser
@ -84,7 +83,6 @@ import io.element.android.tests.testutils.test
import io.element.android.tests.testutils.testCoroutineDispatchers import io.element.android.tests.testutils.testCoroutineDispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.advanceTimeBy import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
@ -202,7 +200,7 @@ class RoomListPresenterTest {
val encryptionService = FakeEncryptionService().apply { val encryptionService = FakeEncryptionService().apply {
emitRecoveryState(RecoveryState.INCOMPLETE) emitRecoveryState(RecoveryState.INCOMPLETE)
} }
val syncService = FakeSyncService(MutableStateFlow(SyncState.Running)) val syncService = FakeSyncService(initialSyncState = SyncState.Running)
val presenter = createRoomListPresenter( val presenter = createRoomListPresenter(
client = FakeMatrixClient(roomListService = roomListService, encryptionService = encryptionService, syncService = syncService), client = FakeMatrixClient(roomListService = roomListService, encryptionService = encryptionService, syncService = syncService),
) )
@ -233,7 +231,7 @@ class RoomListPresenterTest {
sessionVerificationService = FakeSessionVerificationService().apply { sessionVerificationService = FakeSessionVerificationService().apply {
emitNeedsSessionVerification(false) emitNeedsSessionVerification(false)
}, },
syncService = FakeSyncService(MutableStateFlow(SyncState.Running)), syncService = FakeSyncService(initialSyncState = SyncState.Running),
) )
val presenter = createRoomListPresenter( val presenter = createRoomListPresenter(
client = matrixClient, client = matrixClient,
@ -633,7 +631,7 @@ class RoomListPresenterTest {
private fun TestScope.createRoomListPresenter( private fun TestScope.createRoomListPresenter(
client: MatrixClient = FakeMatrixClient(), client: MatrixClient = FakeMatrixClient(),
networkMonitor: NetworkMonitor = FakeNetworkMonitor(), syncService: SyncService = FakeSyncService(),
snackbarDispatcher: SnackbarDispatcher = SnackbarDispatcher(), snackbarDispatcher: SnackbarDispatcher = SnackbarDispatcher(),
leaveRoomState: LeaveRoomState = aLeaveRoomState(), leaveRoomState: LeaveRoomState = aLeaveRoomState(),
dateFormatter: DateFormatter = FakeDateFormatter(), dateFormatter: DateFormatter = FakeDateFormatter(),
@ -647,7 +645,7 @@ class RoomListPresenterTest {
notificationCleaner: NotificationCleaner = FakeNotificationCleaner(), notificationCleaner: NotificationCleaner = FakeNotificationCleaner(),
) = RoomListPresenter( ) = RoomListPresenter(
client = client, client = client,
networkMonitor = networkMonitor, syncService = syncService,
snackbarDispatcher = snackbarDispatcher, snackbarDispatcher = snackbarDispatcher,
leaveRoomPresenter = { leaveRoomState }, leaveRoomPresenter = { leaveRoomState },
roomListDataSource = RoomListDataSource( roomListDataSource = RoomListDataSource(

View file

@ -7,6 +7,7 @@
package io.element.android.libraries.matrix.api.sync package io.element.android.libraries.matrix.api.sync
import io.element.android.libraries.core.coroutine.mapState
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
interface SyncService { interface SyncService {
@ -25,3 +26,5 @@ interface SyncService {
*/ */
val syncState: StateFlow<SyncState> val syncState: StateFlow<SyncState>
} }
fun SyncService.isOnline(): StateFlow<Boolean> = syncState.mapState { it != SyncState.Offline }

View file

@ -74,6 +74,7 @@ class RustMatrixClientFactory @Inject constructor(
val syncService = client.syncService() val syncService = client.syncService()
.withUtdHook(UtdTracker(analyticsService)) .withUtdHook(UtdTracker(analyticsService))
.withOfflineMode()
.finish() .finish()
return RustMatrixClient( return RustMatrixClient(

View file

@ -19,6 +19,7 @@ import io.element.android.libraries.matrix.api.notificationsettings.Notification
import io.element.android.libraries.matrix.api.room.RoomMembershipObserver import io.element.android.libraries.matrix.api.room.RoomMembershipObserver
import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryService import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryService
import io.element.android.libraries.matrix.api.roomlist.RoomListService import io.element.android.libraries.matrix.api.roomlist.RoomListService
import io.element.android.libraries.matrix.api.sync.SyncService
import io.element.android.libraries.matrix.api.verification.SessionVerificationService import io.element.android.libraries.matrix.api.verification.SessionVerificationService
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@ -45,6 +46,11 @@ object SessionMatrixModule {
return matrixClient.roomListService return matrixClient.roomListService
} }
@Provides
fun providesSyncService(matrixClient: MatrixClient): SyncService {
return matrixClient.syncService()
}
@Provides @Provides
fun providesEncryptionService(matrixClient: MatrixClient): EncryptionService { fun providesEncryptionService(matrixClient: MatrixClient): EncryptionService {
return matrixClient.encryptionService() return matrixClient.encryptionService()

View file

@ -14,5 +14,6 @@ import org.matrix.rustcomponents.sdk.UnableToDecryptDelegate
class FakeRustSyncServiceBuilder : SyncServiceBuilder(NoPointer) { class FakeRustSyncServiceBuilder : SyncServiceBuilder(NoPointer) {
override suspend fun withUtdHook(delegate: UnableToDecryptDelegate): SyncServiceBuilder = this override suspend fun withUtdHook(delegate: UnableToDecryptDelegate): SyncServiceBuilder = this
override fun withOfflineMode(): SyncServiceBuilder = this
override suspend fun finish(): SyncService = FakeRustSyncService() override suspend fun finish(): SyncService = FakeRustSyncService()
} }

View file

@ -13,8 +13,10 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
class FakeSyncService( class FakeSyncService(
syncStateFlow: MutableStateFlow<SyncState> = MutableStateFlow(SyncState.Idle) initialSyncState: SyncState = SyncState.Idle,
) : SyncService { ) : SyncService {
private val syncStateFlow: MutableStateFlow<SyncState> = MutableStateFlow(initialSyncState)
var startSyncLambda: () -> Result<Unit> = { Result.success(Unit) } var startSyncLambda: () -> Result<Unit> = { Result.success(Unit) }
override suspend fun startSync(): Result<Unit> { override suspend fun startSync(): Result<Unit> {
return startSyncLambda() return startSyncLambda()
@ -26,4 +28,8 @@ class FakeSyncService(
} }
override val syncState: StateFlow<SyncState> = syncStateFlow override val syncState: StateFlow<SyncState> = syncStateFlow
suspend fun emitSyncState(syncState: SyncState) {
syncStateFlow.emit(syncState)
}
} }

View file

@ -36,7 +36,6 @@ import org.junit.Test
class SyncOnNotifiableEventTest { class SyncOnNotifiableEventTest {
private val timelineItems = MutableStateFlow<List<MatrixTimelineItem>>(emptyList()) private val timelineItems = MutableStateFlow<List<MatrixTimelineItem>>(emptyList())
private val syncStateFlow = MutableStateFlow(SyncState.Idle)
private val startSyncLambda = lambdaRecorder<Result<Unit>> { Result.success(Unit) } private val startSyncLambda = lambdaRecorder<Result<Unit>> { Result.success(Unit) }
private val stopSyncLambda = lambdaRecorder<Result<Unit>> { Result.success(Unit) } private val stopSyncLambda = lambdaRecorder<Result<Unit>> { Result.success(Unit) }
private val subscribeToSyncLambda = lambdaRecorder<Unit> { } private val subscribeToSyncLambda = lambdaRecorder<Unit> { }
@ -49,7 +48,7 @@ class SyncOnNotifiableEventTest {
liveTimeline = liveTimeline, liveTimeline = liveTimeline,
subscribeToSyncLambda = subscribeToSyncLambda subscribeToSyncLambda = subscribeToSyncLambda
) )
private val syncService = FakeSyncService(syncStateFlow).also { private val syncService = FakeSyncService(SyncState.Idle).also {
it.startSyncLambda = startSyncLambda it.startSyncLambda = startSyncLambda
it.stopSyncLambda = stopSyncLambda it.stopSyncLambda = stopSyncLambda
} }
@ -115,7 +114,7 @@ class SyncOnNotifiableEventTest {
timelineItems.emit( timelineItems.emit(
listOf(MatrixTimelineItem.Event(A_UNIQUE_ID, anEventTimelineItem())) listOf(MatrixTimelineItem.Event(A_UNIQUE_ID, anEventTimelineItem()))
) )
syncStateFlow.emit(SyncState.Running) syncService.emitSyncState(SyncState.Running)
sut(notifiableEvent) sut(notifiableEvent)
assert(startSyncLambda).isCalledOnce() assert(startSyncLambda).isCalledOnce()