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 aa44b4f001..21f946b3fd 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 @@ -30,43 +30,30 @@ import io.element.android.features.leaveroom.api.LeaveRoomEvent import io.element.android.features.leaveroom.api.LeaveRoomPresenter import io.element.android.features.networkmonitor.api.NetworkMonitor import io.element.android.features.networkmonitor.api.NetworkStatus -import io.element.android.features.roomlist.impl.model.RoomListRoomSummary -import io.element.android.features.roomlist.impl.model.RoomListRoomSummaryPlaceholders +import io.element.android.features.roomlist.impl.datasource.InviteStateDataSource +import io.element.android.features.roomlist.impl.datasource.RoomListDataSource import io.element.android.libraries.architecture.Presenter -import io.element.android.libraries.core.coroutine.parallelMap -import io.element.android.libraries.core.extensions.orEmpty -import io.element.android.libraries.dateformatter.api.LastMessageTimestampFormatter -import io.element.android.libraries.designsystem.components.avatar.AvatarData -import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.designsystem.utils.SnackbarDispatcher import io.element.android.libraries.designsystem.utils.collectSnackbarMessageAsState -import io.element.android.libraries.eventformatter.api.RoomLastMessageFormatter 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.user.MatrixUser import io.element.android.libraries.matrix.api.user.getCurrentUser import io.element.android.libraries.matrix.api.verification.SessionVerificationService import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus -import kotlinx.collections.immutable.ImmutableList -import kotlinx.collections.immutable.persistentListOf -import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch -import timber.log.Timber import javax.inject.Inject private const val extendedRangeSize = 40 class RoomListPresenter @Inject constructor( private val client: MatrixClient, - private val lastMessageTimestampFormatter: LastMessageTimestampFormatter, - private val roomLastMessageFormatter: RoomLastMessageFormatter, private val sessionVerificationService: SessionVerificationService, private val networkMonitor: NetworkMonitor, private val snackbarDispatcher: SnackbarDispatcher, private val inviteStateDataSource: InviteStateDataSource, private val leaveRoomPresenter: LeaveRoomPresenter, + private val roomListDataSource: RoomListDataSource, ) : Presenter { @Composable @@ -75,21 +62,13 @@ class RoomListPresenter @Inject constructor( val matrixUser: MutableState = rememberSaveable { mutableStateOf(null) } - var filter by rememberSaveable { mutableStateOf("") } - val roomSummaries by client - .roomSummaryDataSource - .allRooms() - .collectAsState() - + val roomList by roomListDataSource.allRooms.collectAsState() + val filteredRoomList by roomListDataSource.filteredRooms.collectAsState() + val filter by roomListDataSource.filter.collectAsState() val networkConnectionStatus by networkMonitor.connectivity.collectAsState() - Timber.v("RoomSummaries size = ${roomSummaries.size}") - - val mappedRoomSummaries: MutableState> = remember { mutableStateOf(persistentListOf()) } - val filteredRoomSummaries: MutableState> = remember { - mutableStateOf(persistentListOf()) - } LaunchedEffect(Unit) { + roomListDataSource.launchIn(this) initialLoad(matrixUser) } @@ -107,12 +86,12 @@ class RoomListPresenter @Inject constructor( fun handleEvents(event: RoomListEvents) { when (event) { - is RoomListEvents.UpdateFilter -> filter = event.newFilter + is RoomListEvents.UpdateFilter -> roomListDataSource.updateFilter(event.newFilter) is RoomListEvents.UpdateVisibleRange -> updateVisibleRange(event.range) RoomListEvents.DismissRequestVerificationPrompt -> verificationPromptDismissed = true RoomListEvents.ToggleSearchResults -> { if (displaySearchResults) { - filter = "" + roomListDataSource.updateFilter("") } displaySearchResults = !displaySearchResults } @@ -127,22 +106,13 @@ class RoomListPresenter @Inject constructor( } } - LaunchedEffect(roomSummaries, filter) { - mappedRoomSummaries.value = if (roomSummaries.isEmpty()) { - RoomListRoomSummaryPlaceholders.createFakeList(16).toImmutableList() - } else { - mapRoomSummaries(roomSummaries).toImmutableList() - } - filteredRoomSummaries.value = updateFilteredRoomSummaries(mappedRoomSummaries.value, filter) - } - val snackbarMessage by snackbarDispatcher.collectSnackbarMessageAsState() return RoomListState( matrixUser = matrixUser.value, - roomList = mappedRoomSummaries.value, + roomList = roomList, filter = filter, - filteredRoomList = filteredRoomSummaries.value, + filteredRoomList = filteredRoomList, displayVerificationPrompt = displayVerificationPrompt, snackbarMessage = snackbarMessage, hasNetworkConnection = networkConnectionStatus == NetworkStatus.Online, @@ -154,13 +124,6 @@ class RoomListPresenter @Inject constructor( ) } - private fun updateFilteredRoomSummaries(mappedRoomSummaries: ImmutableList, filter: String): ImmutableList { - return when { - filter.isEmpty() -> emptyList() - else -> mappedRoomSummaries.filter { it.name.contains(filter, ignoreCase = true) } - }.toImmutableList() - } - private fun CoroutineScope.initialLoad(matrixUser: MutableState) = launch { matrixUser.value = client.getCurrentUser() } @@ -174,34 +137,4 @@ class RoomListPresenter @Inject constructor( val extendedRange = IntRange(extendedRangeStart, extendedRangeEnd) client.roomSummaryDataSource.updateAllRoomsVisibleRange(extendedRange) } - - private suspend fun mapRoomSummaries( - roomSummaries: List - ): List { - return roomSummaries.parallelMap { roomSummary -> - when (roomSummary) { - is RoomSummary.Empty -> RoomListRoomSummaryPlaceholders.create(roomSummary.identifier) - is RoomSummary.Filled -> { - val avatarData = AvatarData( - id = roomSummary.identifier(), - name = roomSummary.details.name, - url = roomSummary.details.avatarURLString, - size = AvatarSize.RoomListItem, - ) - val roomIdentifier = roomSummary.identifier() - RoomListRoomSummary( - id = roomSummary.identifier(), - roomId = RoomId(roomIdentifier), - name = roomSummary.details.name, - hasUnread = roomSummary.details.unreadNotificationCount > 0, - timestamp = lastMessageTimestampFormatter.format(roomSummary.details.lastMessageTimestamp), - lastMessage = roomSummary.details.lastMessage?.let { message -> - roomLastMessageFormatter.format(message.event, roomSummary.details.isDirect) - }.orEmpty(), - avatarData = avatarData, - ) - } - } - } - } } diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/DefaultInviteStateDataSource.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/datasource/DefaultInviteStateDataSource.kt similarity index 95% rename from features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/DefaultInviteStateDataSource.kt rename to features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/datasource/DefaultInviteStateDataSource.kt index a20039355f..3a89014799 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/DefaultInviteStateDataSource.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/datasource/DefaultInviteStateDataSource.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.element.android.features.roomlist.impl +package io.element.android.features.roomlist.impl.datasource import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -25,6 +25,7 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import com.squareup.anvil.annotations.ContributesBinding import io.element.android.features.invitelist.api.SeenInvitesStore +import io.element.android.features.roomlist.impl.InvitesState import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.di.SessionScope import io.element.android.libraries.matrix.api.MatrixClient diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/InviteStateDataSource.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/datasource/InviteStateDataSource.kt similarity index 85% rename from features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/InviteStateDataSource.kt rename to features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/datasource/InviteStateDataSource.kt index e236c206c5..f44ec1ea83 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/InviteStateDataSource.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/datasource/InviteStateDataSource.kt @@ -14,9 +14,10 @@ * limitations under the License. */ -package io.element.android.features.roomlist.impl +package io.element.android.features.roomlist.impl.datasource import androidx.compose.runtime.Composable +import io.element.android.features.roomlist.impl.InvitesState interface InviteStateDataSource { 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 new file mode 100644 index 0000000000..8602f15910 --- /dev/null +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/datasource/RoomListDataSource.kt @@ -0,0 +1,116 @@ +/* + * 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.features.roomlist.impl.datasource + +import io.element.android.features.roomlist.impl.model.RoomListRoomSummary +import io.element.android.features.roomlist.impl.model.RoomListRoomSummaryPlaceholders +import io.element.android.libraries.core.coroutine.CoroutineDispatchers +import io.element.android.libraries.core.extensions.orEmpty +import io.element.android.libraries.dateformatter.api.LastMessageTimestampFormatter +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 kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.toImmutableList +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.withContext +import javax.inject.Inject + +class RoomListDataSource @Inject constructor( + private val roomSummaryDataSource: RoomSummaryDataSource, + private val lastMessageTimestampFormatter: LastMessageTimestampFormatter, + private val roomLastMessageFormatter: RoomLastMessageFormatter, + private val coroutineDispatchers: CoroutineDispatchers, +) { + + private val _filter = MutableStateFlow("") + private val _allRooms = MutableStateFlow>(persistentListOf()) + private val _filteredRooms = MutableStateFlow>(persistentListOf()) + + fun launchIn(coroutineScope: CoroutineScope) { + roomSummaryDataSource + .allRooms() + .onEach { roomSummaries -> + _allRooms.value = if (roomSummaries.isEmpty()) { + RoomListRoomSummaryPlaceholders.createFakeList(16) + } else { + mapRoomSummaries(roomSummaries) + }.toImmutableList() + } + .launchIn(coroutineScope) + + combine( + _filter, + _allRooms + ) { filterValue, allRoomsValue -> + when { + filterValue.isEmpty() -> emptyList() + else -> allRoomsValue.filter { it.name.contains(filterValue, ignoreCase = true) } + }.toImmutableList() + } + .onEach { + _filteredRooms.value = it + }.launchIn(coroutineScope) + } + + fun updateFilter(filterValue: String) { + _filter.value = filterValue + } + + val filter: StateFlow = _filter + val allRooms: StateFlow> = _allRooms + val filteredRooms: StateFlow> = _filteredRooms + + private suspend fun mapRoomSummaries( + roomSummaries: List + ): List = withContext(coroutineDispatchers.computation) { + roomSummaries.map { roomSummary -> + when (roomSummary) { + is RoomSummary.Empty -> RoomListRoomSummaryPlaceholders.create(roomSummary.identifier) + is RoomSummary.Filled -> { + val avatarData = AvatarData( + id = roomSummary.identifier(), + name = roomSummary.details.name, + url = roomSummary.details.avatarURLString, + size = AvatarSize.RoomListItem, + ) + val roomIdentifier = roomSummary.identifier() + RoomListRoomSummary( + id = roomSummary.identifier(), + roomId = RoomId(roomIdentifier), + name = roomSummary.details.name, + hasUnread = roomSummary.details.unreadNotificationCount > 0, + timestamp = lastMessageTimestampFormatter.format(roomSummary.details.lastMessageTimestamp), + lastMessage = roomSummary.details.lastMessage?.let { message -> + roomLastMessageFormatter.format(message.event, roomSummary.details.isDirect) + }.orEmpty(), + avatarData = avatarData, + ) + } + } + } + } +} diff --git a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/DefaultInviteStateDataSourceTest.kt b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/DefaultInviteStateDataSourceTest.kt index 1f3f0105da..0f8dff07ca 100644 --- a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/DefaultInviteStateDataSourceTest.kt +++ b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/DefaultInviteStateDataSourceTest.kt @@ -21,6 +21,7 @@ import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth import io.element.android.features.invitelist.test.FakeSeenInvitesStore +import io.element.android.features.roomlist.impl.datasource.DefaultInviteStateDataSource 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 diff --git a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/FakeInviteDataSource.kt b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/FakeInviteDataSource.kt index 083fd1ec70..314590d9b7 100644 --- a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/FakeInviteDataSource.kt +++ b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/FakeInviteDataSource.kt @@ -18,6 +18,7 @@ package io.element.android.features.roomlist.impl import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState +import io.element.android.features.roomlist.impl.datasource.InviteStateDataSource import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flowOf 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 50db5a3be2..f98b741e56 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 @@ -21,8 +21,12 @@ import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth import io.element.android.features.leaveroom.api.LeaveRoomEvent +import io.element.android.features.leaveroom.api.LeaveRoomPresenter import io.element.android.features.leaveroom.fake.LeaveRoomPresenterFake +import io.element.android.features.networkmonitor.api.NetworkMonitor import io.element.android.features.networkmonitor.test.FakeNetworkMonitor +import io.element.android.features.roomlist.impl.datasource.InviteStateDataSource +import io.element.android.features.roomlist.impl.datasource.RoomListDataSource import io.element.android.features.roomlist.impl.model.RoomListRoomSummary import io.element.android.features.roomlist.impl.model.aRoomListRoomSummary import io.element.android.libraries.dateformatter.api.LastMessageTimestampFormatter @@ -30,7 +34,10 @@ import io.element.android.libraries.dateformatter.test.FakeLastMessageTimestampF import io.element.android.libraries.designsystem.components.avatar.AvatarData import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.designsystem.utils.SnackbarDispatcher +import io.element.android.libraries.eventformatter.api.RoomLastMessageFormatter import io.element.android.libraries.eventformatter.test.FakeRoomLastMessageFormatter +import io.element.android.libraries.matrix.api.MatrixClient +import io.element.android.libraries.matrix.api.verification.SessionVerificationService import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus import io.element.android.libraries.matrix.test.AN_AVATAR_URL import io.element.android.libraries.matrix.test.AN_EXCEPTION @@ -42,7 +49,9 @@ 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.verification.FakeSessionVerificationService +import io.element.android.tests.testutils.testCoroutineDispatchers import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest import org.junit.Test @@ -50,17 +59,7 @@ class RoomListPresenterTests { @Test fun `present - should start with no user and then load user with success`() = runTest { - val matrixClient = FakeMatrixClient() - val presenter = RoomListPresenter( - matrixClient, - createDateFormatter(), - FakeRoomLastMessageFormatter(), - FakeSessionVerificationService(), - FakeNetworkMonitor(), - SnackbarDispatcher(), - FakeInviteDataSource(), - LeaveRoomPresenterFake(), - ) + val presenter = aRoomListPresenter() moleculeFlow(RecompositionClock.Immediate) { presenter.present() }.test { @@ -80,16 +79,7 @@ class RoomListPresenterTests { userDisplayName = Result.failure(AN_EXCEPTION), userAvatarURLString = Result.failure(AN_EXCEPTION), ) - val presenter = RoomListPresenter( - matrixClient, - createDateFormatter(), - FakeRoomLastMessageFormatter(), - FakeSessionVerificationService(), - FakeNetworkMonitor(), - SnackbarDispatcher(), - FakeInviteDataSource(), - LeaveRoomPresenterFake(), - ) + val presenter = aRoomListPresenter(matrixClient) moleculeFlow(RecompositionClock.Immediate) { presenter.present() }.test { @@ -102,17 +92,7 @@ class RoomListPresenterTests { @Test fun `present - should filter room with success`() = runTest { - val matrixClient = FakeMatrixClient() - val presenter = RoomListPresenter( - matrixClient, - createDateFormatter(), - FakeRoomLastMessageFormatter(), - FakeSessionVerificationService(), - FakeNetworkMonitor(), - SnackbarDispatcher(), - FakeInviteDataSource(), - LeaveRoomPresenterFake(), - ) + val presenter = aRoomListPresenter() moleculeFlow(RecompositionClock.Immediate) { presenter.present() }.test { @@ -133,26 +113,16 @@ class RoomListPresenterTests { val matrixClient = FakeMatrixClient( roomSummaryDataSource = roomSummaryDataSource ) - val presenter = RoomListPresenter( - matrixClient, - createDateFormatter(), - FakeRoomLastMessageFormatter(), - FakeSessionVerificationService(), - FakeNetworkMonitor(), - SnackbarDispatcher(), - FakeInviteDataSource(), - LeaveRoomPresenterFake(), - ) + val presenter = aRoomListPresenter(matrixClient) moleculeFlow(RecompositionClock.Immediate) { presenter.present() }.test { skipItems(1) - val withUserState = awaitItem() + val initialState = awaitItem() // Room list is loaded with 16 placeholders - Truth.assertThat(withUserState.roomList.size).isEqualTo(16) - Truth.assertThat(withUserState.roomList.all { it.isPlaceholder }).isTrue() + Truth.assertThat(initialState.roomList.size).isEqualTo(16) + Truth.assertThat(initialState.roomList.all { it.isPlaceholder }).isTrue() roomSummaryDataSource.postAllRooms(listOf(aRoomSummaryFilled())) - skipItems(1) val withRoomState = awaitItem() Truth.assertThat(withRoomState.roomList.size).isEqualTo(1) Truth.assertThat(withRoomState.roomList.first()) @@ -166,21 +136,12 @@ class RoomListPresenterTests { val matrixClient = FakeMatrixClient( roomSummaryDataSource = roomSummaryDataSource ) - val presenter = RoomListPresenter( - matrixClient, - createDateFormatter(), - FakeRoomLastMessageFormatter(), - FakeSessionVerificationService(), - FakeNetworkMonitor(), - SnackbarDispatcher(), - FakeInviteDataSource(), - LeaveRoomPresenterFake(), - ) + val presenter = aRoomListPresenter(matrixClient) moleculeFlow(RecompositionClock.Immediate) { presenter.present() }.test { roomSummaryDataSource.postAllRooms(listOf(aRoomSummaryFilled())) - skipItems(3) + skipItems(1) val loadedState = awaitItem() // Test filtering with result loadedState.eventSink.invoke(RoomListEvents.UpdateFilter(A_ROOM_NAME.substring(0, 3))) @@ -193,9 +154,8 @@ class RoomListPresenterTests { // Test filtering without result withNotFilteredRoomState.eventSink.invoke(RoomListEvents.UpdateFilter("tada")) skipItems(1) // Filter update - val withFilteredRoomState = awaitItem() - Truth.assertThat(withFilteredRoomState.filter).isEqualTo("tada") - Truth.assertThat(withFilteredRoomState.filteredRoomList).isEmpty() + Truth.assertThat(awaitItem().filter).isEqualTo("tada") + Truth.assertThat(awaitItem().filteredRoomList).isEmpty() } } @@ -205,21 +165,11 @@ class RoomListPresenterTests { val matrixClient = FakeMatrixClient( roomSummaryDataSource = roomSummaryDataSource ) - val presenter = RoomListPresenter( - matrixClient, - createDateFormatter(), - FakeRoomLastMessageFormatter(), - FakeSessionVerificationService(), - FakeNetworkMonitor(), - SnackbarDispatcher(), - FakeInviteDataSource(), - LeaveRoomPresenterFake(), - ) + val presenter = aRoomListPresenter(matrixClient) moleculeFlow(RecompositionClock.Immediate) { presenter.present() }.test { roomSummaryDataSource.postAllRooms(listOf(aRoomSummaryFilled())) - skipItems(3) val loadedState = awaitItem() // check initial value Truth.assertThat(roomSummaryDataSource.latestSlidingSyncRange).isNull() @@ -245,6 +195,7 @@ class RoomListPresenterTests { loadedState.eventSink.invoke(RoomListEvents.UpdateVisibleRange(IntRange(149, 259))) Truth.assertThat(roomSummaryDataSource.latestSlidingSyncRange) .isEqualTo(IntRange(129, 279)) + cancelAndIgnoreRemainingEvents() } } @@ -254,18 +205,12 @@ class RoomListPresenterTests { val matrixClient = FakeMatrixClient( roomSummaryDataSource = roomSummaryDataSource ) - val presenter = RoomListPresenter( - matrixClient, - createDateFormatter(), - FakeRoomLastMessageFormatter(), - FakeSessionVerificationService().apply { + val presenter = aRoomListPresenter( + client = matrixClient, + sessionVerificationService = FakeSessionVerificationService().apply { givenIsReady(true) givenVerifiedStatus(SessionVerifiedStatus.NotVerified) }, - FakeNetworkMonitor(), - SnackbarDispatcher(), - FakeInviteDataSource(), - LeaveRoomPresenterFake(), ) moleculeFlow(RecompositionClock.Immediate) { presenter.present() @@ -281,22 +226,12 @@ class RoomListPresenterTests { @Test fun `present - sets invite state`() = runTest { val inviteStateFlow = MutableStateFlow(InvitesState.NoInvites) - val matrixClient = FakeMatrixClient() - val presenter = RoomListPresenter( - matrixClient, - createDateFormatter(), - FakeRoomLastMessageFormatter(), - FakeSessionVerificationService(), - FakeNetworkMonitor(), - SnackbarDispatcher(), - FakeInviteDataSource(inviteStateFlow), - LeaveRoomPresenterFake(), - ) + val inviteStateDataSource = FakeInviteDataSource(inviteStateFlow) + val presenter = aRoomListPresenter(inviteStateDataSource = inviteStateDataSource) moleculeFlow(RecompositionClock.Immediate) { presenter.present() }.test { skipItems(1) - Truth.assertThat(awaitItem().invitesState).isEqualTo(InvitesState.NoInvites) inviteStateFlow.value = InvitesState.SeenInvites @@ -312,17 +247,7 @@ class RoomListPresenterTests { @Test fun `present - show context menu`() = runTest { - val matrixClient = FakeMatrixClient() - val presenter = RoomListPresenter( - matrixClient, - createDateFormatter(), - FakeRoomLastMessageFormatter(), - FakeSessionVerificationService(), - FakeNetworkMonitor(), - SnackbarDispatcher(), - FakeInviteDataSource(), - LeaveRoomPresenterFake(), - ) + val presenter = aRoomListPresenter() moleculeFlow(RecompositionClock.Immediate) { presenter.present() }.test { @@ -340,17 +265,7 @@ class RoomListPresenterTests { @Test fun `present - hide context menu`() = runTest { - val matrixClient = FakeMatrixClient() - val presenter = RoomListPresenter( - matrixClient, - createDateFormatter(), - FakeRoomLastMessageFormatter(), - FakeSessionVerificationService(), - FakeNetworkMonitor(), - SnackbarDispatcher(), - FakeInviteDataSource(), - LeaveRoomPresenterFake(), - ) + val presenter = aRoomListPresenter() moleculeFlow(RecompositionClock.Immediate) { presenter.present() }.test { @@ -373,34 +288,42 @@ class RoomListPresenterTests { @Test fun `present - leave room calls into leave room presenter`() = runTest { val leaveRoomPresenter = LeaveRoomPresenterFake() - val matrixClient = FakeMatrixClient() - val presenter = RoomListPresenter( - matrixClient, - createDateFormatter(), - FakeRoomLastMessageFormatter(), - FakeSessionVerificationService(), - FakeNetworkMonitor(), - SnackbarDispatcher(), - FakeInviteDataSource(), - leaveRoomPresenter, - ) + val presenter = aRoomListPresenter(leaveRoomPresenter = leaveRoomPresenter) moleculeFlow(RecompositionClock.Immediate) { presenter.present() }.test { - skipItems(1) - val initialState = awaitItem() initialState.eventSink(RoomListEvents.LeaveRoom(A_ROOM_ID)) - Truth.assertThat(leaveRoomPresenter.events).containsExactly(LeaveRoomEvent.ShowConfirmation(A_ROOM_ID)) + cancelAndIgnoreRemainingEvents() } } - private fun createDateFormatter(): LastMessageTimestampFormatter { - return FakeLastMessageTimestampFormatter().apply { + private fun TestScope.aRoomListPresenter( + client: MatrixClient = FakeMatrixClient(), + sessionVerificationService: SessionVerificationService = FakeSessionVerificationService(), + networkMonitor: NetworkMonitor = FakeNetworkMonitor(), + snackbarDispatcher: SnackbarDispatcher = SnackbarDispatcher(), + inviteStateDataSource: InviteStateDataSource = FakeInviteDataSource(), + leaveRoomPresenter: LeaveRoomPresenter = LeaveRoomPresenterFake(), + lastMessageTimestampFormatter: LastMessageTimestampFormatter = FakeLastMessageTimestampFormatter().apply { givenFormat(A_FORMATTED_DATE) - } - } + }, + roomLastMessageFormatter: RoomLastMessageFormatter = FakeRoomLastMessageFormatter() + ) = RoomListPresenter( + client = client, + sessionVerificationService = sessionVerificationService, + networkMonitor = networkMonitor, + snackbarDispatcher = snackbarDispatcher, + inviteStateDataSource = inviteStateDataSource, + leaveRoomPresenter = leaveRoomPresenter, + roomListDataSource = RoomListDataSource( + client.roomSummaryDataSource, + lastMessageTimestampFormatter, + roomLastMessageFormatter, + coroutineDispatchers = testCoroutineDispatchers() + ) + ) } private const val A_FORMATTED_DATE = "formatted_date" 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 104a204164..bf260be6ec 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 @@ -20,29 +20,31 @@ import com.squareup.anvil.annotations.ContributesTo import dagger.Module import dagger.Provides import io.element.android.libraries.di.SessionScope -import io.element.android.libraries.di.SingleIn import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.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.verification.SessionVerificationService @Module @ContributesTo(SessionScope::class) object SessionMatrixModule { @Provides - @SingleIn(SessionScope::class) fun providesSessionVerificationService(matrixClient: MatrixClient): SessionVerificationService { return matrixClient.sessionVerificationService() } @Provides - @SingleIn(SessionScope::class) fun provideRoomMembershipObserver(matrixClient: MatrixClient): RoomMembershipObserver { return matrixClient.roomMembershipObserver() } @Provides - @SingleIn(SessionScope::class) + fun provideRoomSummaryDataSource(matrixClient: MatrixClient): RoomSummaryDataSource { + return matrixClient.roomSummaryDataSource + } + + @Provides fun provideMediaLoader(matrixClient: MatrixClient): MatrixMediaLoader { return matrixClient.mediaLoader } 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 f9cdf0f3fd..d9f4e147f5 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 @@ -23,7 +23,7 @@ import androidx.compose.ui.Modifier import io.element.android.features.invitelist.impl.DefaultSeenInvitesStore import io.element.android.features.leaveroom.impl.LeaveRoomPresenterImpl import io.element.android.features.networkmonitor.impl.NetworkMonitorImpl -import io.element.android.features.roomlist.impl.DefaultInviteStateDataSource +import io.element.android.features.roomlist.impl.datasource.DefaultInviteStateDataSource import io.element.android.features.roomlist.impl.RoomListPresenter import io.element.android.features.roomlist.impl.RoomListView import io.element.android.libraries.core.coroutine.CoroutineDispatchers