Enable Offline mode of the SyncService, so that the sync starts automatically when the network is back.

Also rely on the sync state to render the "Offline" banner.
This commit is contained in:
Benoit Marty 2025-02-03 17:47:35 +01:00
parent f4afda119b
commit f84aa03605
20 changed files with 91 additions and 74 deletions

View file

@ -19,8 +19,6 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import im.vector.app.features.analytics.plan.CryptoSessionStateChange
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.Presenter
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.roomlist.RoomListService
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.isConnected
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus
import io.element.android.libraries.preferences.api.store.EnableNativeSlidingSyncUseCase
@ -46,7 +46,7 @@ private val pusherTag = LoggerTag("Pusher", LoggerTag.PushLoggerTag)
class LoggedInPresenter @Inject constructor(
private val matrixClient: MatrixClient,
private val networkMonitor: NetworkMonitor,
private val syncService: SyncService,
private val pushService: PushService,
private val sessionVerificationService: SessionVerificationService,
private val analyticsService: AnalyticsService,
@ -76,10 +76,10 @@ class LoggedInPresenter @Inject constructor(
.launchIn(this)
}
val syncIndicator by matrixClient.roomListService.syncIndicator.collectAsState()
val networkStatus by networkMonitor.connectivity.collectAsState()
val syncState by syncService.syncState.collectAsState()
val showSyncSpinner by remember {
derivedStateOf {
networkStatus == NetworkStatus.Online && syncIndicator == RoomListService.SyncIndicator.Show
syncState.isConnected() && syncIndicator == RoomListService.SyncIndicator.Show
}
}
var forceNativeSlidingSyncMigration by remember { mutableStateOf(false) }

View file

@ -8,11 +8,12 @@
package io.element.android.appnav.loggedin
import androidx.annotation.VisibleForTesting
import io.element.android.features.networkmonitor.api.NetworkMonitor
import io.element.android.features.networkmonitor.api.NetworkStatus
import io.element.android.libraries.di.SessionScope
import io.element.android.libraries.di.SingleIn
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.FlowPreview
import kotlinx.coroutines.flow.combine
@ -27,7 +28,7 @@ const val SEND_QUEUES_RETRY_DELAY_MILLIS = 500L
@SingleIn(SessionScope::class)
class SendQueues @Inject constructor(
private val matrixClient: MatrixClient,
private val networkMonitor: NetworkMonitor,
private val syncService: SyncService,
) {
/**
* Launches the send queues retry mechanism in the given [coroutineScope].
@ -36,12 +37,12 @@ class SendQueues @Inject constructor(
@OptIn(FlowPreview::class)
fun launchIn(coroutineScope: CoroutineScope) {
combine(
networkMonitor.connectivity,
syncService.syncState,
matrixClient.sendQueueDisabledFlow(),
) { networkStatus, _ -> networkStatus }
) { syncState, _ -> syncState }
.debounce(SEND_QUEUES_RETRY_DELAY_MILLIS)
.onEach { networkStatus ->
if (networkStatus == NetworkStatus.Online) {
.onEach { syncState ->
if (syncState == SyncState.Running) {
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.LoadingRoomState
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.roomdirectory.api.RoomDescription
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.RoomMembershipObserver
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.isConnected
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.first
@ -68,7 +68,7 @@ class RoomFlowNode @AssistedInject constructor(
private val client: MatrixClient,
private val joinRoomEntryPoint: JoinRoomEntryPoint,
private val roomAliasResolverEntryPoint: RoomAliasResolverEntryPoint,
private val networkMonitor: NetworkMonitor,
private val syncService: SyncService,
private val membershipObserver: RoomMembershipObserver,
) : BaseFlowNode<RoomFlowNode.NavTarget>(
backstack = BackStack(
@ -211,10 +211,10 @@ class RoomFlowNode @AssistedInject constructor(
}
private fun loadingNode(buildContext: BuildContext) = node(buildContext) { modifier ->
val networkStatus by networkMonitor.connectivity.collectAsState()
val syncState by syncService.syncState.collectAsState()
LoadingRoomNodeView(
state = LoadingRoomState.Loading,
hasNetworkConnection = networkStatus == NetworkStatus.Online,
hasNetworkConnection = syncState.isConnected(),
onBackClick = { navigateUp() },
modifier = modifier,
)

View file

@ -28,8 +28,6 @@ import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import io.element.android.anvilannotations.ContributesNode
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.BaseFlowNode
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.di.SessionScope
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.isConnected
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
@ -48,7 +48,7 @@ class JoinedRoomFlowNode @AssistedInject constructor(
@Assisted val buildContext: BuildContext,
@Assisted plugins: List<Plugin>,
loadingRoomStateFlowFactory: LoadingRoomStateFlowFactory,
private val networkMonitor: NetworkMonitor,
private val syncService: SyncService,
) :
BaseFlowNode<JoinedRoomFlowNode.NavTarget>(
backstack = BackStack(
@ -114,10 +114,10 @@ class JoinedRoomFlowNode @AssistedInject constructor(
private fun loadingNode(buildContext: BuildContext, onBackClick: () -> Unit) = node(buildContext) { modifier ->
val loadingRoomState by loadingRoomStateStateFlow.collectAsState()
val networkStatus by networkMonitor.connectivity.collectAsState()
val syncState by syncService.syncState.collectAsState()
LoadingRoomNodeView(
state = loadingRoomState,
hasNetworkConnection = networkStatus == NetworkStatus.Online,
hasNetworkConnection = syncState.isConnected(),
modifier = modifier,
onBackClick = onBackClick
)

View file

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

View file

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