Merge pull request #2405 from element-hq/feature/fga/room_list_refact_search

RoomList : rework how search is done to prepare for later filtering
This commit is contained in:
ganfra 2024-02-19 12:31:52 +01:00 committed by GitHub
commit 4f4a73fe64
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
49 changed files with 623 additions and 255 deletions

View file

@ -28,11 +28,26 @@ import kotlin.time.Duration
* Can be retrieved from [RoomListService] methods.
*/
interface RoomList {
/**
* The loading state of the room list.
*/
sealed interface LoadingState {
data object NotLoaded : LoadingState
data class Loaded(val numberOfRooms: Int) : LoadingState
}
/**
* The source of the room list data.
* All: all rooms except invites.
* Invites: only invites.
*
* To apply some dynamic filtering on top of that, use [DynamicRoomList].
*/
enum class Source {
All,
Invites,
}
/**
* The list of room summaries as a flow.
*/

View file

@ -18,38 +18,68 @@ package io.element.android.libraries.matrix.api.roomlist
sealed interface RoomListFilter {
companion object {
/**
* Create a filter that matches all the given filters.
*/
fun all(vararg filters: RoomListFilter): RoomListFilter {
return All(filters.toList())
}
/**
* Create a filter that matches any of the given filters.
*/
fun any(vararg filters: RoomListFilter): RoomListFilter {
return Any(filters.toList())
}
}
/**
* A filter that matches all the given filters.
*/
data class All(
val filters: List<RoomListFilter>
) : RoomListFilter
/**
* A filter that matches any of the given filters.
*/
data class Any(
val filters: List<RoomListFilter>
) : RoomListFilter
/**
* A filter that matches rooms that are not left.
*/
data object NonLeft : RoomListFilter
/**
* A filter that matches rooms that are unread.
*/
data object Unread : RoomListFilter
/**
* A filter that matches either Group or People rooms.
*/
sealed interface Category : RoomListFilter {
data object Group : Category
data object People : Category
}
/**
* A filter that matches no room.
*/
data object None : RoomListFilter
/**
* A filter that matches rooms with a name using a normalized match.
*/
data class NormalizedMatchRoomName(
val pattern: String
) : RoomListFilter
/**
* A filter that matches rooms with a name using a fuzzy match.
*/
data class FuzzyMatchRoomName(
val pattern: String
) : RoomListFilter

View file

@ -17,6 +17,7 @@
package io.element.android.libraries.matrix.api.roomlist
import androidx.compose.runtime.Immutable
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.StateFlow
/**
@ -39,6 +40,20 @@ interface RoomListService {
data object Hide : SyncIndicator
}
/**
* Creates a room list that can be used to load more rooms and filter them dynamically.
* @param coroutineScope the scope to use for the room list. When the scope will be closed, the room list will be closed too.
* @param pageSize the number of rooms to load at once.
* @param initialFilter the initial filter to apply to the rooms.
* @param source the source of the rooms, either all rooms or invites.
*/
fun createRoomList(
coroutineScope: CoroutineScope,
pageSize: Int,
initialFilter: RoomListFilter,
source: RoomList.Source,
): DynamicRoomList
/**
* returns a [DynamicRoomList] object of all rooms we want to display.
* This will exclude some rooms like the invites, or spaces.

View file

@ -197,10 +197,10 @@ class RustMatrixClient(
RustRoomListService(
innerRoomListService = innerRoomListService,
sessionCoroutineScope = sessionCoroutineScope,
sessionDispatcher = sessionDispatcher,
roomListFactory = RoomListFactory(
innerRoomListService = innerRoomListService,
coroutineScope = sessionCoroutineScope,
dispatcher = sessionDispatcher,
sessionCoroutineScope = sessionCoroutineScope,
),
)

View file

@ -20,7 +20,6 @@ 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.RoomListFilter
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
@ -30,13 +29,14 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import org.matrix.rustcomponents.sdk.RoomListLoadingState
import org.matrix.rustcomponents.sdk.RoomListService
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
import org.matrix.rustcomponents.sdk.RoomList as InnerRoomList
import org.matrix.rustcomponents.sdk.RoomListService as InnerRoomListService
internal class RoomListFactory(
private val innerRoomListService: InnerRoomListService,
private val coroutineScope: CoroutineScope,
private val dispatcher: CoroutineDispatcher,
private val innerRoomListService: RoomListService,
private val sessionCoroutineScope: CoroutineScope,
private val roomSummaryDetailsFactory: RoomSummaryDetailsFactory = RoomSummaryDetailsFactory(),
) {
/**
@ -44,18 +44,21 @@ internal class RoomListFactory(
*/
fun createRoomList(
pageSize: Int,
coroutineScope: CoroutineScope = sessionCoroutineScope,
coroutineContext: CoroutineContext = EmptyCoroutineContext,
initialFilter: RoomListFilter = RoomListFilter.all(),
innerProvider: suspend () -> InnerRoomList
): DynamicRoomList {
val loadingStateFlow: MutableStateFlow<RoomList.LoadingState> = MutableStateFlow(RoomList.LoadingState.NotLoaded)
val summariesFlow = MutableStateFlow<List<RoomSummary>>(emptyList())
val processor = RoomSummaryListProcessor(summariesFlow, innerRoomListService, dispatcher, roomSummaryDetailsFactory)
val processor = RoomSummaryListProcessor(summariesFlow, innerRoomListService, coroutineContext, roomSummaryDetailsFactory)
// 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
coroutineScope.launch(dispatcher) {
coroutineScope.launch(coroutineContext) {
innerRoomList = innerProvider()
innerRoomList?.let { innerRoomList ->
innerRoomList.entriesFlow(

View file

@ -17,7 +17,6 @@
package io.element.android.libraries.matrix.impl.roomlist
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
@ -28,11 +27,12 @@ import org.matrix.rustcomponents.sdk.RoomListServiceInterface
import org.matrix.rustcomponents.sdk.use
import timber.log.Timber
import java.util.UUID
import kotlin.coroutines.CoroutineContext
class RoomSummaryListProcessor(
private val roomSummaries: MutableStateFlow<List<RoomSummary>>,
private val roomListService: RoomListServiceInterface,
private val dispatcher: CoroutineDispatcher,
private val coroutineContext: CoroutineContext,
private val roomSummaryDetailsFactory: RoomSummaryDetailsFactory = RoomSummaryDetailsFactory(),
) {
private val roomSummariesByIdentifier = HashMap<String, RoomSummary>()
@ -130,7 +130,7 @@ class RoomSummaryListProcessor(
return builtRoomSummary
}
private suspend fun updateRoomSummaries(block: suspend MutableList<RoomSummary>.() -> Unit) = withContext(dispatcher) {
private suspend fun updateRoomSummaries(block: suspend MutableList<RoomSummary>.() -> Unit) = withContext(coroutineContext) {
mutex.withLock {
val mutableRoomSummaries = roomSummaries.value.toMutableList()
block(mutableRoomSummaries)

View file

@ -21,6 +21,7 @@ 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 kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
@ -42,10 +43,31 @@ private const val DEFAULT_PAGE_SIZE = 20
internal class RustRoomListService(
private val innerRoomListService: InnerRustRoomListService,
private val sessionCoroutineScope: CoroutineScope,
roomListFactory: RoomListFactory,
private val sessionDispatcher: CoroutineDispatcher,
private val roomListFactory: RoomListFactory,
) : RoomListService {
override fun createRoomList(
coroutineScope: CoroutineScope,
pageSize: Int,
initialFilter: RoomListFilter,
source: RoomList.Source
): DynamicRoomList {
return roomListFactory.createRoomList(
pageSize = pageSize,
initialFilter = initialFilter,
coroutineScope = coroutineScope,
coroutineContext = sessionDispatcher,
) {
when (source) {
RoomList.Source.All -> innerRoomListService.allRooms()
RoomList.Source.Invites -> innerRoomListService.invites()
}
}
}
override val allRooms: DynamicRoomList = roomListFactory.createRoomList(
pageSize = DEFAULT_PAGE_SIZE,
coroutineContext = sessionDispatcher,
initialFilter = RoomListFilter.all(RoomListFilter.NonLeft),
) {
innerRoomListService.allRooms()
@ -53,6 +75,7 @@ internal class RustRoomListService(
override val invites: RoomList = roomListFactory.createRoomList(
pageSize = Int.MAX_VALUE,
coroutineContext = sessionDispatcher,
) {
innerRoomListService.invites()
}

View file

@ -158,7 +158,7 @@ class RoomSummaryListProcessorTests {
private fun TestScope.createProcessor() = RoomSummaryListProcessor(
summaries,
fakeRoomListService,
dispatcher = StandardTestDispatcher(testScheduler),
coroutineContext = StandardTestDispatcher(testScheduler),
roomSummaryDetailsFactory = RoomSummaryDetailsFactory(),
)

View file

@ -53,6 +53,10 @@ fun aRoomSummaryFilled(
)
)
fun aRoomSummaryFilled(
details: RoomSummaryDetails = aRoomSummaryDetails(),
) = RoomSummary.Filled(details)
fun aRoomSummaryDetails(
roomId: RoomId = A_ROOM_ID,
name: String = A_ROOM_NAME,

View file

@ -21,6 +21,7 @@ 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 kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@ -59,13 +60,20 @@ class FakeRoomListService : RoomListService {
var latestSlidingSyncRange: IntRange? = null
private set
override val allRooms: DynamicRoomList = SimplePagedRoomList(
override fun createRoomList(coroutineScope: CoroutineScope, pageSize: Int, initialFilter: RoomListFilter, source: RoomList.Source): DynamicRoomList {
return when (source) {
RoomList.Source.All -> allRooms
RoomList.Source.Invites -> invites
}
}
override val allRooms = SimplePagedRoomList(
allRoomSummariesFlow,
allRoomsLoadingStateFlow,
MutableStateFlow(RoomListFilter.all())
)
override val invites: RoomList = SimplePagedRoomList(
override val invites = SimplePagedRoomList(
inviteRoomSummariesFlow,
inviteRoomsLoadingStateFlow,
MutableStateFlow(RoomListFilter.all())

View file

@ -25,7 +25,7 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.getAndUpdate
data class SimplePagedRoomList(
override val summaries: StateFlow<List<RoomSummary>>,
override val summaries: MutableStateFlow<List<RoomSummary>>,
override val loadingState: StateFlow<RoomList.LoadingState>,
override val currentFilter: MutableStateFlow<RoomListFilter>
) : DynamicRoomList {