Merge pull request #6117 from element-hq/feature/fga/room_list_filter_rust
Refactor room list filtering to use Rust SDK
This commit is contained in:
commit
440eacc892
43 changed files with 648 additions and 613 deletions
|
|
@ -23,11 +23,7 @@ import androidx.compose.foundation.lazy.LazyListState
|
|||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberUpdatedState
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
|
|
@ -55,6 +51,7 @@ import io.element.android.libraries.designsystem.theme.components.Button
|
|||
import io.element.android.libraries.designsystem.theme.components.HorizontalDivider
|
||||
import io.element.android.libraries.designsystem.theme.components.IconSource
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.libraries.designsystem.utils.OnVisibleRangeChangeEffect
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
|
|
@ -215,17 +212,8 @@ private fun RoomsViewList(
|
|||
lazyListState: LazyListState,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val visibleRange by remember {
|
||||
derivedStateOf {
|
||||
val layoutInfo = lazyListState.layoutInfo
|
||||
val firstItemIndex = layoutInfo.visibleItemsInfo.firstOrNull()?.index ?: 0
|
||||
val size = layoutInfo.visibleItemsInfo.size
|
||||
firstItemIndex until firstItemIndex + size
|
||||
}
|
||||
}
|
||||
val updatedEventSink by rememberUpdatedState(newValue = eventSink)
|
||||
LaunchedEffect(visibleRange) {
|
||||
updatedEventSink(RoomListEvent.UpdateVisibleRange(visibleRange))
|
||||
OnVisibleRangeChangeEffect(lazyListState) { visibleRange ->
|
||||
eventSink(RoomListEvent.UpdateVisibleRange(visibleRange))
|
||||
}
|
||||
LazyColumn(
|
||||
state = lazyListState,
|
||||
|
|
@ -237,7 +225,7 @@ private fun RoomsViewList(
|
|||
item {
|
||||
SetUpRecoveryKeyBanner(
|
||||
onContinueClick = onSetUpRecoveryClick,
|
||||
onDismissClick = { updatedEventSink(RoomListEvent.DismissBanner) },
|
||||
onDismissClick = { eventSink(RoomListEvent.DismissBanner) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -245,7 +233,7 @@ private fun RoomsViewList(
|
|||
item {
|
||||
ConfirmRecoveryKeyBanner(
|
||||
onContinueClick = onConfirmRecoveryKeyClick,
|
||||
onDismissClick = { updatedEventSink(RoomListEvent.DismissBanner) },
|
||||
onDismissClick = { eventSink(RoomListEvent.DismissBanner) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -260,7 +248,7 @@ private fun RoomsViewList(
|
|||
} else if (state.showNewNotificationSoundBanner) {
|
||||
item {
|
||||
NewNotificationSoundBanner(
|
||||
onDismissClick = { updatedEventSink(RoomListEvent.DismissNewNotificationSoundBanner) },
|
||||
onDismissClick = { eventSink(RoomListEvent.DismissNewNotificationSoundBanner) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,33 +9,48 @@
|
|||
package io.element.android.features.home.impl.datasource
|
||||
|
||||
import dev.zacsweers.metro.Inject
|
||||
import dev.zacsweers.metro.SingleIn
|
||||
import io.element.android.features.home.impl.model.RoomListRoomSummary
|
||||
import io.element.android.libraries.androidutils.diff.DiffCacheUpdater
|
||||
import io.element.android.libraries.androidutils.diff.MutableListDiffCache
|
||||
import io.element.android.libraries.androidutils.system.DateTimeObserver
|
||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||
import io.element.android.libraries.di.SessionScope
|
||||
import io.element.android.libraries.di.annotations.SessionCoroutineScope
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService
|
||||
import io.element.android.libraries.matrix.api.roomlist.RoomList
|
||||
import io.element.android.libraries.matrix.api.roomlist.RoomListFilter
|
||||
import io.element.android.libraries.matrix.api.roomlist.RoomListService
|
||||
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
|
||||
import io.element.android.libraries.matrix.api.roomlist.updateVisibleRange
|
||||
import io.element.android.services.analytics.api.AnalyticsService
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.FlowPreview
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.debounce
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.lang.IllegalStateException
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
private const val PAGE_SIZE = 20
|
||||
private const val EXTENDED_VISIBILITY_RANGE_SIZE = 40
|
||||
private const val SUBSCRIBE_TO_VISIBLE_ROOMS_DEBOUNCE_IN_MILLIS = 300L
|
||||
private const val PAGINATION_THRESHOLD = 3 * PAGE_SIZE
|
||||
|
||||
@Inject
|
||||
@SingleIn(SessionScope::class)
|
||||
class RoomListDataSource(
|
||||
private val roomListService: RoomListService,
|
||||
private val roomListRoomSummaryFactory: RoomListRoomSummaryFactory,
|
||||
|
|
@ -51,7 +66,12 @@ class RoomListDataSource(
|
|||
observeDateTimeChanges()
|
||||
}
|
||||
|
||||
private val _allRooms = MutableSharedFlow<ImmutableList<RoomListRoomSummary>>(replay = 1)
|
||||
private val roomList = roomListService.createRoomList(
|
||||
pageSize = PAGE_SIZE,
|
||||
source = RoomList.Source.All,
|
||||
coroutineScope = sessionCoroutineScope
|
||||
)
|
||||
private val _roomSummariesFlow = MutableSharedFlow<ImmutableList<RoomListRoomSummary>>(replay = 1)
|
||||
|
||||
private val lock = Mutex()
|
||||
private val diffCache = MutableListDiffCache<RoomListRoomSummary>()
|
||||
|
|
@ -59,22 +79,49 @@ class RoomListDataSource(
|
|||
old?.roomId == new?.roomId
|
||||
}
|
||||
|
||||
val allRooms: Flow<ImmutableList<RoomListRoomSummary>> = _allRooms
|
||||
val roomSummariesFlow: Flow<ImmutableList<RoomListRoomSummary>> = _roomSummariesFlow
|
||||
|
||||
val loadingState = roomListService.allRooms.loadingState
|
||||
val loadingState = roomList.loadingState
|
||||
|
||||
fun launchIn(coroutineScope: CoroutineScope) {
|
||||
roomListService
|
||||
.allRooms
|
||||
.filteredSummaries
|
||||
roomList
|
||||
.summaries
|
||||
.onEach { roomSummaries ->
|
||||
replaceWith(roomSummaries)
|
||||
}
|
||||
.launchIn(coroutineScope)
|
||||
}
|
||||
|
||||
suspend fun subscribeToVisibleRooms(roomIds: List<RoomId>) {
|
||||
roomListService.subscribeToVisibleRooms(roomIds)
|
||||
suspend fun updateFilter(filter: RoomListFilter) {
|
||||
roomList.updateFilter(filter)
|
||||
}
|
||||
|
||||
suspend fun updateVisibleRange(visibleRange: IntRange) = coroutineScope {
|
||||
launch {
|
||||
roomList.updateVisibleRange(visibleRange, PAGINATION_THRESHOLD)
|
||||
}
|
||||
launch {
|
||||
subscribeToVisibleRoomsIfNeeded(visibleRange)
|
||||
}
|
||||
}
|
||||
|
||||
private var currentSubscribeToVisibleRoomsJob: Job? = null
|
||||
private fun CoroutineScope.subscribeToVisibleRoomsIfNeeded(range: IntRange) {
|
||||
currentSubscribeToVisibleRoomsJob?.cancel()
|
||||
currentSubscribeToVisibleRoomsJob = launch {
|
||||
// Debounce the subscription to avoid subscribing to too many rooms
|
||||
delay(SUBSCRIBE_TO_VISIBLE_ROOMS_DEBOUNCE_IN_MILLIS)
|
||||
|
||||
if (range.isEmpty()) return@launch
|
||||
val currentRoomList = roomSummariesFlow.first()
|
||||
// Use extended range to 'prefetch' the next rooms info
|
||||
val midExtendedRangeSize = EXTENDED_VISIBILITY_RANGE_SIZE / 2
|
||||
val extendedRange = range.first until range.last + midExtendedRangeSize
|
||||
val roomIds = extendedRange.mapNotNull { index ->
|
||||
currentRoomList.getOrNull(index)?.roomId
|
||||
}
|
||||
roomListService.subscribeToVisibleRooms(roomIds)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(FlowPreview::class)
|
||||
|
|
@ -82,7 +129,7 @@ class RoomListDataSource(
|
|||
notificationSettingsService.notificationSettingsChangeFlow
|
||||
.debounce(0.5.seconds)
|
||||
.onEach {
|
||||
roomListService.allRooms.rebuildSummaries()
|
||||
roomList.rebuildSummaries()
|
||||
}
|
||||
.launchIn(sessionCoroutineScope)
|
||||
}
|
||||
|
|
@ -108,6 +155,7 @@ class RoomListDataSource(
|
|||
private suspend fun buildAndEmitAllRooms(roomSummaries: List<RoomSummary>, useCache: Boolean = true) {
|
||||
// Used to detect duplicates in the room list summaries - see comment below
|
||||
data class CacheResult(val index: Int, val fromCache: Boolean)
|
||||
|
||||
val cachingResults = mutableMapOf<RoomId, MutableList<CacheResult>>()
|
||||
|
||||
val roomListRoomSummaries = diffCache.indices().mapNotNull { index ->
|
||||
|
|
@ -144,14 +192,14 @@ class RoomListDataSource(
|
|||
analyticsService.trackError(
|
||||
IllegalStateException(
|
||||
"Found duplicates in room summaries after a local UI update: $duplicates. " +
|
||||
"This could be a race condition/caching issue of some kind"
|
||||
"This could be a race condition/caching issue of some kind"
|
||||
)
|
||||
)
|
||||
|
||||
// Remove duplicates before emitting the new values
|
||||
_allRooms.emit(roomListRoomSummaries.distinctBy { it.roomId }.toImmutableList())
|
||||
_roomSummariesFlow.emit(roomListRoomSummaries.distinctBy { it.roomId }.toImmutableList())
|
||||
} else {
|
||||
_allRooms.emit(roomListRoomSummaries.toImmutableList())
|
||||
_roomSummariesFlow.emit(roomListRoomSummaries.toImmutableList())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -163,7 +211,7 @@ class RoomListDataSource(
|
|||
|
||||
private suspend fun rebuildAllRoomSummaries() {
|
||||
lock.withLock {
|
||||
roomListService.allRooms.filteredSummaries.replayCache.firstOrNull()?.let { roomSummaries ->
|
||||
roomList.summaries.replayCache.firstOrNull()?.let { roomSummaries ->
|
||||
buildAndEmitAllRooms(roomSummaries, useCache = false)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,16 +12,17 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.produceState
|
||||
import dev.zacsweers.metro.Inject
|
||||
import io.element.android.features.home.impl.datasource.RoomListDataSource
|
||||
import io.element.android.features.home.impl.filters.selection.FilterSelectionStrategy
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.matrix.api.roomlist.RoomListService
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.map
|
||||
import io.element.android.libraries.matrix.api.roomlist.RoomListFilter as MatrixRoomListFilter
|
||||
|
||||
@Inject
|
||||
class RoomListFiltersPresenter(
|
||||
private val roomListService: RoomListService,
|
||||
private val roomListDataSource: RoomListDataSource,
|
||||
private val filterSelectionStrategy: FilterSelectionStrategy,
|
||||
) : Presenter<RoomListFiltersState> {
|
||||
private val initialFilters = filterSelectionStrategy.filterSelectionStates.value.toImmutableList()
|
||||
|
|
@ -56,9 +57,9 @@ class RoomListFiltersPresenter(
|
|||
}
|
||||
}
|
||||
}
|
||||
.collect { filters ->
|
||||
.collectLatest { filters ->
|
||||
val result = MatrixRoomListFilter.All(filters)
|
||||
roomListService.allRooms.updateFilter(result)
|
||||
roomListDataSource.updateFilter(result)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -57,8 +57,6 @@ import kotlinx.collections.immutable.toImmutableList
|
|||
import kotlinx.collections.immutable.toImmutableSet
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.first
|
||||
|
|
@ -68,9 +66,6 @@ import kotlinx.coroutines.flow.onEach
|
|||
import kotlinx.coroutines.flow.takeWhile
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
private const val EXTENDED_RANGE_SIZE = 40
|
||||
private const val SUBSCRIBE_TO_VISIBLE_ROOMS_DEBOUNCE_IN_MILLIS = 300L
|
||||
|
||||
@Inject
|
||||
class RoomListPresenter(
|
||||
private val client: MatrixClient,
|
||||
|
|
@ -119,7 +114,7 @@ class RoomListPresenter(
|
|||
fun handleEvent(event: RoomListEvent) {
|
||||
when (event) {
|
||||
is RoomListEvent.UpdateVisibleRange -> coroutineScope.launch {
|
||||
updateVisibleRange(event.range)
|
||||
roomListDataSource.updateVisibleRange(event.range)
|
||||
}
|
||||
RoomListEvent.DismissRequestVerificationPrompt -> securityBannerDismissed = true
|
||||
RoomListEvent.DismissBanner -> securityBannerDismissed = true
|
||||
|
|
@ -217,7 +212,7 @@ class RoomListPresenter(
|
|||
showNewNotificationSoundBanner: Boolean,
|
||||
): RoomListContentState {
|
||||
val roomSummaries by produceState(initialValue = AsyncData.Loading()) {
|
||||
roomListDataSource.allRooms.collect { value = AsyncData.Success(it) }
|
||||
roomListDataSource.roomSummariesFlow.collect { value = AsyncData.Success(it) }
|
||||
}
|
||||
val loadingState by roomListDataSource.loadingState.collectAsState()
|
||||
val showEmpty by remember {
|
||||
|
|
@ -322,23 +317,4 @@ class RoomListPresenter(
|
|||
room.clearEventCacheStorage()
|
||||
}
|
||||
}
|
||||
|
||||
private var currentUpdateVisibleRangeJob: Job? = null
|
||||
private fun CoroutineScope.updateVisibleRange(range: IntRange) {
|
||||
currentUpdateVisibleRangeJob?.cancel()
|
||||
currentUpdateVisibleRangeJob = launch {
|
||||
// Debounce the subscription to avoid subscribing to too many rooms
|
||||
delay(SUBSCRIBE_TO_VISIBLE_ROOMS_DEBOUNCE_IN_MILLIS)
|
||||
|
||||
if (range.isEmpty()) return@launch
|
||||
val currentRoomList = roomListDataSource.allRooms.first()
|
||||
// Use extended range to 'prefetch' the next rooms info
|
||||
val midExtendedRangeSize = EXTENDED_RANGE_SIZE / 2
|
||||
val extendedRange = range.first until range.last + midExtendedRangeSize
|
||||
val roomIds = extendedRange.mapNotNull { index ->
|
||||
currentRoomList.getOrNull(index)?.roomId
|
||||
}
|
||||
roomListDataSource.subscribeToVisibleRooms(roomIds)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
|||
import io.element.android.libraries.matrix.api.roomlist.RoomList
|
||||
import io.element.android.libraries.matrix.api.roomlist.RoomListFilter
|
||||
import io.element.android.libraries.matrix.api.roomlist.RoomListService
|
||||
import io.element.android.libraries.matrix.api.roomlist.loadAllIncrementally
|
||||
import io.element.android.libraries.matrix.api.roomlist.updateVisibleRange
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
|
|
@ -42,12 +42,11 @@ class RoomListSearchDataSource(
|
|||
|
||||
private val roomList = roomListService.createRoomList(
|
||||
pageSize = PAGE_SIZE,
|
||||
initialFilter = RoomListFilter.None,
|
||||
source = RoomList.Source.All,
|
||||
coroutineScope = coroutineScope
|
||||
)
|
||||
|
||||
val roomSummaries: Flow<ImmutableList<RoomListRoomSummary>> = roomList.filteredSummaries
|
||||
val roomSummaries: Flow<ImmutableList<RoomListRoomSummary>> = roomList.summaries
|
||||
.map { roomSummaries ->
|
||||
roomSummaries
|
||||
.map(roomSummaryFactory::create)
|
||||
|
|
@ -55,12 +54,8 @@ class RoomListSearchDataSource(
|
|||
}
|
||||
.flowOn(coroutineDispatchers.computation)
|
||||
|
||||
suspend fun setIsActive(isActive: Boolean) = coroutineScope {
|
||||
if (isActive) {
|
||||
roomList.loadAllIncrementally(this)
|
||||
} else {
|
||||
roomList.reset()
|
||||
}
|
||||
suspend fun updateVisibleRange(visibleRange: IntRange) {
|
||||
roomList.updateVisibleRange(visibleRange)
|
||||
}
|
||||
|
||||
suspend fun setSearchQuery(searchQuery: String) = coroutineScope {
|
||||
|
|
|
|||
|
|
@ -11,4 +11,5 @@ package io.element.android.features.home.impl.search
|
|||
sealed interface RoomListSearchEvent {
|
||||
data object ToggleSearchVisibility : RoomListSearchEvent
|
||||
data object ClearQuery : RoomListSearchEvent
|
||||
data class UpdateVisibleRange(val range: IntRange) : RoomListSearchEvent
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import androidx.compose.runtime.setValue
|
|||
import dev.zacsweers.metro.Inject
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Inject
|
||||
class RoomListSearchPresenter(
|
||||
|
|
@ -37,10 +38,6 @@ class RoomListSearchPresenter(
|
|||
val coroutineScope = rememberCoroutineScope()
|
||||
val dataSource = remember { dataSourceFactory.create(coroutineScope) }
|
||||
|
||||
LaunchedEffect(isSearchActive) {
|
||||
dataSource.setIsActive(isSearchActive)
|
||||
}
|
||||
|
||||
LaunchedEffect(searchQuery.text) {
|
||||
dataSource.setSearchQuery(searchQuery.text.toString())
|
||||
}
|
||||
|
|
@ -54,6 +51,9 @@ class RoomListSearchPresenter(
|
|||
isSearchActive = !isSearchActive
|
||||
searchQuery.clearText()
|
||||
}
|
||||
is RoomListSearchEvent.UpdateVisibleRange -> coroutineScope.launch {
|
||||
dataSource.updateVisibleRange(visibleRange = event.range)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
|||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.foundation.text.input.TextFieldLineLimits
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
|
|
@ -47,6 +48,7 @@ import io.element.android.libraries.designsystem.theme.components.Icon
|
|||
import io.element.android.libraries.designsystem.theme.components.IconButton
|
||||
import io.element.android.libraries.designsystem.theme.components.Scaffold
|
||||
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
||||
import io.element.android.libraries.designsystem.utils.OnVisibleRangeChangeEffect
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
|
||||
|
|
@ -154,7 +156,12 @@ private fun RoomListSearchContent(
|
|||
.padding(padding)
|
||||
.consumeWindowInsets(padding)
|
||||
) {
|
||||
val lazyListState = rememberLazyListState()
|
||||
OnVisibleRangeChangeEffect(lazyListState) { visibleRange ->
|
||||
state.eventSink(RoomListSearchEvent.UpdateVisibleRange(visibleRange))
|
||||
}
|
||||
LazyColumn(
|
||||
state = lazyListState,
|
||||
modifier = Modifier.weight(1f),
|
||||
) {
|
||||
items(
|
||||
|
|
|
|||
|
|
@ -16,9 +16,11 @@ import io.element.android.libraries.dateformatter.test.FakeDateFormatter
|
|||
import io.element.android.libraries.matrix.api.roomlist.RoomListService
|
||||
import io.element.android.libraries.matrix.test.notificationsettings.FakeNotificationSettingsService
|
||||
import io.element.android.libraries.matrix.test.room.aRoomSummary
|
||||
import io.element.android.libraries.matrix.test.roomlist.FakeDynamicRoomList
|
||||
import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService
|
||||
import io.element.android.services.analytics.test.FakeAnalyticsService
|
||||
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
|
||||
|
|
@ -27,9 +29,13 @@ import java.time.Instant
|
|||
class RoomListDataSourceTest {
|
||||
@Test
|
||||
fun `when DateTimeObserver gets a date change, the room summaries are refreshed`() = runTest {
|
||||
val roomListService = FakeRoomListService().apply {
|
||||
val roomList = FakeDynamicRoomList().apply {
|
||||
summaries.emit(listOf(aRoomSummary()))
|
||||
}
|
||||
val roomListService = FakeRoomListService(
|
||||
createRoomListLambda = { roomList }
|
||||
).apply {
|
||||
postState(RoomListService.State.Running)
|
||||
postAllRooms(listOf(aRoomSummary()))
|
||||
}
|
||||
val dateTimeObserver = FakeDateTimeObserver()
|
||||
var dateFormatterResult = "Today"
|
||||
|
|
@ -42,7 +48,7 @@ class RoomListDataSourceTest {
|
|||
dateTimeObserver = dateTimeObserver,
|
||||
)
|
||||
|
||||
roomListDataSource.allRooms.test {
|
||||
roomListDataSource.roomSummariesFlow.test {
|
||||
// Observe room list items changes
|
||||
roomListDataSource.launchIn(backgroundScope)
|
||||
// Get the initial room list
|
||||
|
|
@ -61,9 +67,11 @@ class RoomListDataSourceTest {
|
|||
|
||||
@Test
|
||||
fun `when DateTimeObserver gets a time zone change, the room summaries are refreshed`() = runTest {
|
||||
val roomListService = FakeRoomListService().apply {
|
||||
val roomList = FakeDynamicRoomList(summaries = MutableStateFlow(listOf(aRoomSummary())))
|
||||
val roomListService = FakeRoomListService(
|
||||
createRoomListLambda = { roomList }
|
||||
).apply {
|
||||
postState(RoomListService.State.Running)
|
||||
postAllRooms(listOf(aRoomSummary()))
|
||||
}
|
||||
val dateTimeObserver = FakeDateTimeObserver()
|
||||
var dateFormatterResult = "Today"
|
||||
|
|
@ -75,7 +83,7 @@ class RoomListDataSourceTest {
|
|||
),
|
||||
dateTimeObserver = dateTimeObserver,
|
||||
)
|
||||
roomListDataSource.allRooms.test {
|
||||
roomListDataSource.roomSummariesFlow.test {
|
||||
// Observe room list items changes
|
||||
roomListDataSource.launchIn(backgroundScope)
|
||||
// Get the initial room list
|
||||
|
|
|
|||
|
|
@ -9,15 +9,28 @@
|
|||
package io.element.android.features.home.impl.filters
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.features.home.impl.FakeDateTimeObserver
|
||||
import io.element.android.features.home.impl.datasource.RoomListDataSource
|
||||
import io.element.android.features.home.impl.datasource.aRoomListRoomSummaryFactory
|
||||
import io.element.android.features.home.impl.filters.selection.DefaultFilterSelectionStrategy
|
||||
import io.element.android.features.home.impl.filters.selection.FilterSelectionState
|
||||
import io.element.android.libraries.dateformatter.api.DateFormatter
|
||||
import io.element.android.libraries.dateformatter.test.FakeDateFormatter
|
||||
import io.element.android.libraries.eventformatter.api.RoomLatestEventFormatter
|
||||
import io.element.android.libraries.eventformatter.test.FakeRoomLatestEventFormatter
|
||||
import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService
|
||||
import io.element.android.libraries.matrix.api.roomlist.RoomListService
|
||||
import io.element.android.libraries.matrix.test.notificationsettings.FakeNotificationSettingsService
|
||||
import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService
|
||||
import io.element.android.services.analytics.test.FakeAnalyticsService
|
||||
import io.element.android.tests.testutils.awaitLastSequentialItem
|
||||
import io.element.android.tests.testutils.test
|
||||
import io.element.android.tests.testutils.testCoroutineDispatchers
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
import kotlinx.coroutines.test.advanceUntilIdle
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
import io.element.android.libraries.matrix.api.roomlist.RoomListFilter as MatrixRoomListFilter
|
||||
|
||||
class RoomListFiltersPresenterTest {
|
||||
@Test
|
||||
|
|
@ -39,13 +52,13 @@ class RoomListFiltersPresenterTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
fun `present - toggle rooms filter`() = runTest {
|
||||
val roomListService = FakeRoomListService()
|
||||
val presenter = createRoomListFiltersPresenter(roomListService)
|
||||
presenter.test {
|
||||
awaitItem().eventSink.invoke(RoomListFiltersEvent.ToggleFilter(RoomListFilter.Rooms))
|
||||
awaitLastSequentialItem().let { state ->
|
||||
|
||||
assertThat(state.hasAnyFilterSelected).isTrue()
|
||||
assertThat(state.filterSelectionStates).containsExactly(
|
||||
filterSelectionState(RoomListFilter.Rooms, true),
|
||||
|
|
@ -56,12 +69,9 @@ class RoomListFiltersPresenterTest {
|
|||
assertThat(state.selectedFilters()).containsExactly(
|
||||
RoomListFilter.Rooms,
|
||||
)
|
||||
val roomListCurrentFilter = roomListService.allRooms.currentFilter.value as MatrixRoomListFilter.All
|
||||
assertThat(roomListCurrentFilter.filters).containsExactly(
|
||||
MatrixRoomListFilter.Category.Group,
|
||||
)
|
||||
state.eventSink.invoke(RoomListFiltersEvent.ToggleFilter(RoomListFilter.Rooms))
|
||||
}
|
||||
advanceUntilIdle()
|
||||
awaitLastSequentialItem().let { state ->
|
||||
assertThat(state.hasAnyFilterSelected).isFalse()
|
||||
assertThat(state.filterSelectionStates).containsExactly(
|
||||
|
|
@ -72,13 +82,12 @@ class RoomListFiltersPresenterTest {
|
|||
filterSelectionState(RoomListFilter.Invites, false),
|
||||
).inOrder()
|
||||
assertThat(state.selectedFilters()).isEmpty()
|
||||
val roomListCurrentFilter = roomListService.allRooms.currentFilter.value as MatrixRoomListFilter.All
|
||||
assertThat(roomListCurrentFilter.filters).isEmpty()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
fun `present - clear filters event`() = runTest {
|
||||
val roomListService = FakeRoomListService()
|
||||
val presenter = createRoomListFiltersPresenter(roomListService)
|
||||
|
|
@ -88,6 +97,7 @@ class RoomListFiltersPresenterTest {
|
|||
assertThat(state.hasAnyFilterSelected).isTrue()
|
||||
state.eventSink.invoke(RoomListFiltersEvent.ClearSelectedFilters)
|
||||
}
|
||||
advanceUntilIdle()
|
||||
awaitLastSequentialItem().let { state ->
|
||||
assertThat(state.hasAnyFilterSelected).isFalse()
|
||||
}
|
||||
|
|
@ -100,11 +110,25 @@ private fun filterSelectionState(filter: RoomListFilter, selected: Boolean) = Fi
|
|||
isSelected = selected,
|
||||
)
|
||||
|
||||
private fun createRoomListFiltersPresenter(
|
||||
private fun TestScope.createRoomListFiltersPresenter(
|
||||
roomListService: RoomListService = FakeRoomListService(),
|
||||
notificationSettingsService: NotificationSettingsService = FakeNotificationSettingsService(),
|
||||
dateFormatter: DateFormatter = FakeDateFormatter(),
|
||||
roomLatestEventFormatter: RoomLatestEventFormatter = FakeRoomLatestEventFormatter(),
|
||||
): RoomListFiltersPresenter {
|
||||
return RoomListFiltersPresenter(
|
||||
roomListService = roomListService,
|
||||
roomListDataSource = RoomListDataSource(
|
||||
roomListService = roomListService,
|
||||
roomListRoomSummaryFactory = aRoomListRoomSummaryFactory(
|
||||
dateFormatter = dateFormatter,
|
||||
roomLatestEventFormatter = roomLatestEventFormatter,
|
||||
),
|
||||
coroutineDispatchers = testCoroutineDispatchers(),
|
||||
notificationSettingsService = notificationSettingsService,
|
||||
sessionCoroutineScope = backgroundScope,
|
||||
dateTimeObserver = FakeDateTimeObserver(),
|
||||
analyticsService = FakeAnalyticsService(),
|
||||
),
|
||||
filterSelectionStrategy = DefaultFilterSelectionStrategy(),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@ import io.element.android.libraries.matrix.test.room.FakeBaseRoom
|
|||
import io.element.android.libraries.matrix.test.room.aRoomInfo
|
||||
import io.element.android.libraries.matrix.test.room.aRoomMember
|
||||
import io.element.android.libraries.matrix.test.room.aRoomSummary
|
||||
import io.element.android.libraries.matrix.test.roomlist.FakeDynamicRoomList
|
||||
import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService
|
||||
import io.element.android.libraries.matrix.test.sync.FakeSyncService
|
||||
import io.element.android.libraries.matrix.test.verification.FakeSessionVerificationService
|
||||
|
|
@ -77,6 +78,7 @@ import io.element.android.tests.testutils.lambda.value
|
|||
import io.element.android.tests.testutils.test
|
||||
import io.element.android.tests.testutils.testCoroutineDispatchers
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
import kotlinx.coroutines.test.advanceTimeBy
|
||||
|
|
@ -91,7 +93,10 @@ class RoomListPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `present - load 1 room with success`() = runTest {
|
||||
val roomListService = FakeRoomListService()
|
||||
val roomList = FakeDynamicRoomList()
|
||||
val roomListService = FakeRoomListService(
|
||||
createRoomListLambda = { roomList }
|
||||
)
|
||||
val matrixClient = FakeMatrixClient(
|
||||
roomListService = roomListService
|
||||
)
|
||||
|
|
@ -102,8 +107,8 @@ class RoomListPresenterTest {
|
|||
presenter.test {
|
||||
val initialState = consumeItemsUntilPredicate { state -> state.contentState is RoomListContentState.Skeleton }.last()
|
||||
assertThat(initialState.contentState).isInstanceOf(RoomListContentState.Skeleton::class.java)
|
||||
roomListService.postAllRoomsLoadingState(RoomList.LoadingState.Loaded(1))
|
||||
roomListService.postAllRooms(
|
||||
roomList.loadingState.emit(RoomList.LoadingState.Loaded(1))
|
||||
roomList.summaries.emit(
|
||||
listOf(
|
||||
aRoomSummary(
|
||||
numUnreadMentions = 1,
|
||||
|
|
@ -128,9 +133,12 @@ class RoomListPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `present - handle DismissRequestVerificationPrompt`() = runTest {
|
||||
val roomListService = FakeRoomListService().apply {
|
||||
postAllRoomsLoadingState(RoomList.LoadingState.Loaded(1))
|
||||
}
|
||||
val roomList = FakeDynamicRoomList(
|
||||
loadingState = MutableStateFlow(RoomList.LoadingState.Loaded(1))
|
||||
)
|
||||
val roomListService = FakeRoomListService(
|
||||
createRoomListLambda = { roomList }
|
||||
)
|
||||
val encryptionService = FakeEncryptionService().apply {
|
||||
emitRecoveryState(RecoveryState.INCOMPLETE)
|
||||
}
|
||||
|
|
@ -154,9 +162,12 @@ class RoomListPresenterTest {
|
|||
val encryptionService = FakeEncryptionService().apply {
|
||||
recoveryStateStateFlow.emit(RecoveryState.DISABLED)
|
||||
}
|
||||
val roomListService = FakeRoomListService().apply {
|
||||
postAllRoomsLoadingState(RoomList.LoadingState.Loaded(1))
|
||||
}
|
||||
val roomList = FakeDynamicRoomList(
|
||||
loadingState = MutableStateFlow(RoomList.LoadingState.Loaded(1))
|
||||
)
|
||||
val roomListService = FakeRoomListService(
|
||||
createRoomListLambda = { roomList }
|
||||
)
|
||||
val matrixClient = FakeMatrixClient(
|
||||
roomListService = roomListService,
|
||||
encryptionService = encryptionService,
|
||||
|
|
@ -344,9 +355,13 @@ class RoomListPresenterTest {
|
|||
fun `present - change in notification settings updates the summary for decorations`() = runTest {
|
||||
val userDefinedMode = RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY
|
||||
val notificationSettingsService = FakeNotificationSettingsService()
|
||||
val roomListService = FakeRoomListService()
|
||||
roomListService.postAllRoomsLoadingState(RoomList.LoadingState.Loaded(1))
|
||||
roomListService.postAllRooms(listOf(aRoomSummary(userDefinedNotificationMode = userDefinedMode)))
|
||||
val roomList = FakeDynamicRoomList(
|
||||
summaries = MutableStateFlow(listOf(aRoomSummary(userDefinedNotificationMode = userDefinedMode))),
|
||||
loadingState = MutableStateFlow(RoomList.LoadingState.Loaded(1))
|
||||
)
|
||||
val roomListService = FakeRoomListService(
|
||||
createRoomListLambda = { roomList }
|
||||
)
|
||||
val matrixClient = FakeMatrixClient(
|
||||
roomListService = roomListService,
|
||||
notificationSettingsService = notificationSettingsService
|
||||
|
|
@ -397,8 +412,12 @@ class RoomListPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `present - when room service returns no room, then contentState is Empty`() = runTest {
|
||||
val roomListService = FakeRoomListService()
|
||||
roomListService.postAllRoomsLoadingState(RoomList.LoadingState.Loaded(0))
|
||||
val roomList = FakeDynamicRoomList(
|
||||
loadingState = MutableStateFlow(RoomList.LoadingState.Loaded(0))
|
||||
)
|
||||
val roomListService = FakeRoomListService(
|
||||
createRoomListLambda = { roomList }
|
||||
)
|
||||
val matrixClient = FakeMatrixClient(
|
||||
roomListService = roomListService,
|
||||
)
|
||||
|
|
@ -479,16 +498,21 @@ class RoomListPresenterTest {
|
|||
val acceptDeclinePresenter = Presenter {
|
||||
anAcceptDeclineInviteState(eventSink = eventSinkRecorder)
|
||||
}
|
||||
val roomListService = FakeRoomListService()
|
||||
val matrixClient = FakeMatrixClient(
|
||||
roomListService = roomListService,
|
||||
)
|
||||
|
||||
val roomSummary = aRoomSummary(
|
||||
currentUserMembership = CurrentUserMembership.INVITED,
|
||||
inviter = aRoomMember(),
|
||||
)
|
||||
roomListService.postAllRoomsLoadingState(RoomList.LoadingState.Loaded(1))
|
||||
roomListService.postAllRooms(listOf(roomSummary))
|
||||
val roomList = FakeDynamicRoomList(
|
||||
summaries = MutableStateFlow(listOf(roomSummary)),
|
||||
loadingState = MutableStateFlow(RoomList.LoadingState.Loaded(1))
|
||||
)
|
||||
val roomListService = FakeRoomListService(
|
||||
createRoomListLambda = { roomList }
|
||||
)
|
||||
val matrixClient = FakeMatrixClient(
|
||||
roomListService = roomListService,
|
||||
)
|
||||
val presenter = createRoomListPresenter(
|
||||
client = matrixClient,
|
||||
acceptDeclineInvitePresenter = acceptDeclinePresenter
|
||||
|
|
@ -519,15 +543,20 @@ class RoomListPresenterTest {
|
|||
@Test
|
||||
fun `present - UpdateVisibleRange will cancel the previous subscription if called too soon`() = runTest {
|
||||
val subscribeToVisibleRoomsLambda = lambdaRecorder { _: List<RoomId> -> }
|
||||
val roomListService = FakeRoomListService(subscribeToVisibleRoomsLambda = subscribeToVisibleRoomsLambda)
|
||||
val matrixClient = FakeMatrixClient(
|
||||
roomListService = roomListService,
|
||||
)
|
||||
val roomSummary = aRoomSummary(
|
||||
currentUserMembership = CurrentUserMembership.INVITED
|
||||
)
|
||||
roomListService.postAllRoomsLoadingState(RoomList.LoadingState.Loaded(1))
|
||||
roomListService.postAllRooms(listOf(roomSummary))
|
||||
val roomList = FakeDynamicRoomList(
|
||||
summaries = MutableStateFlow(listOf(roomSummary)),
|
||||
loadingState = MutableStateFlow(RoomList.LoadingState.Loaded(1))
|
||||
)
|
||||
val roomListService = FakeRoomListService(
|
||||
createRoomListLambda = { roomList },
|
||||
subscribeToVisibleRoomsLambda = subscribeToVisibleRoomsLambda,
|
||||
)
|
||||
val matrixClient = FakeMatrixClient(
|
||||
roomListService = roomListService,
|
||||
)
|
||||
val presenter = createRoomListPresenter(
|
||||
client = matrixClient,
|
||||
)
|
||||
|
|
@ -548,15 +577,20 @@ class RoomListPresenterTest {
|
|||
@Test
|
||||
fun `present - UpdateVisibleRange subscribes to rooms in visible range`() = runTest {
|
||||
val subscribeToVisibleRoomsLambda = lambdaRecorder { _: List<RoomId> -> }
|
||||
val roomListService = FakeRoomListService(subscribeToVisibleRoomsLambda = subscribeToVisibleRoomsLambda)
|
||||
val matrixClient = FakeMatrixClient(
|
||||
roomListService = roomListService,
|
||||
)
|
||||
val roomSummary = aRoomSummary(
|
||||
currentUserMembership = CurrentUserMembership.INVITED
|
||||
)
|
||||
roomListService.postAllRoomsLoadingState(RoomList.LoadingState.Loaded(1))
|
||||
roomListService.postAllRooms(listOf(roomSummary))
|
||||
val roomList = FakeDynamicRoomList(
|
||||
summaries = MutableStateFlow(listOf(roomSummary)),
|
||||
loadingState = MutableStateFlow(RoomList.LoadingState.Loaded(1))
|
||||
)
|
||||
val roomListService = FakeRoomListService(
|
||||
createRoomListLambda = { roomList },
|
||||
subscribeToVisibleRoomsLambda = subscribeToVisibleRoomsLambda,
|
||||
)
|
||||
val matrixClient = FakeMatrixClient(
|
||||
roomListService = roomListService,
|
||||
)
|
||||
val presenter = createRoomListPresenter(
|
||||
client = matrixClient,
|
||||
)
|
||||
|
|
@ -579,15 +613,20 @@ class RoomListPresenterTest {
|
|||
@Test
|
||||
fun `present - notification sound banner`() = runTest {
|
||||
val subscribeToVisibleRoomsLambda = lambdaRecorder { _: List<RoomId> -> }
|
||||
val roomListService = FakeRoomListService(subscribeToVisibleRoomsLambda = subscribeToVisibleRoomsLambda)
|
||||
val matrixClient = FakeMatrixClient(
|
||||
roomListService = roomListService,
|
||||
)
|
||||
val roomSummary = aRoomSummary(
|
||||
currentUserMembership = CurrentUserMembership.INVITED
|
||||
)
|
||||
roomListService.postAllRoomsLoadingState(RoomList.LoadingState.Loaded(1))
|
||||
roomListService.postAllRooms(listOf(roomSummary))
|
||||
val roomList = FakeDynamicRoomList(
|
||||
summaries = MutableStateFlow(listOf(roomSummary)),
|
||||
loadingState = MutableStateFlow(RoomList.LoadingState.Loaded(1))
|
||||
)
|
||||
val roomListService = FakeRoomListService(
|
||||
createRoomListLambda = { roomList },
|
||||
subscribeToVisibleRoomsLambda = subscribeToVisibleRoomsLambda,
|
||||
)
|
||||
val matrixClient = FakeMatrixClient(
|
||||
roomListService = roomListService,
|
||||
)
|
||||
val onAnnouncementDismissedResult = lambdaRecorder<Announcement, Unit> { }
|
||||
val announcementService = FakeAnnouncementService(
|
||||
onAnnouncementDismissedResult = onAnnouncementDismissedResult,
|
||||
|
|
|
|||
|
|
@ -15,7 +15,10 @@ import io.element.android.libraries.eventformatter.test.FakeRoomLatestEventForma
|
|||
import io.element.android.libraries.matrix.api.roomlist.RoomListFilter
|
||||
import io.element.android.libraries.matrix.api.roomlist.RoomListService
|
||||
import io.element.android.libraries.matrix.test.room.aRoomSummary
|
||||
import io.element.android.libraries.matrix.test.roomlist.FakeDynamicRoomList
|
||||
import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService
|
||||
import io.element.android.tests.testutils.lambda.assert
|
||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||
import io.element.android.tests.testutils.test
|
||||
import io.element.android.tests.testutils.testCoroutineDispatchers
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
|
|
@ -56,12 +59,15 @@ class RoomListSearchPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `present - query search changes`() = runTest {
|
||||
val roomListService = FakeRoomListService()
|
||||
val roomList = FakeDynamicRoomList()
|
||||
val roomListService = FakeRoomListService(
|
||||
createRoomListLambda = { roomList }
|
||||
)
|
||||
val presenter = createRoomListSearchPresenter(roomListService)
|
||||
presenter.test {
|
||||
awaitItem().let { state ->
|
||||
assertThat(
|
||||
roomListService.allRooms.currentFilter.value
|
||||
roomList.currentFilter.value
|
||||
).isEqualTo(
|
||||
RoomListFilter.None
|
||||
)
|
||||
|
|
@ -70,7 +76,7 @@ class RoomListSearchPresenterTest {
|
|||
awaitItem().let { state ->
|
||||
assertThat(state.query.text).isEqualTo("Search")
|
||||
assertThat(
|
||||
roomListService.allRooms.currentFilter.value
|
||||
roomList.currentFilter.value
|
||||
).isEqualTo(
|
||||
RoomListFilter.NormalizedMatchRoomName("Search")
|
||||
)
|
||||
|
|
@ -79,7 +85,7 @@ class RoomListSearchPresenterTest {
|
|||
awaitItem().let { state ->
|
||||
assertThat(state.query.text.toString()).isEmpty()
|
||||
assertThat(
|
||||
roomListService.allRooms.currentFilter.value
|
||||
roomList.currentFilter.value
|
||||
).isEqualTo(
|
||||
RoomListFilter.None
|
||||
)
|
||||
|
|
@ -89,24 +95,51 @@ class RoomListSearchPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `present - room list changes`() = runTest {
|
||||
val roomListService = FakeRoomListService()
|
||||
val roomList = FakeDynamicRoomList()
|
||||
val roomListService = FakeRoomListService(
|
||||
createRoomListLambda = { roomList }
|
||||
)
|
||||
val presenter = createRoomListSearchPresenter(roomListService)
|
||||
presenter.test {
|
||||
awaitItem().let { state ->
|
||||
assertThat(state.results).isEmpty()
|
||||
}
|
||||
roomListService.postAllRooms(
|
||||
roomList.summaries.emit(
|
||||
listOf(aRoomSummary())
|
||||
)
|
||||
awaitItem().let { state ->
|
||||
assertThat(state.results).hasSize(1)
|
||||
}
|
||||
roomListService.postAllRooms(emptyList())
|
||||
roomList.summaries.emit(emptyList())
|
||||
awaitItem().let { state ->
|
||||
assertThat(state.results).isEmpty()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - UpdateVisibleRange triggers pagination when near end`() = runTest {
|
||||
val loadMoreLambda = lambdaRecorder<Unit> { }
|
||||
val roomList = FakeDynamicRoomList(loadMoreLambda = loadMoreLambda)
|
||||
val roomListService = FakeRoomListService(
|
||||
createRoomListLambda = { roomList }
|
||||
)
|
||||
val presenter = createRoomListSearchPresenter(roomListService)
|
||||
presenter.test {
|
||||
val initialState = awaitItem()
|
||||
// Post some rooms to simulate loaded content
|
||||
val rooms = (1..10).map { aRoomSummary() }
|
||||
roomList.summaries.emit(rooms)
|
||||
skipItems(1)
|
||||
|
||||
// UpdateVisibleRange near end should trigger loadMore
|
||||
initialState.eventSink(RoomListSearchEvent.UpdateVisibleRange(IntRange(0, 9)))
|
||||
// Give time for the coroutine to complete
|
||||
testScheduler.advanceUntilIdle()
|
||||
|
||||
assert(loadMoreLambda).isCalledOnce()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun TestScope.createRoomListSearchPresenter(
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import io.element.android.features.messages.impl.messagecomposer.suggestions.Roo
|
|||
import io.element.android.libraries.matrix.test.A_ROOM_ALIAS
|
||||
import io.element.android.libraries.matrix.test.A_ROOM_ID_2
|
||||
import io.element.android.libraries.matrix.test.room.aRoomSummary
|
||||
import io.element.android.libraries.matrix.test.roomlist.FakeDynamicRoomList
|
||||
import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
|
@ -22,7 +23,10 @@ import org.junit.Test
|
|||
class DefaultRoomAliasSuggestionsDataSourceTest {
|
||||
@Test
|
||||
fun `getAllRoomAliasSuggestions must emit a list of room alias suggestions`() = runTest {
|
||||
val roomListService = FakeRoomListService()
|
||||
val roomList = FakeDynamicRoomList()
|
||||
val roomListService = FakeRoomListService(
|
||||
createRoomListLambda = { roomList }
|
||||
)
|
||||
val sut = DefaultRoomAliasSuggestionsDataSource(
|
||||
roomListService
|
||||
)
|
||||
|
|
@ -31,7 +35,7 @@ class DefaultRoomAliasSuggestionsDataSourceTest {
|
|||
)
|
||||
sut.getAllRoomAliasSuggestions().test {
|
||||
assertThat(awaitItem()).isEmpty()
|
||||
roomListService.postAllRooms(
|
||||
roomList.summaries.emit(
|
||||
listOf(
|
||||
aRoomSummary(roomId = A_ROOM_ID_2, canonicalAlias = null),
|
||||
aRoomSummaryWithAnAlias,
|
||||
|
|
|
|||
|
|
@ -17,10 +17,12 @@ 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.notificationsettings.FakeNotificationSettingsService
|
||||
import io.element.android.libraries.matrix.test.room.aRoomSummary
|
||||
import io.element.android.libraries.matrix.test.roomlist.FakeDynamicRoomList
|
||||
import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService
|
||||
import io.element.android.tests.testutils.awaitLastSequentialItem
|
||||
import io.element.android.tests.testutils.consumeItemsUntilPredicate
|
||||
import io.element.android.tests.testutils.test
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
||||
|
|
@ -53,10 +55,14 @@ class EditDefaultNotificationSettingsPresenterTest {
|
|||
initialRoomModeIsDefault = false,
|
||||
getRoomsWithUserDefinedRulesResult = { Result.success(listOf(A_ROOM_ID)) },
|
||||
)
|
||||
val roomListService = FakeRoomListService()
|
||||
val roomList = FakeDynamicRoomList(
|
||||
summaries = MutableStateFlow(listOf(aRoomSummary(userDefinedNotificationMode = RoomNotificationMode.ALL_MESSAGES)))
|
||||
)
|
||||
val roomListService = FakeRoomListService(
|
||||
createRoomListLambda = { roomList }
|
||||
)
|
||||
val presenter = createEditDefaultNotificationSettingPresenter(notificationSettingsService, roomListService)
|
||||
presenter.test {
|
||||
roomListService.postAllRooms(listOf(aRoomSummary(userDefinedNotificationMode = RoomNotificationMode.ALL_MESSAGES)))
|
||||
val loadedState = consumeItemsUntilPredicate { state ->
|
||||
state.roomsWithUserDefinedMode.any { it.notificationMode == RoomNotificationMode.ALL_MESSAGES }
|
||||
}.last()
|
||||
|
|
@ -71,10 +77,8 @@ class EditDefaultNotificationSettingsPresenterTest {
|
|||
initialRoomModeIsDefault = false,
|
||||
getRoomsWithUserDefinedRulesResult = { Result.success(listOf(A_ROOM_ID, A_ROOM_ID_2)) },
|
||||
)
|
||||
val roomListService = FakeRoomListService()
|
||||
val presenter = createEditDefaultNotificationSettingPresenter(notificationSettingsService, roomListService)
|
||||
presenter.test {
|
||||
roomListService.postAllRooms(
|
||||
val roomList = FakeDynamicRoomList(
|
||||
summaries = MutableStateFlow(
|
||||
listOf(
|
||||
aRoomSummary(
|
||||
roomId = A_ROOM_ID,
|
||||
|
|
@ -86,8 +90,14 @@ class EditDefaultNotificationSettingsPresenterTest {
|
|||
name = "A",
|
||||
userDefinedNotificationMode = RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY,
|
||||
),
|
||||
),
|
||||
)
|
||||
)
|
||||
)
|
||||
val roomListService = FakeRoomListService(
|
||||
createRoomListLambda = { roomList }
|
||||
)
|
||||
val presenter = createEditDefaultNotificationSettingPresenter(notificationSettingsService, roomListService)
|
||||
presenter.test {
|
||||
val loadedState = consumeItemsUntilPredicate { state ->
|
||||
state.roomsWithUserDefinedMode.any { it.notificationMode == RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY }
|
||||
}.last()
|
||||
|
|
@ -103,10 +113,8 @@ class EditDefaultNotificationSettingsPresenterTest {
|
|||
initialRoomModeIsDefault = false,
|
||||
getRoomsWithUserDefinedRulesResult = { Result.success(listOf(A_ROOM_ID, A_ROOM_ID_2)) },
|
||||
)
|
||||
val roomListService = FakeRoomListService()
|
||||
val presenter = createEditDefaultNotificationSettingPresenter(notificationSettingsService, roomListService)
|
||||
presenter.test {
|
||||
roomListService.postAllRooms(
|
||||
val roomList = FakeDynamicRoomList(
|
||||
summaries = MutableStateFlow(
|
||||
listOf(
|
||||
aRoomSummary(
|
||||
roomId = A_ROOM_ID,
|
||||
|
|
@ -118,8 +126,14 @@ class EditDefaultNotificationSettingsPresenterTest {
|
|||
name = null,
|
||||
userDefinedNotificationMode = RoomNotificationMode.MUTE,
|
||||
),
|
||||
),
|
||||
)
|
||||
)
|
||||
)
|
||||
val roomListService = FakeRoomListService(
|
||||
createRoomListLambda = { roomList }
|
||||
)
|
||||
val presenter = createEditDefaultNotificationSettingPresenter(notificationSettingsService, roomListService)
|
||||
presenter.test {
|
||||
val loadedState = consumeItemsUntilPredicate { state ->
|
||||
state.roomsWithUserDefinedMode.any { it.notificationMode == RoomNotificationMode.MUTE }
|
||||
}.last()
|
||||
|
|
|
|||
|
|
@ -15,4 +15,5 @@ sealed interface AddRoomToSpaceEvent {
|
|||
data object Save : AddRoomToSpaceEvent
|
||||
data object ResetSaveAction : AddRoomToSpaceEvent
|
||||
data object Dismiss : AddRoomToSpaceEvent
|
||||
data class UpdateSearchVisibleRange(val range: IntRange) : AddRoomToSpaceEvent
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,9 +58,6 @@ class AddRoomToSpacePresenter(
|
|||
LaunchedEffect(searchQuery.text) {
|
||||
dataSource.setSearchQuery(searchQuery.text.toString())
|
||||
}
|
||||
LaunchedEffect(isSearchActive) {
|
||||
dataSource.setIsActive(isSearchActive)
|
||||
}
|
||||
|
||||
val suggestions by dataSource.suggestions.collectAsState(initial = persistentListOf())
|
||||
|
||||
|
|
@ -111,6 +108,9 @@ class AddRoomToSpacePresenter(
|
|||
coroutineScope.launch { spaceRoomList.reset() }
|
||||
}
|
||||
}
|
||||
is AddRoomToSpaceEvent.UpdateSearchVisibleRange -> coroutineScope.launch {
|
||||
dataSource.updateVisibleRange(event.range)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,14 +20,13 @@ import io.element.android.libraries.matrix.api.room.recent.getRecentlyVisitedRoo
|
|||
import io.element.android.libraries.matrix.api.roomlist.RoomList
|
||||
import io.element.android.libraries.matrix.api.roomlist.RoomListFilter
|
||||
import io.element.android.libraries.matrix.api.roomlist.RoomListService
|
||||
import io.element.android.libraries.matrix.api.roomlist.loadAllIncrementally
|
||||
import io.element.android.libraries.matrix.api.roomlist.updateVisibleRange
|
||||
import io.element.android.libraries.matrix.api.spaces.SpaceRoomList
|
||||
import io.element.android.libraries.matrix.ui.model.SelectRoomInfo
|
||||
import io.element.android.libraries.matrix.ui.model.toSelectRoomInfo
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
|
|
@ -58,7 +57,6 @@ class AddRoomToSpaceSearchDataSource(
|
|||
|
||||
private val roomList = roomListService.createRoomList(
|
||||
pageSize = PAGE_SIZE,
|
||||
initialFilter = RoomListFilter.all(),
|
||||
source = RoomList.Source.All,
|
||||
coroutineScope = coroutineScope,
|
||||
)
|
||||
|
|
@ -87,7 +85,7 @@ class AddRoomToSpaceSearchDataSource(
|
|||
}
|
||||
|
||||
val roomInfoList: Flow<ImmutableList<SelectRoomInfo>> = combine(
|
||||
roomList.filteredSummaries,
|
||||
roomList.summaries,
|
||||
spaceChildrenFlow,
|
||||
addedRoomIdsFlow,
|
||||
) { roomSummaries, childIds, addedIds ->
|
||||
|
|
@ -109,12 +107,8 @@ class AddRoomToSpaceSearchDataSource(
|
|||
.toImmutableList()
|
||||
}.flowOn(coroutineDispatchers.computation)
|
||||
|
||||
suspend fun setIsActive(isActive: Boolean) = coroutineScope {
|
||||
if (isActive) {
|
||||
roomList.loadAllIncrementally(this)
|
||||
} else {
|
||||
roomList.reset()
|
||||
}
|
||||
suspend fun updateVisibleRange(visibleRange: IntRange) {
|
||||
roomList.updateVisibleRange(visibleRange)
|
||||
}
|
||||
|
||||
suspend fun setSearchQuery(searchQuery: String) {
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import androidx.compose.foundation.layout.padding
|
|||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.LazyRow
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
|
|
@ -43,6 +44,7 @@ import io.element.android.libraries.designsystem.theme.components.SearchBar
|
|||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.libraries.designsystem.theme.components.TextButton
|
||||
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
||||
import io.element.android.libraries.designsystem.utils.OnVisibleRangeChangeEffect
|
||||
import io.element.android.libraries.matrix.ui.components.SelectedRoom
|
||||
import io.element.android.libraries.matrix.ui.model.SelectRoomInfo
|
||||
import io.element.android.libraries.matrix.ui.model.getAvatarData
|
||||
|
|
@ -121,6 +123,10 @@ fun AddRoomToSpaceView(
|
|||
}
|
||||
},
|
||||
) { rooms ->
|
||||
val lazyListState = rememberLazyListState()
|
||||
OnVisibleRangeChangeEffect(lazyListState) { visibleRange ->
|
||||
state.eventSink(AddRoomToSpaceEvent.UpdateSearchVisibleRange(visibleRange))
|
||||
}
|
||||
LazyColumn {
|
||||
items(rooms, key = { it.roomId }) { roomInfo ->
|
||||
RoomListItem(
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import io.element.android.libraries.matrix.test.A_ROOM_ID
|
|||
import io.element.android.libraries.matrix.test.A_ROOM_ID_2
|
||||
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||
import io.element.android.libraries.matrix.test.room.aRoomSummary
|
||||
import io.element.android.libraries.matrix.test.roomlist.FakeDynamicRoomList
|
||||
import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService
|
||||
import io.element.android.libraries.matrix.test.spaces.FakeSpaceRoomList
|
||||
import io.element.android.libraries.matrix.test.spaces.FakeSpaceService
|
||||
|
|
@ -116,12 +117,15 @@ class AddRoomToSpacePresenterTest {
|
|||
|
||||
@Test
|
||||
fun `present - searchResults shows Results when rooms available`() = runTest {
|
||||
val roomListService = FakeRoomListService()
|
||||
val roomList = FakeDynamicRoomList()
|
||||
val roomListService = FakeRoomListService(
|
||||
createRoomListLambda = { roomList }
|
||||
)
|
||||
val presenter = createAddRoomToSpacePresenter(roomListService = roomListService)
|
||||
presenter.test {
|
||||
awaitItem() // Initial state
|
||||
// Post rooms to the service
|
||||
roomListService.postAllRooms(
|
||||
roomList.summaries.emit(
|
||||
listOf(
|
||||
aRoomSummary(
|
||||
roomId = A_ROOM_ID,
|
||||
|
|
@ -296,6 +300,29 @@ class AddRoomToSpacePresenterTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - UpdateSearchVisibleRange triggers pagination when near end`() = runTest {
|
||||
val loadMoreLambda = lambdaRecorder<Unit> { }
|
||||
val roomList = FakeDynamicRoomList(loadMoreLambda = loadMoreLambda)
|
||||
val roomListService = FakeRoomListService(
|
||||
createRoomListLambda = { roomList }
|
||||
)
|
||||
val presenter = createAddRoomToSpacePresenter(roomListService = roomListService)
|
||||
presenter.test {
|
||||
val state = awaitItem()
|
||||
// Post rooms to simulate loaded content
|
||||
roomList.summaries.emit(listOf(aRoomSummary()))
|
||||
advanceUntilIdle()
|
||||
skipItems(1)
|
||||
|
||||
// UpdateSearchVisibleRange should trigger loadMore
|
||||
state.eventSink(AddRoomToSpaceEvent.UpdateSearchVisibleRange(IntRange(0, 9)))
|
||||
advanceUntilIdle()
|
||||
|
||||
assert(loadMoreLambda).isCalledOnce()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - Dismiss after partial success calls reset`() = runTest {
|
||||
val resetResult = lambdaRecorder<Result<Unit>> { Result.success(Unit) }
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import androidx.compose.ui.test.onNodeWithText
|
|||
import androidx.compose.ui.test.performClick
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
import io.element.android.tests.testutils.EnsureNeverCalled
|
||||
import io.element.android.tests.testutils.EventsRecorder
|
||||
|
|
@ -99,6 +100,21 @@ class AddRoomToSpaceViewTest {
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Config(qualifiers = "h1024dp")
|
||||
@Test
|
||||
fun `displaying search results sends UpdateSearchVisibleRange event`() {
|
||||
val eventsRecorder = EventsRecorder<AddRoomToSpaceEvent>()
|
||||
val rooms = aSelectRoomInfoList()
|
||||
rule.setAddRoomToSpaceView(
|
||||
anAddRoomToSpaceState(
|
||||
isSearchActive = true,
|
||||
searchResults = SearchBarResultState.Results(rooms),
|
||||
eventSink = eventsRecorder,
|
||||
),
|
||||
)
|
||||
eventsRecorder.assertTrue(0) { it is AddRoomToSpaceEvent.UpdateSearchVisibleRange }
|
||||
}
|
||||
}
|
||||
|
||||
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setAddRoomToSpaceView(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue