mirror of
https://github.com/Novaloop-AG/element-x-android.git
synced 2026-11-02 12:07:28 +00:00
Merge pull request #7883 from element-hq/feature/bma/roomSelectSearchField
Use a search field to filter the rooms in the room select screen
This commit is contained in:
+8
-4
@@ -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 {
|
||||
@@ -70,9 +70,13 @@ internal fun RoomListInterface.entriesFlow(
|
||||
listener = listener,
|
||||
)
|
||||
val controller = result.controller()
|
||||
controller.setFilter(initialFilterKind)
|
||||
onControllerCreated(controller)
|
||||
awaitClose {
|
||||
// Use try/finally rather than awaitClose's block, so that the resources are released even if
|
||||
// onControllerCreated is cancelled or throws before awaitClose is reached
|
||||
try {
|
||||
controller.setFilter(initialFilterKind)
|
||||
onControllerCreated(controller)
|
||||
awaitClose()
|
||||
} finally {
|
||||
result.entriesStream().cancelAndDestroy()
|
||||
controller.destroy()
|
||||
result.destroy()
|
||||
|
||||
+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