mirror of
https://github.com/Novaloop-AG/element-x-android.git
synced 2026-10-31 08:15:02 +00:00
Apply the room list filter requested before the controller is created
The dynamic entries controller of a room list is created asynchronously, once the entries are observed. Filters set before that were silently dropped, so for instance the search query was not applied to the rooms of a newly selected account in the room select screen. RustDynamicRoomList now owns the controller, remembers the last requested filter, and applies it when the controller is created if it differs from the initial one. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+1
-1
@@ -57,7 +57,7 @@ fun RoomListInterface.loadingStateFlow(): Flow<RoomListLoadingState> =
|
||||
internal fun RoomListInterface.entriesFlow(
|
||||
pageSize: Int,
|
||||
initialFilterKind: RoomListEntriesDynamicFilterKind,
|
||||
onControllerCreated: (RoomListDynamicEntriesController) -> Unit,
|
||||
onControllerCreated: suspend (RoomListDynamicEntriesController) -> Unit,
|
||||
): Flow<List<RoomListEntriesUpdate>> =
|
||||
callbackFlow {
|
||||
val listener = object : RoomListEntriesListener {
|
||||
|
||||
+9
-10
@@ -23,7 +23,6 @@ import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import org.matrix.rustcomponents.sdk.RoomListDynamicEntriesController
|
||||
import org.matrix.rustcomponents.sdk.RoomListLoadingState
|
||||
import org.matrix.rustcomponents.sdk.RoomListService
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
@@ -49,7 +48,13 @@ internal class RoomListFactory(
|
||||
val summariesFlow = MutableSharedFlow<List<RoomSummary>>(replay = 1, extraBufferCapacity = 1)
|
||||
val processor = RoomSummaryListProcessor(summariesFlow, innerRoomListService, coroutineContext, roomSummaryFactory, analyticsService)
|
||||
var innerRoomList: InnerRoomList? = null
|
||||
var dynamicController: RoomListDynamicEntriesController? = null
|
||||
val dynamicRoomList = RustDynamicRoomList(
|
||||
summaries = summariesFlow,
|
||||
loadingState = loadingStateFlow,
|
||||
processor = processor,
|
||||
pageSize = pageSize,
|
||||
initialFilter = initialFilter,
|
||||
)
|
||||
|
||||
val firstRoomsTransaction = analyticsService.startTransaction("Load first set of rooms", "innerRoomList.entriesFlow")
|
||||
|
||||
@@ -60,7 +65,7 @@ internal class RoomListFactory(
|
||||
pageSize = pageSize,
|
||||
initialFilterKind = RoomListFilterMapper.toRustFilter(initialFilter),
|
||||
onControllerCreated = { controller ->
|
||||
dynamicController = controller
|
||||
dynamicRoomList.onControllerCreated(controller, initialFilter)
|
||||
}
|
||||
).onEach { update ->
|
||||
if (!firstRoomsTransaction.isFinished()) {
|
||||
@@ -80,13 +85,7 @@ internal class RoomListFactory(
|
||||
}.invokeOnCompletion {
|
||||
innerRoomList?.destroy()
|
||||
}
|
||||
return RustDynamicRoomList(
|
||||
summaries = summariesFlow,
|
||||
loadingState = loadingStateFlow,
|
||||
processor = processor,
|
||||
pageSize = pageSize,
|
||||
dynamicController = { dynamicController }
|
||||
)
|
||||
return dynamicRoomList
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+32
-11
@@ -24,37 +24,58 @@ internal class RustDynamicRoomList(
|
||||
override val loadingState: MutableStateFlow<RoomList.LoadingState>,
|
||||
private val processor: RoomSummaryListProcessor,
|
||||
override val pageSize: Int,
|
||||
private val dynamicController: () -> RoomListDynamicEntriesController?,
|
||||
initialFilter: RoomListFilter,
|
||||
private val addPagesCount: Int = DEFAULT_ADD_PAGES_COUNT
|
||||
) : DynamicRoomList {
|
||||
private val mutex = Mutex()
|
||||
|
||||
// The controller is created asynchronously, once the entries of the room list are observed
|
||||
private var dynamicController: RoomListDynamicEntriesController? = null
|
||||
|
||||
// The last requested filter, so that it can be applied when the controller is created
|
||||
private var currentFilter: RoomListFilter = initialFilter
|
||||
|
||||
/**
|
||||
* To be called when the controller is created. The controller already has the initial filter set, so apply the filter
|
||||
* only if it has been updated in the meantime.
|
||||
*/
|
||||
suspend fun onControllerCreated(controller: RoomListDynamicEntriesController, initialFilter: RoomListFilter) {
|
||||
mutex.withLock {
|
||||
dynamicController = controller
|
||||
if (currentFilter != initialFilter) {
|
||||
controller.applyFilter(currentFilter)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun rebuildSummaries() {
|
||||
processor.rebuildRoomSummaries()
|
||||
}
|
||||
|
||||
override suspend fun updateFilter(filter: RoomListFilter) {
|
||||
mutex.withLock {
|
||||
dynamicController()?.let { controller ->
|
||||
// Reset pagination when filter changes
|
||||
controller.resetToOnePage()
|
||||
val rustFilter = RoomListFilterMapper.toRustFilter(filter)
|
||||
controller.setFilter(rustFilter)
|
||||
// Then preload some pages
|
||||
controller.addPages(addPagesCount)
|
||||
}
|
||||
currentFilter = filter
|
||||
dynamicController?.applyFilter(filter)
|
||||
}
|
||||
}
|
||||
|
||||
private fun RoomListDynamicEntriesController.applyFilter(filter: RoomListFilter) {
|
||||
// Reset pagination when filter changes
|
||||
resetToOnePage()
|
||||
setFilter(RoomListFilterMapper.toRustFilter(filter))
|
||||
// Then preload some pages
|
||||
addPages(addPagesCount)
|
||||
}
|
||||
|
||||
override suspend fun loadMore() {
|
||||
mutex.withLock {
|
||||
dynamicController()?.addPages(addPagesCount)
|
||||
dynamicController?.addPages(addPagesCount)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun reset() {
|
||||
mutex.withLock {
|
||||
dynamicController()?.resetToOnePage()
|
||||
dynamicController?.resetToOnePage()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (c) 2026 Element Creations Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.impl.fixtures.fakes
|
||||
|
||||
import org.matrix.rustcomponents.sdk.NoHandle
|
||||
import org.matrix.rustcomponents.sdk.RoomListDynamicEntriesController
|
||||
import org.matrix.rustcomponents.sdk.RoomListEntriesDynamicFilterKind
|
||||
|
||||
class FakeFfiRoomListDynamicEntriesController : RoomListDynamicEntriesController(NoHandle) {
|
||||
val filters = mutableListOf<RoomListEntriesDynamicFilterKind>()
|
||||
|
||||
override fun setFilter(kind: RoomListEntriesDynamicFilterKind): Boolean {
|
||||
filters.add(kind)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun resetToOnePage() = Unit
|
||||
|
||||
override fun addOnePage() = Unit
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright (c) 2026 Element Creations Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.impl.roomlist
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
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.RoomSummary
|
||||
import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeFfiRoomListDynamicEntriesController
|
||||
import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeFfiRoomListService
|
||||
import io.element.android.services.analytics.test.FakeAnalyticsService
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.test.StandardTestDispatcher
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
||||
class RustDynamicRoomListTest {
|
||||
@Test
|
||||
fun `updateFilter before the controller is created applies the filter when it is created`() = runTest {
|
||||
val sut = createRustDynamicRoomList()
|
||||
sut.updateFilter(RoomListFilter.NormalizedMatchRoomName("query"))
|
||||
val controller = FakeFfiRoomListDynamicEntriesController()
|
||||
sut.onControllerCreated(controller, initialFilter = RoomListFilter.all())
|
||||
assertThat(controller.filters).containsExactly(RoomListFilterMapper.toRustFilter(RoomListFilter.NormalizedMatchRoomName("query")))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when the filter is not updated, the controller filter is not changed when it is created`() = runTest {
|
||||
val sut = createRustDynamicRoomList()
|
||||
val controller = FakeFfiRoomListDynamicEntriesController()
|
||||
sut.onControllerCreated(controller, initialFilter = RoomListFilter.all())
|
||||
assertThat(controller.filters).isEmpty()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `updateFilter after the controller is created applies the filter`() = runTest {
|
||||
val sut = createRustDynamicRoomList()
|
||||
val controller = FakeFfiRoomListDynamicEntriesController()
|
||||
sut.onControllerCreated(controller, initialFilter = RoomListFilter.all())
|
||||
sut.updateFilter(RoomListFilter.NormalizedMatchRoomName("query"))
|
||||
assertThat(controller.filters).containsExactly(RoomListFilterMapper.toRustFilter(RoomListFilter.NormalizedMatchRoomName("query")))
|
||||
}
|
||||
|
||||
private fun TestScope.createRustDynamicRoomList(): RustDynamicRoomList {
|
||||
val summaries = MutableSharedFlow<List<RoomSummary>>(replay = 1)
|
||||
return RustDynamicRoomList(
|
||||
summaries = summaries,
|
||||
loadingState = MutableStateFlow(RoomList.LoadingState.NotLoaded),
|
||||
processor = RoomSummaryListProcessor(
|
||||
summaries,
|
||||
FakeFfiRoomListService(),
|
||||
coroutineContext = StandardTestDispatcher(testScheduler),
|
||||
roomSummaryFactory = RoomSummaryFactory(),
|
||||
analyticsService = FakeAnalyticsService(),
|
||||
),
|
||||
pageSize = 10,
|
||||
initialFilter = RoomListFilter.all(),
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user