Merge pull request #1920 from vector-im/feature/fga/dynamic_room_list_incremental_load
RoomList: introduce incremental loading to improve performances.
This commit is contained in:
commit
270c4612f5
8 changed files with 100 additions and 56 deletions
1
changelog.d/1920.misc
Normal file
1
changelog.d/1920.misc
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
RoomList: introduce incremental loading to improve performances.
|
||||||
|
|
@ -16,6 +16,12 @@
|
||||||
|
|
||||||
package io.element.android.libraries.matrix.api.roomlist
|
package io.element.android.libraries.matrix.api.roomlist
|
||||||
|
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.combine
|
||||||
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* RoomList with dynamic filtering and loading.
|
* RoomList with dynamic filtering and loading.
|
||||||
* This is useful for large lists of rooms.
|
* This is useful for large lists of rooms.
|
||||||
|
|
@ -23,17 +29,17 @@ package io.element.android.libraries.matrix.api.roomlist
|
||||||
*/
|
*/
|
||||||
interface DynamicRoomList : RoomList {
|
interface DynamicRoomList : RoomList {
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val DEFAULT_PAGE_SIZE = 20
|
|
||||||
const val DEFAULT_PAGES_TO_LOAD = 10
|
|
||||||
}
|
|
||||||
|
|
||||||
sealed interface Filter {
|
sealed interface Filter {
|
||||||
/**
|
/**
|
||||||
* No filter applied.
|
* No filter applied.
|
||||||
*/
|
*/
|
||||||
data object All : Filter
|
data object All : Filter
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter only the left rooms.
|
||||||
|
*/
|
||||||
|
data object AllNonLeft : Filter
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Filter all rooms.
|
* Filter all rooms.
|
||||||
*/
|
*/
|
||||||
|
|
@ -45,6 +51,10 @@ interface DynamicRoomList : RoomList {
|
||||||
data class NormalizedMatchRoomName(val pattern: String) : Filter
|
data class NormalizedMatchRoomName(val pattern: String) : Filter
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val currentFilter: StateFlow<Filter>
|
||||||
|
val loadedPages: StateFlow<Int>
|
||||||
|
val pageSize: Int
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load more rooms into the list if possible.
|
* Load more rooms into the list if possible.
|
||||||
*/
|
*/
|
||||||
|
|
@ -61,3 +71,29 @@ interface DynamicRoomList : RoomList {
|
||||||
*/
|
*/
|
||||||
suspend fun updateFilter(filter: Filter)
|
suspend fun updateFilter(filter: Filter)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Offers a way to load all the rooms incrementally.
|
||||||
|
* It will load more room until all are loaded.
|
||||||
|
* If total number of rooms increase, it will load more pages if needed.
|
||||||
|
* The number of rooms is independent of the filter.
|
||||||
|
*/
|
||||||
|
fun DynamicRoomList.loadAllIncrementally(coroutineScope: CoroutineScope) {
|
||||||
|
combine(
|
||||||
|
loadedPages,
|
||||||
|
loadingState,
|
||||||
|
) { loadedPages, loadingState ->
|
||||||
|
loadedPages to loadingState
|
||||||
|
}
|
||||||
|
.onEach { (loadedPages, loadingState) ->
|
||||||
|
when (loadingState) {
|
||||||
|
is RoomList.LoadingState.Loaded -> {
|
||||||
|
if (pageSize * loadedPages < loadingState.numberOfRooms) {
|
||||||
|
loadMore()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RoomList.LoadingState.NotLoaded -> Unit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.launchIn(coroutineScope)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -41,10 +41,10 @@ interface RoomListService {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* returns a [RoomList] object of all rooms we want to display.
|
* returns a [DynamicRoomList] object of all rooms we want to display.
|
||||||
* This will exclude some rooms like the invites, or spaces.
|
* This will exclude some rooms like the invites, or spaces.
|
||||||
*/
|
*/
|
||||||
val allRooms: RoomList
|
val allRooms: DynamicRoomList
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* returns a [RoomList] object of all invites.
|
* returns a [RoomList] object of all invites.
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,6 @@ fun RoomListInterface.loadingStateFlow(): Flow<RoomListLoadingState> =
|
||||||
|
|
||||||
internal fun RoomListInterface.entriesFlow(
|
internal fun RoomListInterface.entriesFlow(
|
||||||
pageSize: Int,
|
pageSize: Int,
|
||||||
numberOfPages: Int,
|
|
||||||
roomListDynamicEvents: Flow<RoomListDynamicEvents>,
|
roomListDynamicEvents: Flow<RoomListDynamicEvents>,
|
||||||
initialFilterKind: RoomListEntriesDynamicFilterKind
|
initialFilterKind: RoomListEntriesDynamicFilterKind
|
||||||
): Flow<List<RoomListEntriesUpdate>> =
|
): Flow<List<RoomListEntriesUpdate>> =
|
||||||
|
|
@ -84,9 +83,7 @@ internal fun RoomListInterface.entriesFlow(
|
||||||
controller.setFilter(controllerEvents.filter)
|
controller.setFilter(controllerEvents.filter)
|
||||||
}
|
}
|
||||||
is RoomListDynamicEvents.LoadMore -> {
|
is RoomListDynamicEvents.LoadMore -> {
|
||||||
repeat(numberOfPages) {
|
controller.addOnePage()
|
||||||
controller.addOnePage()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
is RoomListDynamicEvents.Reset -> {
|
is RoomListDynamicEvents.Reset -> {
|
||||||
controller.resetToOnePage()
|
controller.resetToOnePage()
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ import kotlinx.coroutines.CoroutineDispatcher
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.getAndUpdate
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
|
@ -39,57 +40,28 @@ internal class RoomListFactory(
|
||||||
private val roomSummaryDetailsFactory: RoomSummaryDetailsFactory = RoomSummaryDetailsFactory(),
|
private val roomSummaryDetailsFactory: RoomSummaryDetailsFactory = RoomSummaryDetailsFactory(),
|
||||||
) {
|
) {
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a room list that will load all rooms in a single page.
|
|
||||||
* It mimics the usage of the old api.
|
|
||||||
*/
|
|
||||||
fun createRoomList(
|
|
||||||
innerProvider: suspend () -> InnerRoomList,
|
|
||||||
): RoomList {
|
|
||||||
return createRustRoomList(
|
|
||||||
pageSize = Int.MAX_VALUE,
|
|
||||||
numberOfPages = 1,
|
|
||||||
initialFilterKind = RoomListEntriesDynamicFilterKind.AllNonLeft,
|
|
||||||
innerRoomListProvider = innerProvider
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a room list that can be used to load more rooms and filter them dynamically.
|
* Creates a room list that can be used to load more rooms and filter them dynamically.
|
||||||
*/
|
*/
|
||||||
fun createDynamicRoomList(
|
fun createRoomList(
|
||||||
pageSize: Int = DynamicRoomList.DEFAULT_PAGE_SIZE,
|
pageSize: Int,
|
||||||
pagesToLoad: Int = DynamicRoomList.DEFAULT_PAGES_TO_LOAD,
|
initialFilter: DynamicRoomList.Filter = DynamicRoomList.Filter.All,
|
||||||
initialFilter: DynamicRoomList.Filter = DynamicRoomList.Filter.None,
|
|
||||||
innerProvider: suspend () -> InnerRoomList
|
innerProvider: suspend () -> InnerRoomList
|
||||||
): DynamicRoomList {
|
): DynamicRoomList {
|
||||||
return createRustRoomList(
|
|
||||||
pageSize = pageSize,
|
|
||||||
numberOfPages = pagesToLoad,
|
|
||||||
initialFilterKind = initialFilter.toRustFilter(),
|
|
||||||
innerRoomListProvider = innerProvider
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun createRustRoomList(
|
|
||||||
pageSize: Int,
|
|
||||||
numberOfPages: Int,
|
|
||||||
initialFilterKind: RoomListEntriesDynamicFilterKind,
|
|
||||||
innerRoomListProvider: suspend () -> InnerRoomList
|
|
||||||
): RustDynamicRoomList {
|
|
||||||
val loadingStateFlow: MutableStateFlow<RoomList.LoadingState> = MutableStateFlow(RoomList.LoadingState.NotLoaded)
|
val loadingStateFlow: MutableStateFlow<RoomList.LoadingState> = MutableStateFlow(RoomList.LoadingState.NotLoaded)
|
||||||
val summariesFlow = MutableStateFlow<List<RoomSummary>>(emptyList())
|
val summariesFlow = MutableStateFlow<List<RoomSummary>>(emptyList())
|
||||||
val processor = RoomSummaryListProcessor(summariesFlow, innerRoomListService, dispatcher, roomSummaryDetailsFactory)
|
val processor = RoomSummaryListProcessor(summariesFlow, innerRoomListService, dispatcher, roomSummaryDetailsFactory)
|
||||||
val dynamicEvents = MutableSharedFlow<RoomListDynamicEvents>()
|
// Makes sure we don't miss any events
|
||||||
|
val dynamicEvents = MutableSharedFlow<RoomListDynamicEvents>(replay = 100)
|
||||||
|
val currentFilter = MutableStateFlow(initialFilter)
|
||||||
|
val loadedPages = MutableStateFlow(1)
|
||||||
var innerRoomList: InnerRoomList? = null
|
var innerRoomList: InnerRoomList? = null
|
||||||
coroutineScope.launch(dispatcher) {
|
coroutineScope.launch(dispatcher) {
|
||||||
innerRoomList = innerRoomListProvider()
|
innerRoomList = innerProvider()
|
||||||
innerRoomList?.let { innerRoomList ->
|
innerRoomList?.let { innerRoomList ->
|
||||||
innerRoomList.entriesFlow(
|
innerRoomList.entriesFlow(
|
||||||
pageSize = pageSize,
|
pageSize = pageSize,
|
||||||
numberOfPages = numberOfPages,
|
initialFilterKind = initialFilter.toRustFilter(),
|
||||||
initialFilterKind = initialFilterKind,
|
|
||||||
roomListDynamicEvents = dynamicEvents
|
roomListDynamicEvents = dynamicEvents
|
||||||
).onEach { update ->
|
).onEach { update ->
|
||||||
processor.postUpdate(update)
|
processor.postUpdate(update)
|
||||||
|
|
@ -105,15 +77,26 @@ internal class RoomListFactory(
|
||||||
}.invokeOnCompletion {
|
}.invokeOnCompletion {
|
||||||
innerRoomList?.destroy()
|
innerRoomList?.destroy()
|
||||||
}
|
}
|
||||||
return RustDynamicRoomList(summariesFlow, loadingStateFlow, dynamicEvents, processor)
|
return RustDynamicRoomList(
|
||||||
|
summaries = summariesFlow,
|
||||||
|
loadingState = loadingStateFlow,
|
||||||
|
currentFilter = currentFilter,
|
||||||
|
loadedPages = loadedPages,
|
||||||
|
dynamicEvents = dynamicEvents,
|
||||||
|
processor = processor,
|
||||||
|
pageSize = pageSize,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class RustDynamicRoomList(
|
private class RustDynamicRoomList(
|
||||||
override val summaries: MutableStateFlow<List<RoomSummary>>,
|
override val summaries: MutableStateFlow<List<RoomSummary>>,
|
||||||
override val loadingState: MutableStateFlow<RoomList.LoadingState>,
|
override val loadingState: MutableStateFlow<RoomList.LoadingState>,
|
||||||
|
override val currentFilter: MutableStateFlow<DynamicRoomList.Filter>,
|
||||||
|
override val loadedPages: MutableStateFlow<Int>,
|
||||||
private val dynamicEvents: MutableSharedFlow<RoomListDynamicEvents>,
|
private val dynamicEvents: MutableSharedFlow<RoomListDynamicEvents>,
|
||||||
private val processor: RoomSummaryListProcessor,
|
private val processor: RoomSummaryListProcessor,
|
||||||
|
override val pageSize: Int,
|
||||||
) : DynamicRoomList {
|
) : DynamicRoomList {
|
||||||
|
|
||||||
override suspend fun rebuildSummaries() {
|
override suspend fun rebuildSummaries() {
|
||||||
|
|
@ -121,16 +104,19 @@ private class RustDynamicRoomList(
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun updateFilter(filter: DynamicRoomList.Filter) {
|
override suspend fun updateFilter(filter: DynamicRoomList.Filter) {
|
||||||
|
currentFilter.emit(filter)
|
||||||
val filterEvent = RoomListDynamicEvents.SetFilter(filter.toRustFilter())
|
val filterEvent = RoomListDynamicEvents.SetFilter(filter.toRustFilter())
|
||||||
dynamicEvents.emit(filterEvent)
|
dynamicEvents.emit(filterEvent)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun loadMore() {
|
override suspend fun loadMore() {
|
||||||
dynamicEvents.emit(RoomListDynamicEvents.LoadMore)
|
dynamicEvents.emit(RoomListDynamicEvents.LoadMore)
|
||||||
|
loadedPages.getAndUpdate { it + 1 }
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun reset() {
|
override suspend fun reset() {
|
||||||
dynamicEvents.emit(RoomListDynamicEvents.Reset)
|
dynamicEvents.emit(RoomListDynamicEvents.Reset)
|
||||||
|
loadedPages.emit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -146,6 +132,7 @@ private fun DynamicRoomList.Filter.toRustFilter(): RoomListEntriesDynamicFilterK
|
||||||
DynamicRoomList.Filter.All -> RoomListEntriesDynamicFilterKind.All
|
DynamicRoomList.Filter.All -> RoomListEntriesDynamicFilterKind.All
|
||||||
is DynamicRoomList.Filter.NormalizedMatchRoomName -> RoomListEntriesDynamicFilterKind.NormalizedMatchRoomName(this.pattern)
|
is DynamicRoomList.Filter.NormalizedMatchRoomName -> RoomListEntriesDynamicFilterKind.NormalizedMatchRoomName(this.pattern)
|
||||||
DynamicRoomList.Filter.None -> RoomListEntriesDynamicFilterKind.None
|
DynamicRoomList.Filter.None -> RoomListEntriesDynamicFilterKind.None
|
||||||
|
DynamicRoomList.Filter.AllNonLeft -> RoomListEntriesDynamicFilterKind.AllNonLeft
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,8 +16,10 @@
|
||||||
|
|
||||||
package io.element.android.libraries.matrix.impl.roomlist
|
package io.element.android.libraries.matrix.impl.roomlist
|
||||||
|
|
||||||
|
import io.element.android.libraries.matrix.api.roomlist.DynamicRoomList
|
||||||
import io.element.android.libraries.matrix.api.roomlist.RoomList
|
import io.element.android.libraries.matrix.api.roomlist.RoomList
|
||||||
import io.element.android.libraries.matrix.api.roomlist.RoomListService
|
import io.element.android.libraries.matrix.api.roomlist.RoomListService
|
||||||
|
import io.element.android.libraries.matrix.api.roomlist.loadAllIncrementally
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.flow.SharingStarted
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
|
@ -34,20 +36,31 @@ import org.matrix.rustcomponents.sdk.RoomListServiceSyncIndicator
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import org.matrix.rustcomponents.sdk.RoomListService as InnerRustRoomListService
|
import org.matrix.rustcomponents.sdk.RoomListService as InnerRustRoomListService
|
||||||
|
|
||||||
|
private const val DEFAULT_PAGE_SIZE = 20
|
||||||
|
|
||||||
internal class RustRoomListService(
|
internal class RustRoomListService(
|
||||||
private val innerRoomListService: InnerRustRoomListService,
|
private val innerRoomListService: InnerRustRoomListService,
|
||||||
private val sessionCoroutineScope: CoroutineScope,
|
private val sessionCoroutineScope: CoroutineScope,
|
||||||
roomListFactory: RoomListFactory,
|
roomListFactory: RoomListFactory,
|
||||||
) : RoomListService {
|
) : RoomListService {
|
||||||
|
|
||||||
override val allRooms: RoomList = roomListFactory.createRoomList {
|
override val allRooms: DynamicRoomList = roomListFactory.createRoomList(
|
||||||
|
pageSize = DEFAULT_PAGE_SIZE,
|
||||||
|
initialFilter = DynamicRoomList.Filter.AllNonLeft,
|
||||||
|
) {
|
||||||
innerRoomListService.allRooms()
|
innerRoomListService.allRooms()
|
||||||
}
|
}
|
||||||
|
|
||||||
override val invites: RoomList = roomListFactory.createRoomList {
|
override val invites: RoomList = roomListFactory.createRoomList(
|
||||||
|
pageSize = Int.MAX_VALUE,
|
||||||
|
) {
|
||||||
innerRoomListService.invites()
|
innerRoomListService.invites()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
allRooms.loadAllIncrementally(sessionCoroutineScope)
|
||||||
|
}
|
||||||
|
|
||||||
override fun updateAllRoomsVisibleRange(range: IntRange) {
|
override fun updateAllRoomsVisibleRange(range: IntRange) {
|
||||||
Timber.v("setVisibleRange=$range")
|
Timber.v("setVisibleRange=$range")
|
||||||
sessionCoroutineScope.launch {
|
sessionCoroutineScope.launch {
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
package io.element.android.libraries.matrix.test.roomlist
|
package io.element.android.libraries.matrix.test.roomlist
|
||||||
|
|
||||||
|
import io.element.android.libraries.matrix.api.roomlist.DynamicRoomList
|
||||||
import io.element.android.libraries.matrix.api.roomlist.RoomList
|
import io.element.android.libraries.matrix.api.roomlist.RoomList
|
||||||
import io.element.android.libraries.matrix.api.roomlist.RoomListService
|
import io.element.android.libraries.matrix.api.roomlist.RoomListService
|
||||||
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
|
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
|
||||||
|
|
@ -54,14 +55,16 @@ class FakeRoomListService : RoomListService {
|
||||||
var latestSlidingSyncRange: IntRange? = null
|
var latestSlidingSyncRange: IntRange? = null
|
||||||
private set
|
private set
|
||||||
|
|
||||||
override val allRooms: RoomList = SimplePagedRoomList(
|
override val allRooms: DynamicRoomList = SimplePagedRoomList(
|
||||||
allRoomSummariesFlow,
|
allRoomSummariesFlow,
|
||||||
allRoomsLoadingStateFlow,
|
allRoomsLoadingStateFlow,
|
||||||
|
MutableStateFlow(DynamicRoomList.Filter.None)
|
||||||
)
|
)
|
||||||
|
|
||||||
override val invites: RoomList = SimplePagedRoomList(
|
override val invites: RoomList = SimplePagedRoomList(
|
||||||
inviteRoomSummariesFlow,
|
inviteRoomSummariesFlow,
|
||||||
inviteRoomsLoadingStateFlow,
|
inviteRoomsLoadingStateFlow,
|
||||||
|
MutableStateFlow(DynamicRoomList.Filter.None)
|
||||||
)
|
)
|
||||||
|
|
||||||
override fun updateAllRoomsVisibleRange(range: IntRange) {
|
override fun updateAllRoomsVisibleRange(range: IntRange) {
|
||||||
|
|
|
||||||
|
|
@ -19,23 +19,30 @@ package io.element.android.libraries.matrix.test.roomlist
|
||||||
import io.element.android.libraries.matrix.api.roomlist.DynamicRoomList
|
import io.element.android.libraries.matrix.api.roomlist.DynamicRoomList
|
||||||
import io.element.android.libraries.matrix.api.roomlist.RoomList
|
import io.element.android.libraries.matrix.api.roomlist.RoomList
|
||||||
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
|
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.getAndUpdate
|
||||||
|
|
||||||
data class SimplePagedRoomList(
|
data class SimplePagedRoomList(
|
||||||
override val summaries: StateFlow<List<RoomSummary>>,
|
override val summaries: StateFlow<List<RoomSummary>>,
|
||||||
override val loadingState: StateFlow<RoomList.LoadingState>
|
override val loadingState: StateFlow<RoomList.LoadingState>,
|
||||||
|
override val currentFilter: MutableStateFlow<DynamicRoomList.Filter>
|
||||||
) : DynamicRoomList {
|
) : DynamicRoomList {
|
||||||
|
|
||||||
|
override val pageSize: Int = Int.MAX_VALUE
|
||||||
|
override val loadedPages = MutableStateFlow(1)
|
||||||
|
|
||||||
override suspend fun loadMore() {
|
override suspend fun loadMore() {
|
||||||
//No-op
|
//No-op
|
||||||
|
loadedPages.getAndUpdate { it + 1 }
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun reset() {
|
override suspend fun reset() {
|
||||||
//No-op
|
loadedPages.emit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun updateFilter(filter: DynamicRoomList.Filter) {
|
override suspend fun updateFilter(filter: DynamicRoomList.Filter) {
|
||||||
//No-op
|
currentFilter.emit(filter)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun rebuildSummaries() {
|
override suspend fun rebuildSummaries() {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue