diff --git a/appnav/build.gradle.kts b/appnav/build.gradle.kts index 6abc3c656b..cffd318fb1 100644 --- a/appnav/build.gradle.kts +++ b/appnav/build.gradle.kts @@ -65,6 +65,8 @@ dependencies { testImplementation(libs.test.truth) testImplementation(libs.test.turbine) testImplementation(projects.libraries.matrix.test) + testImplementation(projects.features.networkmonitor.test) + testImplementation(projects.tests.testutils) testImplementation(projects.features.rageshake.test) testImplementation(projects.features.rageshake.impl) testImplementation(projects.services.appnavstate.test) diff --git a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt index fe4e31c70d..ec8beb16c1 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt @@ -69,10 +69,13 @@ import io.element.android.libraries.matrix.ui.di.MatrixUIBindings import io.element.android.libraries.push.api.notifications.NotificationDrawerManager import io.element.android.services.appnavstate.api.AppNavigationStateService import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.launch import kotlinx.parcelize.Parcelize +import timber.log.Timber @ContributesNode(AppScope::class) class LoggedInFlowNode @AssistedInject constructor( @@ -123,7 +126,6 @@ class LoggedInFlowNode @AssistedInject constructor( override fun onBuilt() { super.onBuilt() - lifecycle.subscribe( onCreate = { plugins().forEach { it.onFlowCreated(id, inputs.matrixClient) } @@ -138,12 +140,8 @@ class LoggedInFlowNode @AssistedInject constructor( backstack.push(NavTarget.Ftue) } }, - onStart = { - lifecycleScope.launch { - syncService.startSync() - } - }, onStop = { + //Counterpart startSync is done in observeSyncStateAndNetworkStatus method. syncService.stopSync() }, onDestroy = { @@ -153,22 +151,24 @@ class LoggedInFlowNode @AssistedInject constructor( loggedInFlowProcessor.stopObserving() } ) - observeSyncStateAndNetworkStatus() } + @OptIn(FlowPreview::class) private fun observeSyncStateAndNetworkStatus() { lifecycleScope.launch { - repeatOnLifecycle(Lifecycle.State.RESUMED) { + repeatOnLifecycle(Lifecycle.State.STARTED) { combine( - syncService.syncState, + // small debounce to avoid spamming startSync when the state is changing quickly in case of error. + syncService.syncState.debounce(100), networkMonitor.connectivity ) { syncState, networkStatus -> - syncState == SyncState.Error && networkStatus == NetworkStatus.Online + Pair(syncState, networkStatus) } .distinctUntilChanged() - .collect { restartSync -> - if (restartSync) { + .collect { (syncState, networkStatus) -> + Timber.d("Sync state: $syncState, network status: $networkStatus") + if (syncState != SyncState.Running && networkStatus == NetworkStatus.Online) { syncService.startSync() } } @@ -351,3 +351,4 @@ class LoggedInFlowNode @AssistedInject constructor( backstack.push(NavTarget.InviteList) } } + diff --git a/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInPresenter.kt b/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInPresenter.kt index 8910cc3976..6d386a17e5 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInPresenter.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInPresenter.kt @@ -21,16 +21,27 @@ import android.os.Build import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import io.element.android.features.networkmonitor.api.NetworkMonitor +import io.element.android.features.networkmonitor.api.NetworkStatus import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.matrix.api.MatrixClient +import io.element.android.libraries.matrix.api.roomlist.RoomListService import io.element.android.libraries.permissions.api.PermissionsPresenter import io.element.android.libraries.permissions.noop.NoopPermissionsPresenter import io.element.android.libraries.push.api.PushService +import kotlinx.coroutines.delay import javax.inject.Inject +private const val DELAY_BEFORE_SHOWING_SYNC_SPINNER_IN_MILLIS = 1500L + class LoggedInPresenter @Inject constructor( private val matrixClient: MatrixClient, private val permissionsPresenterFactory: PermissionsPresenter.Factory, + private val networkMonitor: NetworkMonitor, private val pushService: PushService, ) : Presenter { @@ -53,18 +64,25 @@ class LoggedInPresenter @Inject constructor( pushService.registerWith(matrixClient, pushProvider, distributor) } - val syncState = matrixClient.syncService().syncState.collectAsState() + val roomListState by matrixClient.roomListService.state.collectAsState() + val networkStatus by networkMonitor.connectivity.collectAsState() val permissionsState = postNotificationPermissionsPresenter.present() - - // fun handleEvents(event: LoggedInEvents) { - // when (event) { - // } - // } - + var showSyncSpinner by remember { + mutableStateOf(false) + } + LaunchedEffect(roomListState, networkStatus) { + showSyncSpinner = when { + networkStatus == NetworkStatus.Offline -> false + roomListState == RoomListService.State.Running -> false + else -> { + delay(DELAY_BEFORE_SHOWING_SYNC_SPINNER_IN_MILLIS) + true + } + } + } return LoggedInState( - syncState = syncState.value, + showSyncSpinner = showSyncSpinner, permissionsState = permissionsState, - // eventSink = ::handleEvents ) } } diff --git a/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInState.kt b/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInState.kt index 075242cddb..bb06952a50 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInState.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInState.kt @@ -16,11 +16,9 @@ package io.element.android.appnav.loggedin -import io.element.android.libraries.matrix.api.sync.SyncState import io.element.android.libraries.permissions.api.PermissionsState data class LoggedInState( - val syncState: SyncState, + val showSyncSpinner: Boolean, val permissionsState: PermissionsState, - // val eventSink: (LoggedInEvents) -> Unit ) diff --git a/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInStateProvider.kt b/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInStateProvider.kt index e8a8a4762c..3cfb03f123 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInStateProvider.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInStateProvider.kt @@ -17,22 +17,20 @@ package io.element.android.appnav.loggedin import androidx.compose.ui.tooling.preview.PreviewParameterProvider -import io.element.android.libraries.matrix.api.sync.SyncState import io.element.android.libraries.permissions.api.createDummyPostNotificationPermissionsState open class LoggedInStateProvider : PreviewParameterProvider { override val values: Sequence get() = sequenceOf( - aLoggedInState(), - aLoggedInState(syncState = SyncState.Idle), + aLoggedInState(false), + aLoggedInState(true), // Add other state here ) } fun aLoggedInState( - syncState: SyncState = SyncState.Running, + showSyncSpinner: Boolean = true, ) = LoggedInState( - syncState = syncState, + showSyncSpinner = showSyncSpinner, permissionsState = createDummyPostNotificationPermissionsState(), - // eventSink = {} ) diff --git a/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInView.kt b/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInView.kt index 964cb447aa..0ade93a795 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInView.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInView.kt @@ -47,7 +47,7 @@ fun LoggedInView( modifier = Modifier .padding(top = 8.dp) .align(Alignment.TopCenter), - syncState = state.syncState, + isVisible = state.showSyncSpinner, ) PermissionsView( state = state.permissionsState, diff --git a/appnav/src/main/kotlin/io/element/android/appnav/loggedin/SyncStateView.kt b/appnav/src/main/kotlin/io/element/android/appnav/loggedin/SyncStateView.kt index bdec2b528f..6d045a431a 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/loggedin/SyncStateView.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/loggedin/SyncStateView.kt @@ -38,19 +38,18 @@ import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator import io.element.android.libraries.designsystem.theme.components.Surface import io.element.android.libraries.designsystem.theme.components.Text -import io.element.android.libraries.matrix.api.sync.SyncState import io.element.android.libraries.theme.ElementTheme import io.element.android.libraries.ui.strings.CommonStrings @Composable fun SyncStateView( - syncState: SyncState, + isVisible: Boolean, modifier: Modifier = Modifier ) { val animationSpec = spring(stiffness = 500F) AnimatedVisibility( modifier = modifier, - visible = syncState.mustBeVisible(), + visible = isVisible, enter = fadeIn(animationSpec = animationSpec), exit = fadeOut(animationSpec = animationSpec), ) { @@ -60,15 +59,15 @@ fun SyncStateView( ) { Row( modifier = Modifier - .background(color = ElementTheme.colors.bgSubtleSecondary) - .padding(horizontal = 24.dp, vertical = 10.dp), + .background(color = ElementTheme.colors.bgSubtleSecondary) + .padding(horizontal = 24.dp, vertical = 10.dp), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(10.dp) ) { CircularProgressIndicator( modifier = Modifier - .progressSemantics() - .size(12.dp), + .progressSemantics() + .size(12.dp), color = ElementTheme.colors.textPrimary, strokeWidth = 1.5.dp, ) @@ -82,20 +81,13 @@ fun SyncStateView( } } -private fun SyncState.mustBeVisible() = when (this) { - SyncState.Idle -> true /* Cold start of the app */ - SyncState.Running -> false - SyncState.Error -> false /* In this case, the network error banner can be displayed */ - SyncState.Terminated -> true /* The app is resumed and the sync is started again */ -} - @DayNightPreviews @Composable internal fun SyncStateViewPreview() = ElementPreview { // Add a box to see the shadow Box(modifier = Modifier.padding(24.dp)) { SyncStateView( - syncState = SyncState.Idle + isVisible = true ) } } diff --git a/appnav/src/test/kotlin/io/element/android/appnav/loggedin/LoggedInPresenterTest.kt b/appnav/src/test/kotlin/io/element/android/appnav/loggedin/LoggedInPresenterTest.kt index a811d0283d..4abc89e7ee 100644 --- a/appnav/src/test/kotlin/io/element/android/appnav/loggedin/LoggedInPresenterTest.kt +++ b/appnav/src/test/kotlin/io/element/android/appnav/loggedin/LoggedInPresenterTest.kt @@ -20,13 +20,18 @@ import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat +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.roomlist.RoomListService import io.element.android.libraries.matrix.test.FakeMatrixClient +import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService import io.element.android.libraries.permissions.api.PermissionsPresenter import io.element.android.libraries.permissions.noop.NoopPermissionsPresenter import io.element.android.libraries.push.api.PushService import io.element.android.libraries.pushproviders.api.Distributor import io.element.android.libraries.pushproviders.api.PushProvider +import io.element.android.tests.testutils.consumeItemsUntilPredicate import kotlinx.coroutines.test.runTest import org.junit.Test @@ -42,14 +47,33 @@ class LoggedInPresenterTest { } } - private fun createPresenter(): LoggedInPresenter { + @Test + fun `present - show sync spinner`() = runTest { + val roomListService = FakeRoomListService() + val presenter = createPresenter(roomListService, NetworkStatus.Online) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + assertThat(initialState.showSyncSpinner).isFalse() + consumeItemsUntilPredicate { it.showSyncSpinner } + roomListService.postState(RoomListService.State.Running) + consumeItemsUntilPredicate { !it.showSyncSpinner } + } + } + + private fun createPresenter( + roomListService: RoomListService = FakeRoomListService(), + networkStatus: NetworkStatus = NetworkStatus.Offline + ): LoggedInPresenter { return LoggedInPresenter( - matrixClient = FakeMatrixClient(), + matrixClient = FakeMatrixClient(roomListService = roomListService), permissionsPresenterFactory = object : PermissionsPresenter.Factory { override fun create(permission: String): PermissionsPresenter { return NoopPermissionsPresenter() } }, + networkMonitor = FakeNetworkMonitor(networkStatus), pushService = object : PushService { override fun notificationStyleChanged() { } diff --git a/appnav/src/test/kotlin/io/element/android/appnav/room/LoadingRoomStateFlowFactoryTest.kt b/appnav/src/test/kotlin/io/element/android/appnav/room/LoadingRoomStateFlowFactoryTest.kt index 17b6f6deb9..f56367e5f8 100644 --- a/appnav/src/test/kotlin/io/element/android/appnav/room/LoadingRoomStateFlowFactoryTest.kt +++ b/appnav/src/test/kotlin/io/element/android/appnav/room/LoadingRoomStateFlowFactoryTest.kt @@ -18,12 +18,12 @@ package io.element.android.appnav.room import app.cash.turbine.test import com.google.common.truth.Truth -import io.element.android.libraries.matrix.api.room.RoomSummaryDataSource +import io.element.android.libraries.matrix.api.roomlist.RoomList import io.element.android.libraries.matrix.test.A_ROOM_ID 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.room.FakeMatrixRoom -import io.element.android.libraries.matrix.test.room.FakeRoomSummaryDataSource +import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService import kotlinx.coroutines.test.runTest import org.junit.Test @@ -47,29 +47,29 @@ class LoadingRoomStateFlowFactoryTest { @Test fun `flow should emit Loading and then Loaded when there is a room in cache after SS is loaded`() = runTest { val room = FakeMatrixRoom(sessionId= A_SESSION_ID, roomId = A_ROOM_ID) - val roomSummaryDataSource = FakeRoomSummaryDataSource() - val matrixClient = FakeMatrixClient(A_SESSION_ID, roomSummaryDataSource = roomSummaryDataSource) + val roomListService = FakeRoomListService() + val matrixClient = FakeMatrixClient(A_SESSION_ID, roomListService = roomListService) val flowFactory = LoadingRoomStateFlowFactory(matrixClient) flowFactory .create(this, A_ROOM_ID) .test { Truth.assertThat(awaitItem()).isEqualTo(LoadingRoomState.Loading) matrixClient.givenGetRoomResult(A_ROOM_ID, room) - roomSummaryDataSource.postLoadingState(RoomSummaryDataSource.LoadingState.Loaded(1)) + roomListService.postAllRoomsLoadingState(RoomList.LoadingState.Loaded(1)) Truth.assertThat(awaitItem()).isEqualTo(LoadingRoomState.Loaded(room)) } } @Test fun `flow should emit Loading and then Error when there is no room in cache after SS is loaded`() = runTest { - val roomSummaryDataSource = FakeRoomSummaryDataSource() - val matrixClient = FakeMatrixClient(A_SESSION_ID, roomSummaryDataSource = roomSummaryDataSource) + val roomListService = FakeRoomListService() + val matrixClient = FakeMatrixClient(A_SESSION_ID, roomListService = roomListService) val flowFactory = LoadingRoomStateFlowFactory(matrixClient) flowFactory .create(this, A_ROOM_ID) .test { Truth.assertThat(awaitItem()).isEqualTo(LoadingRoomState.Loading) - roomSummaryDataSource.postLoadingState(RoomSummaryDataSource.LoadingState.Loaded(1)) + roomListService.postAllRoomsLoadingState(RoomList.LoadingState.Loaded(1)) Truth.assertThat(awaitItem()).isEqualTo(LoadingRoomState.Error) } } diff --git a/features/invitelist/impl/src/main/kotlin/io/element/android/features/invitelist/impl/InviteListPresenter.kt b/features/invitelist/impl/src/main/kotlin/io/element/android/features/invitelist/impl/InviteListPresenter.kt index bba497e2fd..8c43b815ae 100644 --- a/features/invitelist/impl/src/main/kotlin/io/element/android/features/invitelist/impl/InviteListPresenter.kt +++ b/features/invitelist/impl/src/main/kotlin/io/element/android/features/invitelist/impl/InviteListPresenter.kt @@ -36,7 +36,7 @@ import io.element.android.libraries.designsystem.components.avatar.AvatarData import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.core.RoomId -import io.element.android.libraries.matrix.api.room.RoomSummary +import io.element.android.libraries.matrix.api.roomlist.RoomSummary import io.element.android.libraries.push.api.notifications.NotificationDrawerManager import io.element.android.services.analytics.api.AnalyticsService import io.element.android.services.analytics.api.extensions.toAnalyticsJoinedRoom @@ -56,8 +56,9 @@ class InviteListPresenter @Inject constructor( @Composable override fun present(): InviteListState { val invites by client - .roomSummaryDataSource - .inviteRooms() + .roomListService + .invites() + .summaries .collectAsState() var seenInvites by remember { mutableStateOf>(emptySet()) } diff --git a/features/invitelist/impl/src/test/kotlin/io/element/android/features/invitelist/impl/InviteListPresenterTests.kt b/features/invitelist/impl/src/test/kotlin/io/element/android/features/invitelist/impl/InviteListPresenterTests.kt index 03e0f46de8..f3eef2784c 100644 --- a/features/invitelist/impl/src/test/kotlin/io/element/android/features/invitelist/impl/InviteListPresenterTests.kt +++ b/features/invitelist/impl/src/test/kotlin/io/element/android/features/invitelist/impl/InviteListPresenterTests.kt @@ -30,8 +30,8 @@ import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.room.RoomMember import io.element.android.libraries.matrix.api.room.RoomMembershipState -import io.element.android.libraries.matrix.api.room.RoomSummary -import io.element.android.libraries.matrix.api.room.RoomSummaryDetails +import io.element.android.libraries.matrix.api.roomlist.RoomSummary +import io.element.android.libraries.matrix.api.roomlist.RoomSummaryDetails import io.element.android.libraries.matrix.test.AN_AVATAR_URL import io.element.android.libraries.matrix.test.A_ROOM_ID import io.element.android.libraries.matrix.test.A_ROOM_ID_2 @@ -40,7 +40,7 @@ import io.element.android.libraries.matrix.test.A_USER_ID import io.element.android.libraries.matrix.test.A_USER_NAME 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.FakeRoomSummaryDataSource +import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService import io.element.android.libraries.push.api.notifications.NotificationDrawerManager import io.element.android.libraries.push.test.notifications.FakeNotificationDrawerManager import io.element.android.services.analytics.api.AnalyticsService @@ -51,9 +51,9 @@ class InviteListPresenterTests { @Test fun `present - starts empty, adds invites when received`() = runTest { - val roomSummaryDataSource = FakeRoomSummaryDataSource() + val roomListService = FakeRoomListService() val presenter = createPresenter( - FakeMatrixClient(roomSummaryDataSource = roomSummaryDataSource) + FakeMatrixClient(roomListService = roomListService) ) moleculeFlow(RecompositionMode.Immediate) { presenter.present() @@ -61,7 +61,7 @@ class InviteListPresenterTests { val initialState = awaitItem() Truth.assertThat(initialState.inviteList).isEmpty() - roomSummaryDataSource.postInviteRooms(listOf(aRoomSummary())) + roomListService.postInviteRooms(listOf(aRoomSummary())) val withInviteState = awaitItem() Truth.assertThat(withInviteState.inviteList.size).isEqualTo(1) @@ -72,9 +72,9 @@ class InviteListPresenterTests { @Test fun `present - uses user ID and avatar for direct invites`() = runTest { - val roomSummaryDataSource = FakeRoomSummaryDataSource().withDirectChatInvitation() + val roomListService = FakeRoomListService().withDirectChatInvitation() val presenter = createPresenter( - FakeMatrixClient(roomSummaryDataSource = roomSummaryDataSource) + FakeMatrixClient(roomListService = roomListService) ) moleculeFlow(RecompositionMode.Immediate) { presenter.present() @@ -98,9 +98,9 @@ class InviteListPresenterTests { @Test fun `present - includes sender details for room invites`() = runTest { - val roomSummaryDataSource = FakeRoomSummaryDataSource().withRoomInvitation() + val roomListService = FakeRoomListService().withRoomInvitation() val presenter = createPresenter( - FakeMatrixClient(roomSummaryDataSource = roomSummaryDataSource) + FakeMatrixClient(roomListService = roomListService) ) moleculeFlow(RecompositionMode.Immediate) { presenter.present() @@ -122,10 +122,10 @@ class InviteListPresenterTests { @Test fun `present - shows confirm dialog for declining direct chat invites`() = runTest { - val roomSummaryDataSource = FakeRoomSummaryDataSource().withDirectChatInvitation() + val roomListService = FakeRoomListService().withDirectChatInvitation() val presenter = InviteListPresenter( FakeMatrixClient( - roomSummaryDataSource = roomSummaryDataSource, + roomListService = roomListService, ), FakeSeenInvitesStore(), FakeAnalyticsService(), @@ -148,9 +148,9 @@ class InviteListPresenterTests { @Test fun `present - shows confirm dialog for declining room invites`() = runTest { - val roomSummaryDataSource = FakeRoomSummaryDataSource().withRoomInvitation() + val roomListService = FakeRoomListService().withRoomInvitation() val presenter = createPresenter( - FakeMatrixClient(roomSummaryDataSource = roomSummaryDataSource) + FakeMatrixClient(roomListService = roomListService) ) moleculeFlow(RecompositionMode.Immediate) { presenter.present() @@ -169,9 +169,9 @@ class InviteListPresenterTests { @Test fun `present - hides confirm dialog when cancelling`() = runTest { - val roomSummaryDataSource = FakeRoomSummaryDataSource().withRoomInvitation() + val roomListService = FakeRoomListService().withRoomInvitation() val presenter = createPresenter( - FakeMatrixClient(roomSummaryDataSource = roomSummaryDataSource) + FakeMatrixClient(roomListService = roomListService) ) moleculeFlow(RecompositionMode.Immediate) { presenter.present() @@ -190,10 +190,10 @@ class InviteListPresenterTests { @Test fun `present - declines invite after confirming`() = runTest { - val roomSummaryDataSource = FakeRoomSummaryDataSource().withRoomInvitation() + val roomListService = FakeRoomListService().withRoomInvitation() val fakeNotificationDrawerManager = FakeNotificationDrawerManager() val client = FakeMatrixClient( - roomSummaryDataSource = roomSummaryDataSource, + roomListService = roomListService, ) val room = FakeMatrixRoom() val presenter = createPresenter(client = client, notificationDrawerManager = fakeNotificationDrawerManager) @@ -217,9 +217,9 @@ class InviteListPresenterTests { @Test fun `present - declines invite after confirming and sets state on error`() = runTest { - val roomSummaryDataSource = FakeRoomSummaryDataSource().withRoomInvitation() + val roomListService = FakeRoomListService().withRoomInvitation() val client = FakeMatrixClient( - roomSummaryDataSource = roomSummaryDataSource, + roomListService = roomListService, ) val room = FakeMatrixRoom() val presenter = createPresenter(client) @@ -247,9 +247,9 @@ class InviteListPresenterTests { @Test fun `present - dismisses declining error state`() = runTest { - val roomSummaryDataSource = FakeRoomSummaryDataSource().withRoomInvitation() + val roomListService = FakeRoomListService().withRoomInvitation() val client = FakeMatrixClient( - roomSummaryDataSource = roomSummaryDataSource, + roomListService = roomListService, ) val room = FakeMatrixRoom() val presenter = createPresenter(client) @@ -279,10 +279,10 @@ class InviteListPresenterTests { @Test fun `present - accepts invites and sets state on success`() = runTest { - val roomSummaryDataSource = FakeRoomSummaryDataSource().withRoomInvitation() + val roomListService = FakeRoomListService().withRoomInvitation() val fakeNotificationDrawerManager = FakeNotificationDrawerManager() val client = FakeMatrixClient( - roomSummaryDataSource = roomSummaryDataSource, + roomListService = roomListService, ) val room = FakeMatrixRoom() val presenter = createPresenter(client = client, notificationDrawerManager = fakeNotificationDrawerManager) @@ -303,9 +303,9 @@ class InviteListPresenterTests { @Test fun `present - accepts invites and sets state on error`() = runTest { - val roomSummaryDataSource = FakeRoomSummaryDataSource().withRoomInvitation() + val roomListService = FakeRoomListService().withRoomInvitation() val client = FakeMatrixClient( - roomSummaryDataSource = roomSummaryDataSource, + roomListService = roomListService, ) val room = FakeMatrixRoom() val presenter = createPresenter(client) @@ -325,9 +325,9 @@ class InviteListPresenterTests { @Test fun `present - dismisses accepting error state`() = runTest { - val roomSummaryDataSource = FakeRoomSummaryDataSource().withRoomInvitation() + val roomListService = FakeRoomListService().withRoomInvitation() val client = FakeMatrixClient( - roomSummaryDataSource = roomSummaryDataSource, + roomListService = roomListService, ) val room = FakeMatrixRoom() val presenter = createPresenter(client) @@ -352,11 +352,11 @@ class InviteListPresenterTests { @Test fun `present - stores seen invites when received`() = runTest { - val roomSummaryDataSource = FakeRoomSummaryDataSource() + val roomListService = FakeRoomListService() val store = FakeSeenInvitesStore() val presenter = InviteListPresenter( FakeMatrixClient( - roomSummaryDataSource = roomSummaryDataSource, + roomListService = roomListService, ), store, FakeAnalyticsService(), @@ -368,19 +368,19 @@ class InviteListPresenterTests { awaitItem() // When one invite is received, that ID is saved - roomSummaryDataSource.postInviteRooms(listOf(aRoomSummary())) + roomListService.postInviteRooms(listOf(aRoomSummary())) awaitItem() Truth.assertThat(store.getProvidedRoomIds()).isEqualTo(setOf(A_ROOM_ID)) // When a second is added, both are saved - roomSummaryDataSource.postInviteRooms(listOf(aRoomSummary(), aRoomSummary(A_ROOM_ID_2))) + roomListService.postInviteRooms(listOf(aRoomSummary(), aRoomSummary(A_ROOM_ID_2))) awaitItem() Truth.assertThat(store.getProvidedRoomIds()).isEqualTo(setOf(A_ROOM_ID, A_ROOM_ID_2)) // When they're both dismissed, an empty set is saved - roomSummaryDataSource.postInviteRooms(listOf()) + roomListService.postInviteRooms(listOf()) awaitItem() Truth.assertThat(store.getProvidedRoomIds()).isEmpty() @@ -389,12 +389,12 @@ class InviteListPresenterTests { @Test fun `present - marks invite as new if they're unseen`() = runTest { - val roomSummaryDataSource = FakeRoomSummaryDataSource() + val roomListService = FakeRoomListService() val store = FakeSeenInvitesStore() store.publishRoomIds(setOf(A_ROOM_ID)) val presenter = InviteListPresenter( FakeMatrixClient( - roomSummaryDataSource = roomSummaryDataSource, + roomListService = roomListService, ), store, FakeAnalyticsService(), @@ -405,7 +405,7 @@ class InviteListPresenterTests { }.test { awaitItem() - roomSummaryDataSource.postInviteRooms(listOf(aRoomSummary(), aRoomSummary(A_ROOM_ID_2))) + roomListService.postInviteRooms(listOf(aRoomSummary(), aRoomSummary(A_ROOM_ID_2))) skipItems(1) val withInviteState = awaitItem() @@ -417,7 +417,7 @@ class InviteListPresenterTests { } } - private suspend fun FakeRoomSummaryDataSource.withRoomInvitation(): FakeRoomSummaryDataSource { + private suspend fun FakeRoomListService.withRoomInvitation(): FakeRoomListService { postInviteRooms( listOf( RoomSummary.Filled( @@ -446,7 +446,7 @@ class InviteListPresenterTests { return this } - private suspend fun FakeRoomSummaryDataSource.withDirectChatInvitation(): FakeRoomSummaryDataSource { + private suspend fun FakeRoomListService.withDirectChatInvitation(): FakeRoomListService { postInviteRooms( listOf( RoomSummary.Filled( diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/forward/ForwardMessagesEvents.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/forward/ForwardMessagesEvents.kt index 6b74918d71..0ae406efff 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/forward/ForwardMessagesEvents.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/forward/ForwardMessagesEvents.kt @@ -16,7 +16,7 @@ package io.element.android.features.messages.impl.forward -import io.element.android.libraries.matrix.api.room.RoomSummaryDetails +import io.element.android.libraries.matrix.api.roomlist.RoomSummaryDetails sealed interface ForwardMessagesEvents { data class SetSelectedRoom(val room: RoomSummaryDetails) : ForwardMessagesEvents diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/forward/ForwardMessagesPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/forward/ForwardMessagesPresenter.kt index e1d7ed3e7e..273ed5906d 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/forward/ForwardMessagesPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/forward/ForwardMessagesPresenter.kt @@ -35,8 +35,8 @@ import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.room.MatrixRoom -import io.element.android.libraries.matrix.api.room.RoomSummary -import io.element.android.libraries.matrix.api.room.RoomSummaryDetails +import io.element.android.libraries.matrix.api.roomlist.RoomSummary +import io.element.android.libraries.matrix.api.roomlist.RoomSummaryDetails import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toPersistentList @@ -65,7 +65,7 @@ class ForwardMessagesPresenter @AssistedInject constructor( var results: SearchBarResultState> by remember { mutableStateOf(SearchBarResultState.NotSearching()) } val forwardingActionState: MutableState>> = remember { mutableStateOf(Async.Uninitialized) } - val summaries by client.roomSummaryDataSource.allRooms().collectAsState() + val summaries by client.roomListService.allRooms().summaries.collectAsState() LaunchedEffect(query, summaries) { val filteredSummaries = summaries.filterIsInstance() diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/forward/ForwardMessagesState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/forward/ForwardMessagesState.kt index 7540766097..953a7897f6 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/forward/ForwardMessagesState.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/forward/ForwardMessagesState.kt @@ -18,7 +18,7 @@ package io.element.android.features.messages.impl.forward import io.element.android.libraries.designsystem.theme.components.SearchBarResultState import io.element.android.libraries.matrix.api.core.RoomId -import io.element.android.libraries.matrix.api.room.RoomSummaryDetails +import io.element.android.libraries.matrix.api.roomlist.RoomSummaryDetails import kotlinx.collections.immutable.ImmutableList data class ForwardMessagesState( diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/forward/ForwardMessagesStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/forward/ForwardMessagesStateProvider.kt index 75aacea616..56d7f63eb1 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/forward/ForwardMessagesStateProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/forward/ForwardMessagesStateProvider.kt @@ -20,7 +20,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider import io.element.android.libraries.designsystem.theme.components.SearchBarResultState import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.room.RoomMember -import io.element.android.libraries.matrix.api.room.RoomSummaryDetails +import io.element.android.libraries.matrix.api.roomlist.RoomSummaryDetails import io.element.android.libraries.matrix.api.room.message.RoomMessage import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/forward/ForwardMessagesView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/forward/ForwardMessagesView.kt index 0ff62d4788..0f00a6f090 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/forward/ForwardMessagesView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/forward/ForwardMessagesView.kt @@ -63,7 +63,7 @@ import io.element.android.libraries.designsystem.theme.components.TopAppBar import io.element.android.libraries.designsystem.theme.roomListRoomMessage import io.element.android.libraries.designsystem.theme.roomListRoomName import io.element.android.libraries.matrix.api.core.RoomId -import io.element.android.libraries.matrix.api.room.RoomSummaryDetails +import io.element.android.libraries.matrix.api.roomlist.RoomSummaryDetails import io.element.android.libraries.matrix.ui.components.SelectedRoom import io.element.android.libraries.theme.ElementTheme import io.element.android.libraries.ui.strings.CommonStrings diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/forward/ForwardMessagesPresenterTests.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/forward/ForwardMessagesPresenterTests.kt index 9d19932716..983b251df7 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/forward/ForwardMessagesPresenterTests.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/forward/ForwardMessagesPresenterTests.kt @@ -24,12 +24,12 @@ import io.element.android.features.messages.impl.forward.ForwardMessagesEvents import io.element.android.features.messages.impl.forward.ForwardMessagesPresenter import io.element.android.libraries.designsystem.theme.components.SearchBarResultState import io.element.android.libraries.matrix.api.core.EventId -import io.element.android.libraries.matrix.api.room.RoomSummary +import io.element.android.libraries.matrix.api.roomlist.RoomSummary import io.element.android.libraries.matrix.test.AN_EVENT_ID 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.FakeRoomSummaryDataSource import io.element.android.libraries.matrix.test.room.aRoomSummaryDetail +import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.test.runTest @@ -76,10 +76,10 @@ class ForwardMessagesPresenterTests { @Test fun `present - update query`() = runTest { - val roomSummaryDataSource = FakeRoomSummaryDataSource().apply { + val roomListService = FakeRoomListService().apply { postAllRooms(listOf(RoomSummary.Filled(aRoomSummaryDetail()))) } - val client = FakeMatrixClient(roomSummaryDataSource = roomSummaryDataSource) + val client = FakeMatrixClient(roomListService = roomListService) val presenter = aPresenter(client = client) moleculeFlow(RecompositionMode.Immediate) { presenter.present() @@ -166,11 +166,10 @@ class ForwardMessagesPresenterTests { } } - private fun CoroutineScope.aPresenter( + private fun CoroutineScope.aPresenter( eventId: EventId = AN_EVENT_ID, fakeMatrixRoom: FakeMatrixRoom = FakeMatrixRoom(), coroutineScope: CoroutineScope = this, client: FakeMatrixClient = FakeMatrixClient(), ) = ForwardMessagesPresenter(eventId.value, fakeMatrixRoom, coroutineScope, client) - } diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt index 5a82b20723..dbef9c799b 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt @@ -135,6 +135,6 @@ class RoomListPresenter @Inject constructor( // Safe to give bigger size than room list val extendedRangeEnd = range.last + midExtendedRangeSize val extendedRange = IntRange(extendedRangeStart, extendedRangeEnd) - client.roomSummaryDataSource.updateAllRoomsVisibleRange(extendedRange) + client.roomListService.updateAllRoomsVisibleRange(extendedRange) } } diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/datasource/DefaultInviteStateDataSource.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/datasource/DefaultInviteStateDataSource.kt index 3a89014799..e05efd3ddd 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/datasource/DefaultInviteStateDataSource.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/datasource/DefaultInviteStateDataSource.kt @@ -30,7 +30,7 @@ import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.di.SessionScope import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.core.RoomId -import io.element.android.libraries.matrix.api.room.RoomSummary +import io.element.android.libraries.matrix.api.roomlist.RoomSummary import kotlinx.coroutines.withContext import javax.inject.Inject @@ -44,8 +44,9 @@ class DefaultInviteStateDataSource @Inject constructor( @Composable override fun inviteState(): InvitesState { val invites by client - .roomSummaryDataSource - .inviteRooms() + .roomListService + .invites() + .summaries .collectAsState() val seenInvites by seenInvitesStore diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/datasource/RoomListDataSource.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/datasource/RoomListDataSource.kt index 714f6e2e11..e44bcd6b6b 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/datasource/RoomListDataSource.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/datasource/RoomListDataSource.kt @@ -27,8 +27,8 @@ import io.element.android.libraries.designsystem.components.avatar.AvatarData import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.eventformatter.api.RoomLastMessageFormatter import io.element.android.libraries.matrix.api.core.RoomId -import io.element.android.libraries.matrix.api.room.RoomSummary -import io.element.android.libraries.matrix.api.room.RoomSummaryDataSource +import io.element.android.libraries.matrix.api.roomlist.RoomListService +import io.element.android.libraries.matrix.api.roomlist.RoomSummary import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList @@ -44,7 +44,7 @@ import kotlinx.coroutines.withContext import javax.inject.Inject class RoomListDataSource @Inject constructor( - private val roomSummaryDataSource: RoomSummaryDataSource, + private val roomListService: RoomListService, private val lastMessageTimestampFormatter: LastMessageTimestampFormatter, private val roomLastMessageFormatter: RoomLastMessageFormatter, private val coroutineDispatchers: CoroutineDispatchers, @@ -61,8 +61,9 @@ class RoomListDataSource @Inject constructor( } fun launchIn(coroutineScope: CoroutineScope) { - roomSummaryDataSource + roomListService .allRooms() + .summaries .onEach { roomSummaries -> replaceWith(roomSummaries) } diff --git a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt index d2d52aecc8..eaa3801e12 100644 --- a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt +++ b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt @@ -47,8 +47,8 @@ import io.element.android.libraries.matrix.test.A_ROOM_NAME import io.element.android.libraries.matrix.test.A_USER_ID import io.element.android.libraries.matrix.test.A_USER_NAME import io.element.android.libraries.matrix.test.FakeMatrixClient -import io.element.android.libraries.matrix.test.room.FakeRoomSummaryDataSource import io.element.android.libraries.matrix.test.room.aRoomSummaryFilled +import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService import io.element.android.libraries.matrix.test.verification.FakeSessionVerificationService import io.element.android.tests.testutils.consumeItemsUntilPredicate import io.element.android.tests.testutils.testCoroutineDispatchers @@ -111,9 +111,9 @@ class RoomListPresenterTests { @Test fun `present - load 1 room with success`() = runTest { - val roomSummaryDataSource = FakeRoomSummaryDataSource() + val roomListService = FakeRoomListService() val matrixClient = FakeMatrixClient( - roomSummaryDataSource = roomSummaryDataSource + roomListService = roomListService ) val presenter = createRoomListPresenter(matrixClient) moleculeFlow(RecompositionMode.Immediate) { @@ -123,7 +123,7 @@ class RoomListPresenterTests { // Room list is loaded with 16 placeholders Truth.assertThat(initialState.roomList.size).isEqualTo(16) Truth.assertThat(initialState.roomList.all { it.isPlaceholder }).isTrue() - roomSummaryDataSource.postAllRooms(listOf(aRoomSummaryFilled())) + roomListService.postAllRooms(listOf(aRoomSummaryFilled())) val withRoomState = consumeItemsUntilPredicate { state -> state.roomList.size == 1 }.last() Truth.assertThat(withRoomState.roomList.size).isEqualTo(1) Truth.assertThat(withRoomState.roomList.first()) @@ -133,15 +133,15 @@ class RoomListPresenterTests { @Test fun `present - load 1 room with success and filter rooms`() = runTest { - val roomSummaryDataSource = FakeRoomSummaryDataSource() + val roomListService = FakeRoomListService() val matrixClient = FakeMatrixClient( - roomSummaryDataSource = roomSummaryDataSource + roomListService = roomListService ) val presenter = createRoomListPresenter(matrixClient) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { - roomSummaryDataSource.postAllRooms(listOf(aRoomSummaryFilled())) + roomListService.postAllRooms(listOf(aRoomSummaryFilled())) val loadedState = consumeItemsUntilPredicate { state -> state.roomList.size == 1 }.last() // Test filtering with result loadedState.eventSink.invoke(RoomListEvents.UpdateFilter(A_ROOM_NAME.substring(0, 3))) @@ -160,39 +160,39 @@ class RoomListPresenterTests { @Test fun `present - update visible range`() = runTest { - val roomSummaryDataSource = FakeRoomSummaryDataSource() + val roomListService = FakeRoomListService() val matrixClient = FakeMatrixClient( - roomSummaryDataSource = roomSummaryDataSource + roomListService = roomListService ) val presenter = createRoomListPresenter(matrixClient) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { - roomSummaryDataSource.postAllRooms(listOf(aRoomSummaryFilled())) + roomListService.postAllRooms(listOf(aRoomSummaryFilled())) val loadedState = awaitItem() // check initial value - Truth.assertThat(roomSummaryDataSource.latestSlidingSyncRange).isNull() + Truth.assertThat(roomListService.latestSlidingSyncRange).isNull() // Test empty range loadedState.eventSink.invoke(RoomListEvents.UpdateVisibleRange(IntRange(1, 0))) - Truth.assertThat(roomSummaryDataSource.latestSlidingSyncRange).isNull() + Truth.assertThat(roomListService.latestSlidingSyncRange).isNull() // Update visible range and check that range is transmitted to the SDK after computation loadedState.eventSink.invoke(RoomListEvents.UpdateVisibleRange(IntRange(0, 0))) - Truth.assertThat(roomSummaryDataSource.latestSlidingSyncRange) + Truth.assertThat(roomListService.latestSlidingSyncRange) .isEqualTo(IntRange(0, 20)) loadedState.eventSink.invoke(RoomListEvents.UpdateVisibleRange(IntRange(0, 1))) - Truth.assertThat(roomSummaryDataSource.latestSlidingSyncRange) + Truth.assertThat(roomListService.latestSlidingSyncRange) .isEqualTo(IntRange(0, 21)) loadedState.eventSink.invoke(RoomListEvents.UpdateVisibleRange(IntRange(19, 29))) - Truth.assertThat(roomSummaryDataSource.latestSlidingSyncRange) + Truth.assertThat(roomListService.latestSlidingSyncRange) .isEqualTo(IntRange(0, 49)) loadedState.eventSink.invoke(RoomListEvents.UpdateVisibleRange(IntRange(49, 59))) - Truth.assertThat(roomSummaryDataSource.latestSlidingSyncRange) + Truth.assertThat(roomListService.latestSlidingSyncRange) .isEqualTo(IntRange(29, 79)) loadedState.eventSink.invoke(RoomListEvents.UpdateVisibleRange(IntRange(149, 159))) - Truth.assertThat(roomSummaryDataSource.latestSlidingSyncRange) + Truth.assertThat(roomListService.latestSlidingSyncRange) .isEqualTo(IntRange(129, 179)) loadedState.eventSink.invoke(RoomListEvents.UpdateVisibleRange(IntRange(149, 259))) - Truth.assertThat(roomSummaryDataSource.latestSlidingSyncRange) + Truth.assertThat(roomListService.latestSlidingSyncRange) .isEqualTo(IntRange(129, 279)) cancelAndIgnoreRemainingEvents() } @@ -200,9 +200,9 @@ class RoomListPresenterTests { @Test fun `present - handle DismissRequestVerificationPrompt`() = runTest { - val roomSummaryDataSource = FakeRoomSummaryDataSource() + val roomListService = FakeRoomListService() val matrixClient = FakeMatrixClient( - roomSummaryDataSource = roomSummaryDataSource + roomListService = roomListService ) val presenter = createRoomListPresenter( client = matrixClient, @@ -317,7 +317,7 @@ class RoomListPresenterTests { inviteStateDataSource = inviteStateDataSource, leaveRoomPresenter = leaveRoomPresenter, roomListDataSource = RoomListDataSource( - client.roomSummaryDataSource, + client.roomListService, lastMessageTimestampFormatter, roomLastMessageFormatter, coroutineDispatchers = testCoroutineDispatchers() diff --git a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/datasource/DefaultInviteStateDataSourceTest.kt b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/datasource/DefaultInviteStateDataSourceTest.kt index 88e69068fd..ce7bf685a8 100644 --- a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/datasource/DefaultInviteStateDataSourceTest.kt +++ b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/datasource/DefaultInviteStateDataSourceTest.kt @@ -25,8 +25,8 @@ import io.element.android.features.roomlist.impl.InvitesState import io.element.android.libraries.matrix.test.A_ROOM_ID import io.element.android.libraries.matrix.test.A_ROOM_ID_2 import io.element.android.libraries.matrix.test.FakeMatrixClient -import io.element.android.libraries.matrix.test.room.FakeRoomSummaryDataSource import io.element.android.libraries.matrix.test.room.aRoomSummaryFilled +import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService import io.element.android.tests.testutils.testCoroutineDispatchers import kotlinx.coroutines.test.runTest import org.junit.Test @@ -35,8 +35,8 @@ internal class DefaultInviteStateDataSourceTest { @Test fun `emits NoInvites state if invites list is empty`() = runTest { - val roomSummaryDataSource = FakeRoomSummaryDataSource() - val client = FakeMatrixClient(roomSummaryDataSource = roomSummaryDataSource) + val roomListService = FakeRoomListService() + val client = FakeMatrixClient(roomListService = roomListService) val seenStore = FakeSeenInvitesStore() val dataSource = DefaultInviteStateDataSource(client, seenStore, testCoroutineDispatchers()) @@ -49,9 +49,9 @@ internal class DefaultInviteStateDataSourceTest { @Test fun `emits NewInvites state if unseen invite exists`() = runTest { - val roomSummaryDataSource = FakeRoomSummaryDataSource() - roomSummaryDataSource.postInviteRooms(listOf(aRoomSummaryFilled(roomId = A_ROOM_ID))) - val client = FakeMatrixClient(roomSummaryDataSource = roomSummaryDataSource) + val roomListService = FakeRoomListService() + roomListService.postInviteRooms(listOf(aRoomSummaryFilled(roomId = A_ROOM_ID))) + val client = FakeMatrixClient(roomListService = roomListService) val seenStore = FakeSeenInvitesStore() val dataSource = DefaultInviteStateDataSource(client, seenStore, testCoroutineDispatchers()) @@ -65,9 +65,9 @@ internal class DefaultInviteStateDataSourceTest { @Test fun `emits NewInvites state if multiple invites exist and at least one is unseen`() = runTest { - val roomSummaryDataSource = FakeRoomSummaryDataSource() - roomSummaryDataSource.postInviteRooms(listOf(aRoomSummaryFilled(roomId = A_ROOM_ID), aRoomSummaryFilled(roomId = A_ROOM_ID_2))) - val client = FakeMatrixClient(roomSummaryDataSource = roomSummaryDataSource) + val roomListService = FakeRoomListService() + roomListService.postInviteRooms(listOf(aRoomSummaryFilled(roomId = A_ROOM_ID), aRoomSummaryFilled(roomId = A_ROOM_ID_2))) + val client = FakeMatrixClient(roomListService = roomListService) val seenStore = FakeSeenInvitesStore() seenStore.publishRoomIds(setOf(A_ROOM_ID)) val dataSource = DefaultInviteStateDataSource(client, seenStore, testCoroutineDispatchers(useUnconfinedTestDispatcher = true)) @@ -82,9 +82,9 @@ internal class DefaultInviteStateDataSourceTest { @Test fun `emits SeenInvites state if invite exists in seen store`() = runTest { - val roomSummaryDataSource = FakeRoomSummaryDataSource() - roomSummaryDataSource.postInviteRooms(listOf(aRoomSummaryFilled(roomId = A_ROOM_ID))) - val client = FakeMatrixClient(roomSummaryDataSource = roomSummaryDataSource) + val roomListService = FakeRoomListService() + roomListService.postInviteRooms(listOf(aRoomSummaryFilled(roomId = A_ROOM_ID))) + val client = FakeMatrixClient(roomListService = roomListService) val seenStore = FakeSeenInvitesStore() seenStore.publishRoomIds(setOf(A_ROOM_ID)) val dataSource = DefaultInviteStateDataSource(client, seenStore, testCoroutineDispatchers(useUnconfinedTestDispatcher = true)) @@ -100,8 +100,8 @@ internal class DefaultInviteStateDataSourceTest { @Test fun `emits new state in response to upstream events`() = runTest { - val roomSummaryDataSource = FakeRoomSummaryDataSource() - val client = FakeMatrixClient(roomSummaryDataSource = roomSummaryDataSource) + val roomListService = FakeRoomListService() + val client = FakeMatrixClient(roomListService = roomListService) val seenStore = FakeSeenInvitesStore() val dataSource = DefaultInviteStateDataSource(client, seenStore, testCoroutineDispatchers()) @@ -112,7 +112,7 @@ internal class DefaultInviteStateDataSourceTest { Truth.assertThat(awaitItem()).isEqualTo(InvitesState.NoInvites) // When a single invite is received, state should be NewInvites - roomSummaryDataSource.postInviteRooms(listOf(aRoomSummaryFilled(roomId = A_ROOM_ID))) + roomListService.postInviteRooms(listOf(aRoomSummaryFilled(roomId = A_ROOM_ID))) skipItems(1) Truth.assertThat(awaitItem()).isEqualTo(InvitesState.NewInvites) @@ -122,12 +122,12 @@ internal class DefaultInviteStateDataSourceTest { Truth.assertThat(awaitItem()).isEqualTo(InvitesState.SeenInvites) // Another new invite resets it to NewInvites - roomSummaryDataSource.postInviteRooms(listOf(aRoomSummaryFilled(roomId = A_ROOM_ID), aRoomSummaryFilled(roomId = A_ROOM_ID_2))) + roomListService.postInviteRooms(listOf(aRoomSummaryFilled(roomId = A_ROOM_ID), aRoomSummaryFilled(roomId = A_ROOM_ID_2))) skipItems(1) Truth.assertThat(awaitItem()).isEqualTo(InvitesState.NewInvites) // All of the invites going away reverts to NoInvites - roomSummaryDataSource.postInviteRooms(emptyList()) + roomListService.postInviteRooms(emptyList()) skipItems(1) Truth.assertThat(awaitItem()).isEqualTo(InvitesState.NoInvites) } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt index 747de5f554..67c0625a91 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt @@ -26,7 +26,7 @@ import io.element.android.libraries.matrix.api.notification.NotificationService import io.element.android.libraries.matrix.api.pusher.PushersService import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.RoomMembershipObserver -import io.element.android.libraries.matrix.api.room.RoomSummaryDataSource +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.user.MatrixSearchUserResults import io.element.android.libraries.matrix.api.user.MatrixUser @@ -35,7 +35,7 @@ import java.io.Closeable interface MatrixClient : Closeable { val sessionId: SessionId - val roomSummaryDataSource: RoomSummaryDataSource + val roomListService: RoomListService val mediaLoader: MatrixMediaLoader suspend fun getRoom(roomId: RoomId): MatrixRoom? suspend fun findDM(userId: UserId): MatrixRoom? diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomSummaryDataSource.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/RoomList.kt similarity index 65% rename from libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomSummaryDataSource.kt rename to libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/RoomList.kt index d677d56ed9..8714bc2c5c 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomSummaryDataSource.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/RoomList.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.element.android.libraries.matrix.api.room +package io.element.android.libraries.matrix.api.roomlist import kotlinx.coroutines.TimeoutCancellationException import kotlinx.coroutines.flow.StateFlow @@ -23,25 +23,34 @@ import kotlinx.coroutines.withTimeout import timber.log.Timber import kotlin.time.Duration -interface RoomSummaryDataSource { - +/** + * Holds some flows related to a specific set of rooms. + * Can be retrieved from [RoomListService] methods. + */ +interface RoomList { sealed class LoadingState { object NotLoaded : LoadingState() data class Loaded(val numberOfRooms: Int) : LoadingState() } - fun updateAllRoomsVisibleRange(range: IntRange) - fun allRoomsLoadingState(): StateFlow - fun allRooms(): StateFlow> - fun inviteRooms(): StateFlow> + /** + * The list of room summaries as a flow. + */ + val summaries: StateFlow> + + /** + * The loading state of the room list as a flow. + * This is useful to know if a specific set of rooms is loaded or not. + */ + val loadingState: StateFlow } -suspend fun RoomSummaryDataSource.awaitAllRoomsAreLoaded(timeout: Duration = Duration.INFINITE) { +suspend fun RoomList.awaitLoaded(timeout: Duration = Duration.INFINITE) { try { Timber.d("awaitAllRoomsAreLoaded: wait") withTimeout(timeout) { - allRoomsLoadingState().firstOrNull { - it is RoomSummaryDataSource.LoadingState.Loaded + loadingState.firstOrNull { + it is RoomList.LoadingState.Loaded } } } catch (timeoutException: TimeoutCancellationException) { diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/RoomListService.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/RoomListService.kt new file mode 100644 index 0000000000..99381d0e74 --- /dev/null +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/RoomListService.kt @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.matrix.api.roomlist + +import kotlinx.coroutines.flow.StateFlow + +/** + * Entry point for the room list api. + * This service will provide different sets of rooms (all, invites, etc.). + * It requires the SyncService to be started to receive updates. + */ +interface RoomListService { + + sealed class State { + object Idle : State() + object Running : State() + object Error : State() + object Terminated : State() + } + + /** + * returns a [RoomList] object of all rooms we want to display. + * This will exclude some rooms like the invites, or spaces. + */ + fun allRooms(): RoomList + + /** + * returns a [RoomList] object of all invites. + */ + fun invites(): RoomList + + /** + * Will set the visible range of all rooms. + * This is useful to load more data when the user scrolls down. + */ + fun updateAllRoomsVisibleRange(range: IntRange) + + /** + * The state of the service as a flow. + */ + val state: StateFlow +} diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomSummary.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/RoomSummary.kt similarity index 92% rename from libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomSummary.kt rename to libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/RoomSummary.kt index 7dedd86b63..87cf2139d6 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomSummary.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/RoomSummary.kt @@ -14,9 +14,10 @@ * limitations under the License. */ -package io.element.android.libraries.matrix.api.room +package io.element.android.libraries.matrix.api.roomlist import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.api.room.RoomMember import io.element.android.libraries.matrix.api.room.message.RoomMessage sealed interface RoomSummary { diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt index 545fbe0e3f..8f5cfa496b 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt @@ -32,8 +32,8 @@ import io.element.android.libraries.matrix.api.notification.NotificationService import io.element.android.libraries.matrix.api.pusher.PushersService import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.RoomMembershipObserver -import io.element.android.libraries.matrix.api.room.RoomSummaryDataSource -import io.element.android.libraries.matrix.api.room.awaitAllRoomsAreLoaded +import io.element.android.libraries.matrix.api.roomlist.RoomListService +import io.element.android.libraries.matrix.api.roomlist.awaitLoaded 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.user.MatrixSearchUserResults @@ -45,9 +45,8 @@ import io.element.android.libraries.matrix.impl.notification.RustNotificationSer import io.element.android.libraries.matrix.impl.pushers.RustPushersService import io.element.android.libraries.matrix.impl.room.RoomContentForwarder import io.element.android.libraries.matrix.impl.room.RustMatrixRoom -import io.element.android.libraries.matrix.impl.room.RustRoomSummaryDataSource -import io.element.android.libraries.matrix.impl.room.roomOrNull -import io.element.android.libraries.matrix.impl.room.stateFlow +import io.element.android.libraries.matrix.impl.roomlist.RustRoomListService +import io.element.android.libraries.matrix.impl.roomlist.roomOrNull import io.element.android.libraries.matrix.impl.sync.RustSyncService import io.element.android.libraries.matrix.impl.usersearch.UserProfileMapper import io.element.android.libraries.matrix.impl.usersearch.UserSearchResultMapper @@ -90,11 +89,11 @@ class RustMatrixClient constructor( ) : MatrixClient { override val sessionId: UserId = UserId(client.userId()) - private val roomListService = syncService.roomListService() + private val innerRoomListService = syncService.roomListService() private val sessionDispatcher = dispatchers.io.limitedParallelism(64) private val sessionCoroutineScope = appCoroutineScope.childScope(dispatchers.main, "Session-${sessionId}") private val verificationService = RustSessionVerificationService() - private val rustSyncService = RustSyncService(syncService, roomListService.stateFlow(), sessionCoroutineScope) + private val rustSyncService = RustSyncService(syncService, sessionCoroutineScope) private val pushersService = RustPushersService( client = client, dispatchers = dispatchers, @@ -122,15 +121,15 @@ class RustMatrixClient constructor( } } - private val rustRoomSummaryDataSource: RustRoomSummaryDataSource = - RustRoomSummaryDataSource( - roomListService = roomListService, + private val rustRoomListService: RoomListService = + RustRoomListService( + innerRoomListService = innerRoomListService, sessionCoroutineScope = sessionCoroutineScope, dispatcher = sessionDispatcher, ) - override val roomSummaryDataSource: RoomSummaryDataSource - get() = rustRoomSummaryDataSource + override val roomListService: RoomListService + get() = rustRoomListService private val rustMediaLoader = RustMediaLoader(baseCacheDirectory, dispatchers, client) override val mediaLoader: MatrixMediaLoader @@ -138,7 +137,7 @@ class RustMatrixClient constructor( private val roomMembershipObserver = RoomMembershipObserver() - private val roomContentForwarder = RoomContentForwarder(roomListService) + private val roomContentForwarder = RoomContentForwarder(innerRoomListService) init { client.setDelegate(clientDelegate) @@ -156,7 +155,7 @@ class RustMatrixClient constructor( var cachedPairOfRoom = pairOfRoom(roomId) if (cachedPairOfRoom == null) { //... otherwise, lets wait for the SS to load all rooms and check again. - roomSummaryDataSource.awaitAllRoomsAreLoaded() + roomListService.allRooms().awaitLoaded() cachedPairOfRoom = pairOfRoom(roomId) } cachedPairOfRoom?.let { (roomListItem, fullRoom) -> @@ -174,7 +173,7 @@ class RustMatrixClient constructor( } private fun pairOfRoom(roomId: RoomId): Pair? { - val cachedRoomListItem = roomListService.roomOrNull(roomId.value) + val cachedRoomListItem = innerRoomListService.roomOrNull(roomId.value) val fullRoom = cachedRoomListItem?.fullRoom() return if (cachedRoomListItem == null || fullRoom == null) { Timber.d("No room cached for $roomId") @@ -225,7 +224,7 @@ class RustMatrixClient constructor( // Wait to receive the room back from the sync withTimeout(30_000L) { - roomSummaryDataSource.allRooms() + roomListService.allRooms().summaries .filter { roomSummaries -> roomSummaries.map { it.identifier() }.contains(roomId.value) } @@ -273,7 +272,7 @@ class RustMatrixClient constructor( client.setDelegate(null) verificationService.destroy() syncService.destroy() - roomListService.destroy() + innerRoomListService.destroy() notificationClient.destroy() client.destroy() } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/di/SessionMatrixModule.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/di/SessionMatrixModule.kt index bf260be6ec..dba1dbd0a3 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/di/SessionMatrixModule.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/di/SessionMatrixModule.kt @@ -23,7 +23,7 @@ import io.element.android.libraries.di.SessionScope import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.media.MatrixMediaLoader import io.element.android.libraries.matrix.api.room.RoomMembershipObserver -import io.element.android.libraries.matrix.api.room.RoomSummaryDataSource +import io.element.android.libraries.matrix.api.roomlist.RoomListService import io.element.android.libraries.matrix.api.verification.SessionVerificationService @Module @@ -40,8 +40,8 @@ object SessionMatrixModule { } @Provides - fun provideRoomSummaryDataSource(matrixClient: MatrixClient): RoomSummaryDataSource { - return matrixClient.roomSummaryDataSource + fun providesRoomListService(matrixClient: MatrixClient): RoomListService { + return matrixClient.roomListService } @Provides diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomContentForwarder.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomContentForwarder.kt index c4f272797a..8ee0361ace 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomContentForwarder.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomContentForwarder.kt @@ -20,6 +20,7 @@ import io.element.android.libraries.core.coroutine.parallelMap import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.room.ForwardEventException +import io.element.android.libraries.matrix.impl.roomlist.roomOrNull import kotlinx.coroutines.CancellationException import kotlinx.coroutines.withTimeout import org.matrix.rustcomponents.sdk.Room diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustRoomSummaryDataSource.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustRoomSummaryDataSource.kt deleted file mode 100644 index 133bf508ce..0000000000 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustRoomSummaryDataSource.kt +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright (c) 2023 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.element.android.libraries.matrix.impl.room - -import io.element.android.libraries.matrix.api.room.RoomSummary -import io.element.android.libraries.matrix.api.room.RoomSummaryDataSource -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.launch -import org.matrix.rustcomponents.sdk.RoomList -import org.matrix.rustcomponents.sdk.RoomListEntriesUpdate -import org.matrix.rustcomponents.sdk.RoomListException -import org.matrix.rustcomponents.sdk.RoomListInput -import org.matrix.rustcomponents.sdk.RoomListLoadingState -import org.matrix.rustcomponents.sdk.RoomListRange -import org.matrix.rustcomponents.sdk.RoomListService -import org.matrix.rustcomponents.sdk.RoomListServiceState -import timber.log.Timber - -internal class RustRoomSummaryDataSource( - private val roomListService: RoomListService, - private val sessionCoroutineScope: CoroutineScope, - dispatcher: CoroutineDispatcher, - roomSummaryDetailsFactory: RoomSummaryDetailsFactory = RoomSummaryDetailsFactory(), -) : RoomSummaryDataSource { - - private val allRooms = MutableStateFlow>(emptyList()) - private val inviteRooms = MutableStateFlow>(emptyList()) - - private val allRoomsLoadingState: MutableStateFlow = MutableStateFlow(RoomSummaryDataSource.LoadingState.NotLoaded) - private val allRoomsListProcessor = RoomSummaryListProcessor(allRooms, roomListService, roomSummaryDetailsFactory, shouldFetchFullRoom = false) - private val inviteRoomsListProcessor = RoomSummaryListProcessor(inviteRooms, roomListService, roomSummaryDetailsFactory, shouldFetchFullRoom = true) - - init { - sessionCoroutineScope.launch(dispatcher) { - val allRooms = roomListService.allRooms() - allRooms - .observeEntriesWithProcessor(allRoomsListProcessor) - .launchIn(this) - - allRooms - .loadingStateFlow() - .map { it.toRoomSummaryDataSourceLoadingState() } - .onEach { - allRoomsLoadingState.value = it - } - .launchIn(this) - - launch { - // Wait until running, as invites is only available after that - roomListService.stateFlow().first { - it == RoomListServiceState.RUNNING - } - roomListService.invites() - .observeEntriesWithProcessor(inviteRoomsListProcessor) - .launchIn(this) - } - } - } - - override fun allRooms(): StateFlow> { - return allRooms - } - - override fun inviteRooms(): StateFlow> { - return inviteRooms - } - - override fun allRoomsLoadingState(): StateFlow { - return allRoomsLoadingState - } - - override fun updateAllRoomsVisibleRange(range: IntRange) { - Timber.v("setVisibleRange=$range") - sessionCoroutineScope.launch { - try { - val ranges = listOf(RoomListRange(range.first.toUInt(), range.last.toUInt())) - roomListService.applyInput( - RoomListInput.Viewport(ranges) - ) - } catch (exception: RoomListException) { - Timber.e(exception, "Failed updating visible range") - } - } - } -} - -private fun RoomListLoadingState.toRoomSummaryDataSourceLoadingState(): RoomSummaryDataSource.LoadingState { - return when (this) { - is RoomListLoadingState.Loaded -> RoomSummaryDataSource.LoadingState.Loaded(maximumNumberOfRooms?.toInt() ?: 0) - is RoomListLoadingState.NotLoaded -> RoomSummaryDataSource.LoadingState.NotLoaded - } -} - -private fun RoomList.observeEntriesWithProcessor(processor: RoomSummaryListProcessor): Flow> { - return entriesFlow { roomListEntries -> - processor.postEntries(roomListEntries) - }.onEach { update -> - processor.postUpdate(update) - } -} - diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomListExtensions.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomListExtensions.kt similarity index 98% rename from libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomListExtensions.kt rename to libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomListExtensions.kt index 8e7047aaa4..8d96990a9e 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomListExtensions.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomListExtensions.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.element.android.libraries.matrix.impl.room +package io.element.android.libraries.matrix.impl.roomlist import io.element.android.libraries.core.data.tryOrNull import io.element.android.libraries.matrix.impl.util.mxCallbackFlow diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomSummaryDetailsFactory.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryDetailsFactory.kt similarity index 89% rename from libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomSummaryDetailsFactory.kt rename to libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryDetailsFactory.kt index 7dd7bf4581..b57eb892e0 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomSummaryDetailsFactory.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryDetailsFactory.kt @@ -14,10 +14,11 @@ * limitations under the License. */ -package io.element.android.libraries.matrix.impl.room +package io.element.android.libraries.matrix.impl.roomlist import io.element.android.libraries.matrix.api.core.RoomId -import io.element.android.libraries.matrix.api.room.RoomSummaryDetails +import io.element.android.libraries.matrix.api.roomlist.RoomSummaryDetails +import io.element.android.libraries.matrix.impl.room.RoomMemberMapper import io.element.android.libraries.matrix.impl.room.message.RoomMessageFactory import org.matrix.rustcomponents.sdk.Room import org.matrix.rustcomponents.sdk.RoomListItem diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomSummaryListProcessor.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryListProcessor.kt similarity index 97% rename from libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomSummaryListProcessor.kt rename to libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryListProcessor.kt index ef54a0604d..9a67ff1f30 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomSummaryListProcessor.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryListProcessor.kt @@ -14,10 +14,10 @@ * limitations under the License. */ -package io.element.android.libraries.matrix.impl.room +package io.element.android.libraries.matrix.impl.roomlist import io.element.android.libraries.core.coroutine.parallelMap -import io.element.android.libraries.matrix.api.room.RoomSummary +import io.element.android.libraries.matrix.api.roomlist.RoomSummary import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.sync.Mutex diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RustRoomList.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RustRoomList.kt new file mode 100644 index 0000000000..481b38dd9b --- /dev/null +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RustRoomList.kt @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.matrix.impl.roomlist + +import io.element.android.libraries.matrix.api.roomlist.RoomList +import io.element.android.libraries.matrix.api.roomlist.RoomSummary +import kotlinx.coroutines.flow.StateFlow + +/** + * Simple implementation of [RoomList] where state flows are provided through constructor. + */ +class RustRoomList( + override val summaries: StateFlow>, + override val loadingState: StateFlow +) : RoomList diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RustRoomListService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RustRoomListService.kt new file mode 100644 index 0000000000..bf66bade3b --- /dev/null +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RustRoomListService.kt @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.matrix.impl.roomlist + +import io.element.android.libraries.matrix.api.roomlist.RoomList +import io.element.android.libraries.matrix.api.roomlist.RoomListService +import io.element.android.libraries.matrix.api.roomlist.RoomSummary +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.launch +import org.matrix.rustcomponents.sdk.RoomListEntriesUpdate +import org.matrix.rustcomponents.sdk.RoomListException +import org.matrix.rustcomponents.sdk.RoomListInput +import org.matrix.rustcomponents.sdk.RoomListLoadingState +import org.matrix.rustcomponents.sdk.RoomListRange +import org.matrix.rustcomponents.sdk.RoomListServiceState +import timber.log.Timber +import org.matrix.rustcomponents.sdk.RoomListService as InnerRustRoomListService + +class RustRoomListService( + private val innerRoomListService: InnerRustRoomListService, + private val sessionCoroutineScope: CoroutineScope, + dispatcher: CoroutineDispatcher, + roomSummaryDetailsFactory: RoomSummaryDetailsFactory = RoomSummaryDetailsFactory(), +) : RoomListService { + + private val allRooms = MutableStateFlow>(emptyList()) + private val inviteRooms = MutableStateFlow>(emptyList()) + + private val allRoomsLoadingState: MutableStateFlow = MutableStateFlow(RoomList.LoadingState.NotLoaded) + private val allRoomsListProcessor = RoomSummaryListProcessor(allRooms, innerRoomListService, roomSummaryDetailsFactory, shouldFetchFullRoom = false) + private val invitesLoadingState: MutableStateFlow = MutableStateFlow(RoomList.LoadingState.NotLoaded) + private val inviteRoomsListProcessor = RoomSummaryListProcessor(inviteRooms, innerRoomListService, roomSummaryDetailsFactory, shouldFetchFullRoom = true) + + init { + sessionCoroutineScope.launch(dispatcher) { + val allRooms = innerRoomListService.allRooms() + allRooms + .observeEntriesWithProcessor(allRoomsListProcessor) + .launchIn(this) + allRooms + .observeLoadingState(allRoomsLoadingState) + .launchIn(this) + + + launch { + // Wait until running, as invites is only available after that + innerRoomListService.stateFlow().first { + it == RoomListServiceState.RUNNING + } + val invites = innerRoomListService.invites() + invites + .observeEntriesWithProcessor(inviteRoomsListProcessor) + .launchIn(this) + invites + .observeLoadingState(invitesLoadingState) + .launchIn(this) + + } + } + } + + override fun allRooms(): RoomList { + return RustRoomList(allRooms, allRoomsLoadingState) + } + + override fun invites(): RoomList { + return RustRoomList(inviteRooms, invitesLoadingState) + } + + override fun updateAllRoomsVisibleRange(range: IntRange) { + Timber.v("setVisibleRange=$range") + sessionCoroutineScope.launch { + try { + val ranges = listOf(RoomListRange(range.first.toUInt(), range.last.toUInt())) + innerRoomListService.applyInput( + RoomListInput.Viewport(ranges) + ) + } catch (exception: RoomListException) { + Timber.e(exception, "Failed updating visible range") + } + } + } + + override val state: StateFlow = + innerRoomListService.stateFlow() + .map { it.toRoomListState() } + .onEach { state -> + Timber.d("RoomList state=$state") + } + .distinctUntilChanged() + .stateIn(sessionCoroutineScope, SharingStarted.Eagerly, RoomListService.State.Idle) +} + +private fun RoomListLoadingState.toLoadingState(): RoomList.LoadingState { + return when (this) { + is RoomListLoadingState.Loaded -> RoomList.LoadingState.Loaded(maximumNumberOfRooms?.toInt() ?: 0) + RoomListLoadingState.NotLoaded -> RoomList.LoadingState.NotLoaded + } +} + +private fun RoomListServiceState.toRoomListState(): RoomListService.State { + return when (this) { + RoomListServiceState.INIT, + RoomListServiceState.SETTING_UP -> RoomListService.State.Idle + RoomListServiceState.RUNNING -> RoomListService.State.Running + RoomListServiceState.ERROR -> RoomListService.State.Error + RoomListServiceState.TERMINATED -> RoomListService.State.Terminated + } +} + +private fun org.matrix.rustcomponents.sdk.RoomList.observeEntriesWithProcessor(processor: RoomSummaryListProcessor): Flow> { + return entriesFlow { roomListEntries -> + processor.postEntries(roomListEntries) + }.onEach { update -> + processor.postUpdate(update) + } +} + +private fun org.matrix.rustcomponents.sdk.RoomList.observeLoadingState(stateFlow: MutableStateFlow): Flow { + return loadingStateFlow() + .map { it.toLoadingState() } + .onEach { + stateFlow.value = it + } +} + diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/sync/AppStateMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/sync/AppStateMapper.kt index 51228231f9..ae90f9fc44 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/sync/AppStateMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/sync/AppStateMapper.kt @@ -17,19 +17,8 @@ package io.element.android.libraries.matrix.impl.sync import io.element.android.libraries.matrix.api.sync.SyncState -import org.matrix.rustcomponents.sdk.RoomListServiceState import org.matrix.rustcomponents.sdk.SyncServiceState -internal fun RoomListServiceState.toSyncState(): SyncState { - return when (this) { - RoomListServiceState.INIT, - RoomListServiceState.SETTING_UP -> SyncState.Idle - RoomListServiceState.RUNNING -> SyncState.Running - RoomListServiceState.ERROR -> SyncState.Error - RoomListServiceState.TERMINATED -> SyncState.Terminated - } -} - internal fun SyncServiceState.toSyncState(): SyncState { return when (this) { SyncServiceState.IDLE -> SyncState.Idle diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/sync/RustSyncService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/sync/RustSyncService.kt index 2546614a78..87fa02edd2 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/sync/RustSyncService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/sync/RustSyncService.kt @@ -19,36 +19,38 @@ package io.element.android.libraries.matrix.impl.sync 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.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.stateIn -import org.matrix.rustcomponents.sdk.RoomListServiceState import org.matrix.rustcomponents.sdk.SyncServiceInterface +import org.matrix.rustcomponents.sdk.SyncServiceState import timber.log.Timber class RustSyncService( private val innerSyncService: SyncServiceInterface, - roomListStateFlow: Flow, sessionCoroutineScope: CoroutineScope ) : SyncService { override suspend fun startSync() = runCatching { Timber.i("Start sync") innerSyncService.start() + }.onFailure { + Timber.d("Start sync failed: $it") } override fun stopSync() = runCatching { Timber.i("Stop sync") innerSyncService.pause() + }.onFailure { + Timber.d("Stop sync failed: $it") } override val syncState: StateFlow = - roomListStateFlow - .map(RoomListServiceState::toSyncState) + innerSyncService.stateFlow() + .map(SyncServiceState::toSyncState) .onEach { state -> Timber.i("Sync state=$state") } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/sync/SyncServiceExtension.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/sync/SyncServiceExtension.kt index a8e366ff7b..c9e38ec7d4 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/sync/SyncServiceExtension.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/sync/SyncServiceExtension.kt @@ -22,11 +22,11 @@ import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.trySendBlocking import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.buffer -import org.matrix.rustcomponents.sdk.SyncService +import org.matrix.rustcomponents.sdk.SyncServiceInterface import org.matrix.rustcomponents.sdk.SyncServiceState import org.matrix.rustcomponents.sdk.SyncServiceStateObserver -fun SyncService.stateFlow(): Flow = +fun SyncServiceInterface.stateFlow(): Flow = mxCallbackFlow { val listener = object : SyncServiceStateObserver { override fun onUpdate(state: SyncServiceState) { diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt index 1a654ac8d4..1229836e30 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt @@ -27,7 +27,7 @@ import io.element.android.libraries.matrix.api.notification.NotificationService import io.element.android.libraries.matrix.api.pusher.PushersService import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.RoomMembershipObserver -import io.element.android.libraries.matrix.api.room.RoomSummaryDataSource +import io.element.android.libraries.matrix.api.roomlist.RoomListService import io.element.android.libraries.matrix.api.user.MatrixSearchUserResults import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.matrix.api.verification.SessionVerificationService @@ -35,7 +35,7 @@ import io.element.android.libraries.matrix.test.media.FakeMediaLoader import io.element.android.libraries.matrix.test.notification.FakeNotificationService import io.element.android.libraries.matrix.test.pushers.FakePushersService import io.element.android.libraries.matrix.test.room.FakeMatrixRoom -import io.element.android.libraries.matrix.test.room.FakeRoomSummaryDataSource +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.tests.testutils.simulateLongTask @@ -45,7 +45,7 @@ class FakeMatrixClient( override val sessionId: SessionId = A_SESSION_ID, private val userDisplayName: Result = Result.success(A_USER_NAME), private val userAvatarURLString: Result = Result.success(AN_AVATAR_URL), - override val roomSummaryDataSource: RoomSummaryDataSource = FakeRoomSummaryDataSource(), + override val roomListService: RoomListService = FakeRoomListService(), override val mediaLoader: MatrixMediaLoader = FakeMediaLoader(), private val sessionVerificationService: FakeSessionVerificationService = FakeSessionVerificationService(), private val pushersService: FakePushersService = FakePushersService(), diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/RoomSummaryFixture.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/RoomSummaryFixture.kt index 4df815c54c..59bac7ad40 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/RoomSummaryFixture.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/RoomSummaryFixture.kt @@ -20,8 +20,8 @@ import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.TransactionId import io.element.android.libraries.matrix.api.core.UserId -import io.element.android.libraries.matrix.api.room.RoomSummary -import io.element.android.libraries.matrix.api.room.RoomSummaryDetails +import io.element.android.libraries.matrix.api.roomlist.RoomSummary +import io.element.android.libraries.matrix.api.roomlist.RoomSummaryDetails import io.element.android.libraries.matrix.api.room.message.RoomMessage import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo import io.element.android.libraries.matrix.api.timeline.item.event.EventContent diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeRoomSummaryDataSource.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomlist/FakeRoomListService.kt similarity index 51% rename from libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeRoomSummaryDataSource.kt rename to libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomlist/FakeRoomListService.kt index cae36e14c8..fa2e347e3b 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeRoomSummaryDataSource.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomlist/FakeRoomListService.kt @@ -14,18 +14,21 @@ * limitations under the License. */ -package io.element.android.libraries.matrix.test.room +package io.element.android.libraries.matrix.test.roomlist -import io.element.android.libraries.matrix.api.room.RoomSummary -import io.element.android.libraries.matrix.api.room.RoomSummaryDataSource +import io.element.android.libraries.matrix.api.roomlist.RoomList +import io.element.android.libraries.matrix.api.roomlist.RoomListService +import io.element.android.libraries.matrix.api.roomlist.RoomSummary import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow -class FakeRoomSummaryDataSource : RoomSummaryDataSource { +class FakeRoomListService : RoomListService { private val allRoomSummariesFlow = MutableStateFlow>(emptyList()) private val inviteRoomSummariesFlow = MutableStateFlow>(emptyList()) - private val allRoomsLoadingStateFlow = MutableStateFlow(RoomSummaryDataSource.LoadingState.NotLoaded) + private val allRoomsLoadingStateFlow = MutableStateFlow(RoomList.LoadingState.NotLoaded) + private val inviteRoomsLoadingStateFlow = MutableStateFlow(RoomList.LoadingState.NotLoaded) + private val roomListStateFlow = MutableStateFlow(RoomListService.State.Idle) suspend fun postAllRooms(roomSummaries: List) { allRoomSummariesFlow.emit(roomSummaries) @@ -35,20 +38,16 @@ class FakeRoomSummaryDataSource : RoomSummaryDataSource { inviteRoomSummariesFlow.emit(roomSummaries) } - suspend fun postLoadingState(loadingState: RoomSummaryDataSource.LoadingState) { + suspend fun postAllRoomsLoadingState(loadingState: RoomList.LoadingState) { allRoomsLoadingStateFlow.emit(loadingState) } - override fun allRoomsLoadingState(): StateFlow { - return allRoomsLoadingStateFlow + suspend fun postInviteRoomsLoadingState(loadingState: RoomList.LoadingState) { + inviteRoomsLoadingStateFlow.emit(loadingState) } - override fun allRooms(): StateFlow> { - return allRoomSummariesFlow - } - - override fun inviteRooms(): StateFlow> { - return inviteRoomSummariesFlow + suspend fun postState(state: RoomListService.State) { + roomListStateFlow.emit(state) } var latestSlidingSyncRange: IntRange? = null @@ -57,4 +56,20 @@ class FakeRoomSummaryDataSource : RoomSummaryDataSource { override fun updateAllRoomsVisibleRange(range: IntRange) { latestSlidingSyncRange = range } + + override fun allRooms(): RoomList { + return SimpleRoomList( + summaries = allRoomSummariesFlow, + loadingState = allRoomsLoadingStateFlow + ) + } + + override fun invites(): RoomList { + return SimpleRoomList( + summaries = inviteRoomSummariesFlow, + loadingState = inviteRoomsLoadingStateFlow + ) + } + + override val state: StateFlow = roomListStateFlow } diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomlist/SimpleRoomList.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomlist/SimpleRoomList.kt new file mode 100644 index 0000000000..28b04ae318 --- /dev/null +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomlist/SimpleRoomList.kt @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.matrix.test.roomlist + +import io.element.android.libraries.matrix.api.roomlist.RoomList +import io.element.android.libraries.matrix.api.roomlist.RoomSummary +import kotlinx.coroutines.flow.StateFlow + +data class SimpleRoomList( + override val summaries: StateFlow>, + override val loadingState: StateFlow +) : RoomList diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SelectedRoom.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SelectedRoom.kt index 2881235335..da6f69d830 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SelectedRoom.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SelectedRoom.kt @@ -46,7 +46,7 @@ import io.element.android.libraries.designsystem.theme.components.Icon import io.element.android.libraries.designsystem.theme.components.Surface import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.matrix.api.core.RoomId -import io.element.android.libraries.matrix.api.room.RoomSummaryDetails +import io.element.android.libraries.matrix.api.roomlist.RoomSummaryDetails import io.element.android.libraries.ui.strings.CommonStrings @Composable diff --git a/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/RoomListScreen.kt b/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/RoomListScreen.kt index 41dbc8bfe2..b394e1c04d 100644 --- a/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/RoomListScreen.kt +++ b/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/RoomListScreen.kt @@ -68,7 +68,7 @@ class RoomListScreen( inviteStateDataSource = DefaultInviteStateDataSource(matrixClient, DefaultSeenInvitesStore(context), coroutineDispatchers), leaveRoomPresenter = LeaveRoomPresenterImpl(matrixClient, RoomMembershipObserver(), coroutineDispatchers), roomListDataSource = RoomListDataSource( - roomSummaryDataSource = matrixClient.roomSummaryDataSource, + roomListService = matrixClient.roomListService, lastMessageTimestampFormatter = DefaultLastMessageTimestampFormatter(dateTimeProvider, dateFormatters), roomLastMessageFormatter = DefaultRoomLastMessageFormatter( sp = stringProvider,