Browse Source

RoomList: introduce RoomListDataSource so we keep the data in memory as long as the node is in the backstack.

pull/907/head
ganfra 1 year ago
parent
commit
bb12338583
  1. 89
      features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt
  2. 3
      features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/datasource/DefaultInviteStateDataSource.kt
  3. 3
      features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/datasource/InviteStateDataSource.kt
  4. 116
      features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/datasource/RoomListDataSource.kt
  5. 1
      features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/DefaultInviteStateDataSourceTest.kt
  6. 1
      features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/FakeInviteDataSource.kt
  7. 187
      features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt
  8. 10
      libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/di/SessionMatrixModule.kt
  9. 2
      samples/minimal/src/main/kotlin/io/element/android/samples/minimal/RoomListScreen.kt

89
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 @@ -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<RoomListState> {
@Composable
@ -75,21 +62,13 @@ class RoomListPresenter @Inject constructor( @@ -75,21 +62,13 @@ class RoomListPresenter @Inject constructor(
val matrixUser: MutableState<MatrixUser?> = 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<ImmutableList<RoomListRoomSummary>> = remember { mutableStateOf(persistentListOf()) }
val filteredRoomSummaries: MutableState<ImmutableList<RoomListRoomSummary>> = remember {
mutableStateOf(persistentListOf())
}
LaunchedEffect(Unit) {
roomListDataSource.launchIn(this)
initialLoad(matrixUser)
}
@ -107,12 +86,12 @@ class RoomListPresenter @Inject constructor( @@ -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( @@ -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( @@ -154,13 +124,6 @@ class RoomListPresenter @Inject constructor(
)
}
private fun updateFilteredRoomSummaries(mappedRoomSummaries: ImmutableList<RoomListRoomSummary>, filter: String): ImmutableList<RoomListRoomSummary> {
return when {
filter.isEmpty() -> emptyList()
else -> mappedRoomSummaries.filter { it.name.contains(filter, ignoreCase = true) }
}.toImmutableList()
}
private fun CoroutineScope.initialLoad(matrixUser: MutableState<MatrixUser?>) = launch {
matrixUser.value = client.getCurrentUser()
}
@ -174,34 +137,4 @@ class RoomListPresenter @Inject constructor( @@ -174,34 +137,4 @@ class RoomListPresenter @Inject constructor(
val extendedRange = IntRange(extendedRangeStart, extendedRangeEnd)
client.roomSummaryDataSource.updateAllRoomsVisibleRange(extendedRange)
}
private suspend fun mapRoomSummaries(
roomSummaries: List<RoomSummary>
): List<RoomListRoomSummary> {
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,
)
}
}
}
}
}

3
features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/DefaultInviteStateDataSource.kt → features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/datasource/DefaultInviteStateDataSource.kt

@ -14,7 +14,7 @@ @@ -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 @@ -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

3
features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/InviteStateDataSource.kt → features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/datasource/InviteStateDataSource.kt

@ -14,9 +14,10 @@ @@ -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 {

116
features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/datasource/RoomListDataSource.kt

@ -0,0 +1,116 @@ @@ -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<ImmutableList<RoomListRoomSummary>>(persistentListOf())
private val _filteredRooms = MutableStateFlow<ImmutableList<RoomListRoomSummary>>(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<String> = _filter
val allRooms: StateFlow<ImmutableList<RoomListRoomSummary>> = _allRooms
val filteredRooms: StateFlow<ImmutableList<RoomListRoomSummary>> = _filteredRooms
private suspend fun mapRoomSummaries(
roomSummaries: List<RoomSummary>
): List<RoomListRoomSummary> = 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,
)
}
}
}
}
}

1
features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/DefaultInviteStateDataSourceTest.kt

@ -21,6 +21,7 @@ import app.cash.molecule.moleculeFlow @@ -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

1
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 @@ -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

187
features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt

@ -21,8 +21,12 @@ import app.cash.molecule.moleculeFlow @@ -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 @@ -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 @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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"

10
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 @@ -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
}

2
samples/minimal/src/main/kotlin/io/element/android/samples/minimal/RoomListScreen.kt

@ -23,7 +23,7 @@ import androidx.compose.ui.Modifier @@ -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

Loading…
Cancel
Save