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
import io.element.android.features.leaveroom.api.LeaveRoomPresenter import io.element.android.features.leaveroom.api.LeaveRoomPresenter
import io.element.android.features.networkmonitor.api.NetworkMonitor import io.element.android.features.networkmonitor.api.NetworkMonitor
import io.element.android.features.networkmonitor.api.NetworkStatus import io.element.android.features.networkmonitor.api.NetworkStatus
import io.element.android.features.roomlist.impl.model.RoomListRoomSummary import io.element.android.features.roomlist.impl.datasource.InviteStateDataSource
import io.element.android.features.roomlist.impl.model.RoomListRoomSummaryPlaceholders import io.element.android.features.roomlist.impl.datasource.RoomListDataSource
import io.element.android.libraries.architecture.Presenter 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.SnackbarDispatcher
import io.element.android.libraries.designsystem.utils.collectSnackbarMessageAsState 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.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.MatrixUser
import io.element.android.libraries.matrix.api.user.getCurrentUser 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.SessionVerificationService
import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus 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.CoroutineScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
private const val extendedRangeSize = 40 private const val extendedRangeSize = 40
class RoomListPresenter @Inject constructor( class RoomListPresenter @Inject constructor(
private val client: MatrixClient, private val client: MatrixClient,
private val lastMessageTimestampFormatter: LastMessageTimestampFormatter,
private val roomLastMessageFormatter: RoomLastMessageFormatter,
private val sessionVerificationService: SessionVerificationService, private val sessionVerificationService: SessionVerificationService,
private val networkMonitor: NetworkMonitor, private val networkMonitor: NetworkMonitor,
private val snackbarDispatcher: SnackbarDispatcher, private val snackbarDispatcher: SnackbarDispatcher,
private val inviteStateDataSource: InviteStateDataSource, private val inviteStateDataSource: InviteStateDataSource,
private val leaveRoomPresenter: LeaveRoomPresenter, private val leaveRoomPresenter: LeaveRoomPresenter,
private val roomListDataSource: RoomListDataSource,
) : Presenter<RoomListState> { ) : Presenter<RoomListState> {
@Composable @Composable
@ -75,21 +62,13 @@ class RoomListPresenter @Inject constructor(
val matrixUser: MutableState<MatrixUser?> = rememberSaveable { val matrixUser: MutableState<MatrixUser?> = rememberSaveable {
mutableStateOf(null) mutableStateOf(null)
} }
var filter by rememberSaveable { mutableStateOf("") } val roomList by roomListDataSource.allRooms.collectAsState()
val roomSummaries by client val filteredRoomList by roomListDataSource.filteredRooms.collectAsState()
.roomSummaryDataSource val filter by roomListDataSource.filter.collectAsState()
.allRooms()
.collectAsState()
val networkConnectionStatus by networkMonitor.connectivity.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) { LaunchedEffect(Unit) {
roomListDataSource.launchIn(this)
initialLoad(matrixUser) initialLoad(matrixUser)
} }
@ -107,12 +86,12 @@ class RoomListPresenter @Inject constructor(
fun handleEvents(event: RoomListEvents) { fun handleEvents(event: RoomListEvents) {
when (event) { when (event) {
is RoomListEvents.UpdateFilter -> filter = event.newFilter is RoomListEvents.UpdateFilter -> roomListDataSource.updateFilter(event.newFilter)
is RoomListEvents.UpdateVisibleRange -> updateVisibleRange(event.range) is RoomListEvents.UpdateVisibleRange -> updateVisibleRange(event.range)
RoomListEvents.DismissRequestVerificationPrompt -> verificationPromptDismissed = true RoomListEvents.DismissRequestVerificationPrompt -> verificationPromptDismissed = true
RoomListEvents.ToggleSearchResults -> { RoomListEvents.ToggleSearchResults -> {
if (displaySearchResults) { if (displaySearchResults) {
filter = "" roomListDataSource.updateFilter("")
} }
displaySearchResults = !displaySearchResults 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() val snackbarMessage by snackbarDispatcher.collectSnackbarMessageAsState()
return RoomListState( return RoomListState(
matrixUser = matrixUser.value, matrixUser = matrixUser.value,
roomList = mappedRoomSummaries.value, roomList = roomList,
filter = filter, filter = filter,
filteredRoomList = filteredRoomSummaries.value, filteredRoomList = filteredRoomList,
displayVerificationPrompt = displayVerificationPrompt, displayVerificationPrompt = displayVerificationPrompt,
snackbarMessage = snackbarMessage, snackbarMessage = snackbarMessage,
hasNetworkConnection = networkConnectionStatus == NetworkStatus.Online, hasNetworkConnection = networkConnectionStatus == NetworkStatus.Online,
@ -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 { private fun CoroutineScope.initialLoad(matrixUser: MutableState<MatrixUser?>) = launch {
matrixUser.value = client.getCurrentUser() matrixUser.value = client.getCurrentUser()
} }
@ -174,34 +137,4 @@ class RoomListPresenter @Inject constructor(
val extendedRange = IntRange(extendedRangeStart, extendedRangeEnd) val extendedRange = IntRange(extendedRangeStart, extendedRangeEnd)
client.roomSummaryDataSource.updateAllRoomsVisibleRange(extendedRange) 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 @@
* limitations under the License. * 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.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
@ -25,6 +25,7 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import com.squareup.anvil.annotations.ContributesBinding import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.features.invitelist.api.SeenInvitesStore 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.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.di.SessionScope import io.element.android.libraries.di.SessionScope
import io.element.android.libraries.matrix.api.MatrixClient 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 @@
* limitations under the License. * 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.Composable
import io.element.android.features.roomlist.impl.InvitesState
interface InviteStateDataSource { interface InviteStateDataSource {

116
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<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
import app.cash.turbine.test import app.cash.turbine.test
import com.google.common.truth.Truth import com.google.common.truth.Truth
import io.element.android.features.invitelist.test.FakeSeenInvitesStore 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
import io.element.android.libraries.matrix.test.A_ROOM_ID_2 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.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
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import io.element.android.features.roomlist.impl.datasource.InviteStateDataSource
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf 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
import app.cash.turbine.test import app.cash.turbine.test
import com.google.common.truth.Truth import com.google.common.truth.Truth
import io.element.android.features.leaveroom.api.LeaveRoomEvent 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.leaveroom.fake.LeaveRoomPresenterFake
import io.element.android.features.networkmonitor.api.NetworkMonitor
import io.element.android.features.networkmonitor.test.FakeNetworkMonitor 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.RoomListRoomSummary
import io.element.android.features.roomlist.impl.model.aRoomListRoomSummary import io.element.android.features.roomlist.impl.model.aRoomListRoomSummary
import io.element.android.libraries.dateformatter.api.LastMessageTimestampFormatter 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.AvatarData
import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.designsystem.utils.SnackbarDispatcher 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.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.api.verification.SessionVerifiedStatus
import io.element.android.libraries.matrix.test.AN_AVATAR_URL import io.element.android.libraries.matrix.test.AN_AVATAR_URL
import io.element.android.libraries.matrix.test.AN_EXCEPTION 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.FakeRoomSummaryDataSource
import io.element.android.libraries.matrix.test.room.aRoomSummaryFilled import io.element.android.libraries.matrix.test.room.aRoomSummaryFilled
import io.element.android.libraries.matrix.test.verification.FakeSessionVerificationService import io.element.android.libraries.matrix.test.verification.FakeSessionVerificationService
import io.element.android.tests.testutils.testCoroutineDispatchers
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import org.junit.Test import org.junit.Test
@ -50,17 +59,7 @@ class RoomListPresenterTests {
@Test @Test
fun `present - should start with no user and then load user with success`() = runTest { fun `present - should start with no user and then load user with success`() = runTest {
val matrixClient = FakeMatrixClient() val presenter = aRoomListPresenter()
val presenter = RoomListPresenter(
matrixClient,
createDateFormatter(),
FakeRoomLastMessageFormatter(),
FakeSessionVerificationService(),
FakeNetworkMonitor(),
SnackbarDispatcher(),
FakeInviteDataSource(),
LeaveRoomPresenterFake(),
)
moleculeFlow(RecompositionClock.Immediate) { moleculeFlow(RecompositionClock.Immediate) {
presenter.present() presenter.present()
}.test { }.test {
@ -80,16 +79,7 @@ class RoomListPresenterTests {
userDisplayName = Result.failure(AN_EXCEPTION), userDisplayName = Result.failure(AN_EXCEPTION),
userAvatarURLString = Result.failure(AN_EXCEPTION), userAvatarURLString = Result.failure(AN_EXCEPTION),
) )
val presenter = RoomListPresenter( val presenter = aRoomListPresenter(matrixClient)
matrixClient,
createDateFormatter(),
FakeRoomLastMessageFormatter(),
FakeSessionVerificationService(),
FakeNetworkMonitor(),
SnackbarDispatcher(),
FakeInviteDataSource(),
LeaveRoomPresenterFake(),
)
moleculeFlow(RecompositionClock.Immediate) { moleculeFlow(RecompositionClock.Immediate) {
presenter.present() presenter.present()
}.test { }.test {
@ -102,17 +92,7 @@ class RoomListPresenterTests {
@Test @Test
fun `present - should filter room with success`() = runTest { fun `present - should filter room with success`() = runTest {
val matrixClient = FakeMatrixClient() val presenter = aRoomListPresenter()
val presenter = RoomListPresenter(
matrixClient,
createDateFormatter(),
FakeRoomLastMessageFormatter(),
FakeSessionVerificationService(),
FakeNetworkMonitor(),
SnackbarDispatcher(),
FakeInviteDataSource(),
LeaveRoomPresenterFake(),
)
moleculeFlow(RecompositionClock.Immediate) { moleculeFlow(RecompositionClock.Immediate) {
presenter.present() presenter.present()
}.test { }.test {
@ -133,26 +113,16 @@ class RoomListPresenterTests {
val matrixClient = FakeMatrixClient( val matrixClient = FakeMatrixClient(
roomSummaryDataSource = roomSummaryDataSource roomSummaryDataSource = roomSummaryDataSource
) )
val presenter = RoomListPresenter( val presenter = aRoomListPresenter(matrixClient)
matrixClient,
createDateFormatter(),
FakeRoomLastMessageFormatter(),
FakeSessionVerificationService(),
FakeNetworkMonitor(),
SnackbarDispatcher(),
FakeInviteDataSource(),
LeaveRoomPresenterFake(),
)
moleculeFlow(RecompositionClock.Immediate) { moleculeFlow(RecompositionClock.Immediate) {
presenter.present() presenter.present()
}.test { }.test {
skipItems(1) skipItems(1)
val withUserState = awaitItem() val initialState = awaitItem()
// Room list is loaded with 16 placeholders // Room list is loaded with 16 placeholders
Truth.assertThat(withUserState.roomList.size).isEqualTo(16) Truth.assertThat(initialState.roomList.size).isEqualTo(16)
Truth.assertThat(withUserState.roomList.all { it.isPlaceholder }).isTrue() Truth.assertThat(initialState.roomList.all { it.isPlaceholder }).isTrue()
roomSummaryDataSource.postAllRooms(listOf(aRoomSummaryFilled())) roomSummaryDataSource.postAllRooms(listOf(aRoomSummaryFilled()))
skipItems(1)
val withRoomState = awaitItem() val withRoomState = awaitItem()
Truth.assertThat(withRoomState.roomList.size).isEqualTo(1) Truth.assertThat(withRoomState.roomList.size).isEqualTo(1)
Truth.assertThat(withRoomState.roomList.first()) Truth.assertThat(withRoomState.roomList.first())
@ -166,21 +136,12 @@ class RoomListPresenterTests {
val matrixClient = FakeMatrixClient( val matrixClient = FakeMatrixClient(
roomSummaryDataSource = roomSummaryDataSource roomSummaryDataSource = roomSummaryDataSource
) )
val presenter = RoomListPresenter( val presenter = aRoomListPresenter(matrixClient)
matrixClient,
createDateFormatter(),
FakeRoomLastMessageFormatter(),
FakeSessionVerificationService(),
FakeNetworkMonitor(),
SnackbarDispatcher(),
FakeInviteDataSource(),
LeaveRoomPresenterFake(),
)
moleculeFlow(RecompositionClock.Immediate) { moleculeFlow(RecompositionClock.Immediate) {
presenter.present() presenter.present()
}.test { }.test {
roomSummaryDataSource.postAllRooms(listOf(aRoomSummaryFilled())) roomSummaryDataSource.postAllRooms(listOf(aRoomSummaryFilled()))
skipItems(3) skipItems(1)
val loadedState = awaitItem() val loadedState = awaitItem()
// Test filtering with result // Test filtering with result
loadedState.eventSink.invoke(RoomListEvents.UpdateFilter(A_ROOM_NAME.substring(0, 3))) loadedState.eventSink.invoke(RoomListEvents.UpdateFilter(A_ROOM_NAME.substring(0, 3)))
@ -193,9 +154,8 @@ class RoomListPresenterTests {
// Test filtering without result // Test filtering without result
withNotFilteredRoomState.eventSink.invoke(RoomListEvents.UpdateFilter("tada")) withNotFilteredRoomState.eventSink.invoke(RoomListEvents.UpdateFilter("tada"))
skipItems(1) // Filter update skipItems(1) // Filter update
val withFilteredRoomState = awaitItem() Truth.assertThat(awaitItem().filter).isEqualTo("tada")
Truth.assertThat(withFilteredRoomState.filter).isEqualTo("tada") Truth.assertThat(awaitItem().filteredRoomList).isEmpty()
Truth.assertThat(withFilteredRoomState.filteredRoomList).isEmpty()
} }
} }
@ -205,21 +165,11 @@ class RoomListPresenterTests {
val matrixClient = FakeMatrixClient( val matrixClient = FakeMatrixClient(
roomSummaryDataSource = roomSummaryDataSource roomSummaryDataSource = roomSummaryDataSource
) )
val presenter = RoomListPresenter( val presenter = aRoomListPresenter(matrixClient)
matrixClient,
createDateFormatter(),
FakeRoomLastMessageFormatter(),
FakeSessionVerificationService(),
FakeNetworkMonitor(),
SnackbarDispatcher(),
FakeInviteDataSource(),
LeaveRoomPresenterFake(),
)
moleculeFlow(RecompositionClock.Immediate) { moleculeFlow(RecompositionClock.Immediate) {
presenter.present() presenter.present()
}.test { }.test {
roomSummaryDataSource.postAllRooms(listOf(aRoomSummaryFilled())) roomSummaryDataSource.postAllRooms(listOf(aRoomSummaryFilled()))
skipItems(3)
val loadedState = awaitItem() val loadedState = awaitItem()
// check initial value // check initial value
Truth.assertThat(roomSummaryDataSource.latestSlidingSyncRange).isNull() Truth.assertThat(roomSummaryDataSource.latestSlidingSyncRange).isNull()
@ -245,6 +195,7 @@ class RoomListPresenterTests {
loadedState.eventSink.invoke(RoomListEvents.UpdateVisibleRange(IntRange(149, 259))) loadedState.eventSink.invoke(RoomListEvents.UpdateVisibleRange(IntRange(149, 259)))
Truth.assertThat(roomSummaryDataSource.latestSlidingSyncRange) Truth.assertThat(roomSummaryDataSource.latestSlidingSyncRange)
.isEqualTo(IntRange(129, 279)) .isEqualTo(IntRange(129, 279))
cancelAndIgnoreRemainingEvents()
} }
} }
@ -254,18 +205,12 @@ class RoomListPresenterTests {
val matrixClient = FakeMatrixClient( val matrixClient = FakeMatrixClient(
roomSummaryDataSource = roomSummaryDataSource roomSummaryDataSource = roomSummaryDataSource
) )
val presenter = RoomListPresenter( val presenter = aRoomListPresenter(
matrixClient, client = matrixClient,
createDateFormatter(), sessionVerificationService = FakeSessionVerificationService().apply {
FakeRoomLastMessageFormatter(),
FakeSessionVerificationService().apply {
givenIsReady(true) givenIsReady(true)
givenVerifiedStatus(SessionVerifiedStatus.NotVerified) givenVerifiedStatus(SessionVerifiedStatus.NotVerified)
}, },
FakeNetworkMonitor(),
SnackbarDispatcher(),
FakeInviteDataSource(),
LeaveRoomPresenterFake(),
) )
moleculeFlow(RecompositionClock.Immediate) { moleculeFlow(RecompositionClock.Immediate) {
presenter.present() presenter.present()
@ -281,22 +226,12 @@ class RoomListPresenterTests {
@Test @Test
fun `present - sets invite state`() = runTest { fun `present - sets invite state`() = runTest {
val inviteStateFlow = MutableStateFlow(InvitesState.NoInvites) val inviteStateFlow = MutableStateFlow(InvitesState.NoInvites)
val matrixClient = FakeMatrixClient() val inviteStateDataSource = FakeInviteDataSource(inviteStateFlow)
val presenter = RoomListPresenter( val presenter = aRoomListPresenter(inviteStateDataSource = inviteStateDataSource)
matrixClient,
createDateFormatter(),
FakeRoomLastMessageFormatter(),
FakeSessionVerificationService(),
FakeNetworkMonitor(),
SnackbarDispatcher(),
FakeInviteDataSource(inviteStateFlow),
LeaveRoomPresenterFake(),
)
moleculeFlow(RecompositionClock.Immediate) { moleculeFlow(RecompositionClock.Immediate) {
presenter.present() presenter.present()
}.test { }.test {
skipItems(1) skipItems(1)
Truth.assertThat(awaitItem().invitesState).isEqualTo(InvitesState.NoInvites) Truth.assertThat(awaitItem().invitesState).isEqualTo(InvitesState.NoInvites)
inviteStateFlow.value = InvitesState.SeenInvites inviteStateFlow.value = InvitesState.SeenInvites
@ -312,17 +247,7 @@ class RoomListPresenterTests {
@Test @Test
fun `present - show context menu`() = runTest { fun `present - show context menu`() = runTest {
val matrixClient = FakeMatrixClient() val presenter = aRoomListPresenter()
val presenter = RoomListPresenter(
matrixClient,
createDateFormatter(),
FakeRoomLastMessageFormatter(),
FakeSessionVerificationService(),
FakeNetworkMonitor(),
SnackbarDispatcher(),
FakeInviteDataSource(),
LeaveRoomPresenterFake(),
)
moleculeFlow(RecompositionClock.Immediate) { moleculeFlow(RecompositionClock.Immediate) {
presenter.present() presenter.present()
}.test { }.test {
@ -340,17 +265,7 @@ class RoomListPresenterTests {
@Test @Test
fun `present - hide context menu`() = runTest { fun `present - hide context menu`() = runTest {
val matrixClient = FakeMatrixClient() val presenter = aRoomListPresenter()
val presenter = RoomListPresenter(
matrixClient,
createDateFormatter(),
FakeRoomLastMessageFormatter(),
FakeSessionVerificationService(),
FakeNetworkMonitor(),
SnackbarDispatcher(),
FakeInviteDataSource(),
LeaveRoomPresenterFake(),
)
moleculeFlow(RecompositionClock.Immediate) { moleculeFlow(RecompositionClock.Immediate) {
presenter.present() presenter.present()
}.test { }.test {
@ -373,34 +288,42 @@ class RoomListPresenterTests {
@Test @Test
fun `present - leave room calls into leave room presenter`() = runTest { fun `present - leave room calls into leave room presenter`() = runTest {
val leaveRoomPresenter = LeaveRoomPresenterFake() val leaveRoomPresenter = LeaveRoomPresenterFake()
val matrixClient = FakeMatrixClient() val presenter = aRoomListPresenter(leaveRoomPresenter = leaveRoomPresenter)
val presenter = RoomListPresenter(
matrixClient,
createDateFormatter(),
FakeRoomLastMessageFormatter(),
FakeSessionVerificationService(),
FakeNetworkMonitor(),
SnackbarDispatcher(),
FakeInviteDataSource(),
leaveRoomPresenter,
)
moleculeFlow(RecompositionClock.Immediate) { moleculeFlow(RecompositionClock.Immediate) {
presenter.present() presenter.present()
}.test { }.test {
skipItems(1)
val initialState = awaitItem() val initialState = awaitItem()
initialState.eventSink(RoomListEvents.LeaveRoom(A_ROOM_ID)) initialState.eventSink(RoomListEvents.LeaveRoom(A_ROOM_ID))
Truth.assertThat(leaveRoomPresenter.events).containsExactly(LeaveRoomEvent.ShowConfirmation(A_ROOM_ID)) Truth.assertThat(leaveRoomPresenter.events).containsExactly(LeaveRoomEvent.ShowConfirmation(A_ROOM_ID))
cancelAndIgnoreRemainingEvents()
} }
} }
private fun createDateFormatter(): LastMessageTimestampFormatter { private fun TestScope.aRoomListPresenter(
return FakeLastMessageTimestampFormatter().apply { 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) 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" 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
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
import io.element.android.libraries.di.SessionScope import io.element.android.libraries.di.SessionScope
import io.element.android.libraries.di.SingleIn
import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.media.MatrixMediaLoader 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.RoomMembershipObserver
import io.element.android.libraries.matrix.api.room.RoomSummaryDataSource
import io.element.android.libraries.matrix.api.verification.SessionVerificationService import io.element.android.libraries.matrix.api.verification.SessionVerificationService
@Module @Module
@ContributesTo(SessionScope::class) @ContributesTo(SessionScope::class)
object SessionMatrixModule { object SessionMatrixModule {
@Provides @Provides
@SingleIn(SessionScope::class)
fun providesSessionVerificationService(matrixClient: MatrixClient): SessionVerificationService { fun providesSessionVerificationService(matrixClient: MatrixClient): SessionVerificationService {
return matrixClient.sessionVerificationService() return matrixClient.sessionVerificationService()
} }
@Provides @Provides
@SingleIn(SessionScope::class)
fun provideRoomMembershipObserver(matrixClient: MatrixClient): RoomMembershipObserver { fun provideRoomMembershipObserver(matrixClient: MatrixClient): RoomMembershipObserver {
return matrixClient.roomMembershipObserver() return matrixClient.roomMembershipObserver()
} }
@Provides @Provides
@SingleIn(SessionScope::class) fun provideRoomSummaryDataSource(matrixClient: MatrixClient): RoomSummaryDataSource {
return matrixClient.roomSummaryDataSource
}
@Provides
fun provideMediaLoader(matrixClient: MatrixClient): MatrixMediaLoader { fun provideMediaLoader(matrixClient: MatrixClient): MatrixMediaLoader {
return matrixClient.mediaLoader 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
import io.element.android.features.invitelist.impl.DefaultSeenInvitesStore import io.element.android.features.invitelist.impl.DefaultSeenInvitesStore
import io.element.android.features.leaveroom.impl.LeaveRoomPresenterImpl import io.element.android.features.leaveroom.impl.LeaveRoomPresenterImpl
import io.element.android.features.networkmonitor.impl.NetworkMonitorImpl 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.RoomListPresenter
import io.element.android.features.roomlist.impl.RoomListView import io.element.android.features.roomlist.impl.RoomListView
import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.core.coroutine.CoroutineDispatchers

Loading…
Cancel
Save