RoomList: make the main room list working (WIP)

This commit is contained in:
ganfra 2023-06-20 23:22:58 +02:00
parent ec9f0e2869
commit 39fcb1bb80
10 changed files with 146 additions and 328 deletions

View file

@ -19,6 +19,15 @@ package io.element.android.libraries.matrix.api.room
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
interface RoomSummaryDataSource { interface RoomSummaryDataSource {
enum class LoadingState {
NotLoaded,
PreLoaded,
PartiallyLoaded,
FullyLoaded,
}
fun loadingState(): StateFlow<LoadingState>
fun roomSummaries(): StateFlow<List<RoomSummary>> fun roomSummaries(): StateFlow<List<RoomSummary>>
fun setSlidingSyncRange(range: IntRange) fun setSlidingSyncRange(range: IntRange)
} }

View file

@ -31,7 +31,6 @@ import io.element.android.libraries.matrix.api.pusher.PushersService
import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.matrix.api.room.RoomMembershipObserver import io.element.android.libraries.matrix.api.room.RoomMembershipObserver
import io.element.android.libraries.matrix.api.room.RoomSummaryDataSource import io.element.android.libraries.matrix.api.room.RoomSummaryDataSource
import io.element.android.libraries.matrix.api.timeline.item.event.EventType
import io.element.android.libraries.matrix.api.user.MatrixSearchUserResults import io.element.android.libraries.matrix.api.user.MatrixSearchUserResults
import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.matrix.api.verification.SessionVerificationService import io.element.android.libraries.matrix.api.verification.SessionVerificationService
@ -40,7 +39,6 @@ import io.element.android.libraries.matrix.impl.notification.RustNotificationSer
import io.element.android.libraries.matrix.impl.pushers.RustPushersService import io.element.android.libraries.matrix.impl.pushers.RustPushersService
import io.element.android.libraries.matrix.impl.room.RustMatrixRoom import io.element.android.libraries.matrix.impl.room.RustMatrixRoom
import io.element.android.libraries.matrix.impl.room.RustRoomSummaryDataSource import io.element.android.libraries.matrix.impl.room.RustRoomSummaryDataSource
import io.element.android.libraries.matrix.impl.sync.SlidingSyncObserverProxy
import io.element.android.libraries.matrix.impl.usersearch.UserProfileMapper import io.element.android.libraries.matrix.impl.usersearch.UserProfileMapper
import io.element.android.libraries.matrix.impl.usersearch.UserSearchResultMapper import io.element.android.libraries.matrix.impl.usersearch.UserSearchResultMapper
import io.element.android.libraries.matrix.impl.verification.RustSessionVerificationService import io.element.android.libraries.matrix.impl.verification.RustSessionVerificationService
@ -50,21 +48,12 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import kotlinx.coroutines.withTimeout import kotlinx.coroutines.withTimeout
import org.matrix.rustcomponents.sdk.Client import org.matrix.rustcomponents.sdk.Client
import org.matrix.rustcomponents.sdk.ClientDelegate import org.matrix.rustcomponents.sdk.ClientDelegate
import org.matrix.rustcomponents.sdk.RequiredState
import org.matrix.rustcomponents.sdk.SlidingSyncList
import org.matrix.rustcomponents.sdk.SlidingSyncListBuilder
import org.matrix.rustcomponents.sdk.SlidingSyncListOnceBuilt
import org.matrix.rustcomponents.sdk.SlidingSyncRequestListFilters
import org.matrix.rustcomponents.sdk.SlidingSyncSelectiveModeBuilder
import org.matrix.rustcomponents.sdk.TaskHandle import org.matrix.rustcomponents.sdk.TaskHandle
import org.matrix.rustcomponents.sdk.use import org.matrix.rustcomponents.sdk.use
import timber.log.Timber import timber.log.Timber
@ -101,75 +90,11 @@ class RustMatrixClient constructor(
} }
} }
private val visibleRoomsSlidingSyncFilters = SlidingSyncRequestListFilters( private val roomList = client.roomList()
isDm = null,
spaces = emptyList(),
isEncrypted = null,
isInvite = false,
isTombstoned = false,
roomTypes = emptyList(),
notRoomTypes = listOf("m.space"),
roomNameLike = null,
tags = emptyList(),
notTags = emptyList()
)
private val visibleRoomsSlidingSyncList = MutableSharedFlow<SlidingSyncList>(replay = 1)
private val visibleRoomsSlidingSyncListBuilder = SlidingSyncListBuilder("CurrentlyVisibleRooms")
.timelineLimit(limit = 1u)
.requiredState(
requiredState = listOf(
RequiredState(key = EventType.STATE_ROOM_AVATAR, value = ""),
RequiredState(key = EventType.STATE_ROOM_ENCRYPTION, value = ""),
RequiredState(key = EventType.STATE_ROOM_JOIN_RULES, value = ""),
)
)
.filters(visibleRoomsSlidingSyncFilters)
.syncModeSelective(SlidingSyncSelectiveModeBuilder().addRange(0u, 20u))
.onceBuilt(object : SlidingSyncListOnceBuilt {
override fun updateList(list: SlidingSyncList): SlidingSyncList {
visibleRoomsSlidingSyncList.tryEmit(list)
return list
}
})
private val invitesSlidingSyncFilters = visibleRoomsSlidingSyncFilters.copy(isInvite = true)
private val invitesSlidingSyncList = MutableSharedFlow<SlidingSyncList>(replay = 1)
private val invitesSlidingSyncListBuilder = SlidingSyncListBuilder("CurrentInvites")
.timelineLimit(limit = 1u)
.requiredState(
requiredState = listOf(
RequiredState(key = EventType.STATE_ROOM_AVATAR, value = ""),
RequiredState(key = EventType.STATE_ROOM_ENCRYPTION, value = ""),
RequiredState(key = EventType.STATE_ROOM_CANONICAL_ALIAS, value = ""),
)
)
.filters(invitesSlidingSyncFilters)
.syncModeSelective(SlidingSyncSelectiveModeBuilder().addRange(0u, 20u))
.onceBuilt(object : SlidingSyncListOnceBuilt {
override fun updateList(list: SlidingSyncList): SlidingSyncList {
invitesSlidingSyncList.tryEmit(list)
return list
}
})
private val slidingSync = client
.slidingSync("ElementX")
// .homeserver("https://slidingsync.lab.matrix.org")
.withCommonExtensions()
.addList(visibleRoomsSlidingSyncListBuilder)
.addList(invitesSlidingSyncListBuilder)
.use {
it.build()
}
private val slidingSyncObserverProxy = SlidingSyncObserverProxy(coroutineScope)
private val rustRoomSummaryDataSource: RustRoomSummaryDataSource = private val rustRoomSummaryDataSource: RustRoomSummaryDataSource =
RustRoomSummaryDataSource( RustRoomSummaryDataSource(
slidingSyncObserverProxy.updateSummaryFlow, roomList,
slidingSync,
visibleRoomsSlidingSyncList,
dispatchers, dispatchers,
) )
@ -178,9 +103,7 @@ class RustMatrixClient constructor(
private val rustInvitesDataSource: RustRoomSummaryDataSource = private val rustInvitesDataSource: RustRoomSummaryDataSource =
RustRoomSummaryDataSource( RustRoomSummaryDataSource(
slidingSyncObserverProxy.updateSummaryFlow, roomList,
slidingSync,
invitesSlidingSyncList,
dispatchers, dispatchers,
) )
@ -199,25 +122,19 @@ class RustMatrixClient constructor(
init { init {
client.setDelegate(clientDelegate) client.setDelegate(clientDelegate)
rustRoomSummaryDataSource.init() rustRoomSummaryDataSource.subscribeIfNeeded()
rustInvitesDataSource.init() //rustInvitesDataSource.init()
slidingSync.setObserver(slidingSyncObserverProxy)
slidingSyncUpdateJob = slidingSyncObserverProxy.updateSummaryFlow
.onEach { onSlidingSyncUpdate() }
.launchIn(coroutineScope)
} }
override fun getRoom(roomId: RoomId): MatrixRoom? { override fun getRoom(roomId: RoomId): MatrixRoom? {
val slidingSyncRoom = slidingSync.getRoom(roomId.value) ?: return null val roomListItem = roomList.room(roomId.value)
val fullRoom = slidingSyncRoom.fullRoom() ?: return null val fullRoom = roomListItem.fullRoom()
return RustMatrixRoom( return RustMatrixRoom(
sessionId = sessionId, sessionId = sessionId,
slidingSyncUpdateFlow = slidingSyncObserverProxy.updateSummaryFlow, roomListItem = roomListItem,
slidingSyncRoom = slidingSyncRoom,
innerRoom = fullRoom, innerRoom = fullRoom,
coroutineScope = coroutineScope, coroutineScope = coroutineScope,
coroutineDispatchers = dispatchers, coroutineDispatchers = dispatchers,
clock = clock,
) )
} }
@ -261,9 +178,11 @@ class RustMatrixClient constructor(
// Wait to receive the room back from the sync // Wait to receive the room back from the sync
withTimeout(30_000L) { withTimeout(30_000L) {
slidingSyncObserverProxy.updateSummaryFlow.filter { roomId.value in it.rooms }.first() roomSummaryDataSource.roomSummaries()
.filter { roomSummaries ->
roomSummaries.map { it.identifier() }.contains(roomId.value)
}.first()
} }
roomId roomId
} }
} }
@ -301,7 +220,7 @@ class RustMatrixClient constructor(
override fun startSync() { override fun startSync() {
if (isSyncing.compareAndSet(false, true)) { if (isSyncing.compareAndSet(false, true)) {
slidingSyncObserverToken = slidingSync.sync() slidingSyncObserverToken = roomList.sync()
} }
} }
@ -314,16 +233,11 @@ class RustMatrixClient constructor(
override fun close() { override fun close() {
slidingSyncUpdateJob?.cancel() slidingSyncUpdateJob?.cancel()
stopSync() stopSync()
slidingSync.setObserver(null)
rustRoomSummaryDataSource.close() rustRoomSummaryDataSource.close()
rustInvitesDataSource.close() rustInvitesDataSource.close()
client.setDelegate(null) client.setDelegate(null)
visibleRoomsSlidingSyncListBuilder.destroy()
invitesSlidingSyncListBuilder.destroy()
visibleRoomsSlidingSyncList.resetReplayCache()
invitesSlidingSyncList.resetReplayCache()
slidingSync.destroy()
verificationService.destroy() verificationService.destroy()
roomList.destroy()
client.destroy() client.destroy()
} }

View file

@ -0,0 +1,48 @@
package io.element.android.libraries.matrix.impl.room
import io.element.android.libraries.matrix.impl.util.mxCallbackFlow
import kotlinx.coroutines.channels.trySendBlocking
import kotlinx.coroutines.flow.Flow
import org.matrix.rustcomponents.sdk.RoomList
import org.matrix.rustcomponents.sdk.RoomListEntriesListener
import org.matrix.rustcomponents.sdk.RoomListEntriesUpdate
import org.matrix.rustcomponents.sdk.RoomListEntry
import org.matrix.rustcomponents.sdk.RoomListState
import org.matrix.rustcomponents.sdk.RoomListStateListener
import org.matrix.rustcomponents.sdk.SlidingSyncListLoadingState
import org.matrix.rustcomponents.sdk.SlidingSyncListStateObserver
fun RoomList.stateFlow(): Flow<RoomListState> =
mxCallbackFlow {
val listener = object : RoomListStateListener {
override fun onUpdate(state: RoomListState) {
trySendBlocking(state)
}
}
state(listener)
}
fun RoomList.loadingStateFlow(): Flow<SlidingSyncListLoadingState> =
mxCallbackFlow {
val listener = object : SlidingSyncListStateObserver {
override fun didReceiveUpdate(newState: SlidingSyncListLoadingState) {
trySendBlocking(newState)
}
}
val result = entriesLoadingState(listener)
send(result.entriesLoadingState)
result.entriesLoadingStateStream
}
fun RoomList.roomListEntriesUpdateFlow(onInitialList: suspend (List<RoomListEntry>) -> Unit): Flow<RoomListEntriesUpdate> =
mxCallbackFlow {
val listener = object : RoomListEntriesListener {
override fun onUpdate(roomEntriesUpdate: RoomListEntriesUpdate) {
trySendBlocking(roomEntriesUpdate)
}
}
val result = entries(listener)
onInitialList(result.entries)
result.entriesStream
}

View file

@ -20,27 +20,24 @@ import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.room.RoomSummaryDetails import io.element.android.libraries.matrix.api.room.RoomSummaryDetails
import io.element.android.libraries.matrix.impl.room.message.RoomMessageFactory import io.element.android.libraries.matrix.impl.room.message.RoomMessageFactory
import org.matrix.rustcomponents.sdk.Room import org.matrix.rustcomponents.sdk.Room
import org.matrix.rustcomponents.sdk.SlidingSyncRoom import org.matrix.rustcomponents.sdk.RoomListItem
class RoomSummaryDetailsFactory(private val roomMessageFactory: RoomMessageFactory = RoomMessageFactory()) { class RoomSummaryDetailsFactory(private val roomMessageFactory: RoomMessageFactory = RoomMessageFactory()) {
fun create(slidingSyncRoom: SlidingSyncRoom, room: Room?): RoomSummaryDetails { fun create(roomListItem: RoomListItem, room: Room?): RoomSummaryDetails {
val latestRoomMessage = slidingSyncRoom.latestRoomMessage()?.use { val latestRoomMessage = roomListItem.latestEvent()?.use {
roomMessageFactory.create(it) roomMessageFactory.create(it)
} }
return RoomSummaryDetails( return RoomSummaryDetails(
roomId = RoomId(slidingSyncRoom.roomId()), roomId = RoomId(roomListItem.id()),
name = slidingSyncRoom.name() ?: slidingSyncRoom.roomId(), name = roomListItem.name() ?: roomListItem.id(),
canonicalAlias = room?.canonicalAlias(), canonicalAlias = room?.canonicalAlias(),
isDirect = room?.isDirect() ?: false, isDirect = room?.isDirect() ?: false,
avatarURLString = room?.avatarUrl(), avatarURLString = room?.avatarUrl(),
unreadNotificationCount = slidingSyncRoom.unreadNotifications().use { it.notificationCount().toInt() }, unreadNotificationCount = roomListItem.unreadNotifications().use { it.notificationCount().toInt() },
lastMessage = latestRoomMessage, lastMessage = latestRoomMessage,
lastMessageTimestamp = latestRoomMessage?.originServerTs, lastMessageTimestamp = latestRoomMessage?.originServerTs,
inviter = room?.inviter()?.let(RoomMemberMapper::map), inviter = room?.inviter()?.let(RoomMemberMapper::map),
) )
} }
} }

View file

@ -34,30 +34,28 @@ import io.element.android.libraries.matrix.impl.media.map
import io.element.android.libraries.matrix.impl.timeline.RustMatrixTimeline import io.element.android.libraries.matrix.impl.timeline.RustMatrixTimeline
import io.element.android.services.toolbox.api.systemclock.SystemClock import io.element.android.services.toolbox.api.systemclock.SystemClock
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.matrix.rustcomponents.sdk.Room import org.matrix.rustcomponents.sdk.Room
import org.matrix.rustcomponents.sdk.RoomListEntriesUpdate
import org.matrix.rustcomponents.sdk.RoomListItem
import org.matrix.rustcomponents.sdk.RoomMember import org.matrix.rustcomponents.sdk.RoomMember
import org.matrix.rustcomponents.sdk.SlidingSyncRoom
import org.matrix.rustcomponents.sdk.UpdateSummary
import org.matrix.rustcomponents.sdk.genTransactionId import org.matrix.rustcomponents.sdk.genTransactionId
import org.matrix.rustcomponents.sdk.messageEventContentFromMarkdown import org.matrix.rustcomponents.sdk.messageEventContentFromMarkdown
import java.io.File import java.io.File
class RustMatrixRoom( class RustMatrixRoom(
override val sessionId: SessionId, override val sessionId: SessionId,
private val slidingSyncUpdateFlow: Flow<UpdateSummary>, private val roomListItem: RoomListItem,
private val slidingSyncRoom: SlidingSyncRoom,
private val innerRoom: Room, private val innerRoom: Room,
private val coroutineScope: CoroutineScope, private val coroutineScope: CoroutineScope,
private val coroutineDispatchers: CoroutineDispatchers, private val coroutineDispatchers: CoroutineDispatchers,
private val clock: SystemClock,
) : MatrixRoom { ) : MatrixRoom {
override val membersStateFlow: StateFlow<MatrixRoomMembersState> override val membersStateFlow: StateFlow<MatrixRoomMembersState>
@ -69,21 +67,15 @@ class RustMatrixRoom(
RustMatrixTimeline( RustMatrixTimeline(
matrixRoom = this, matrixRoom = this,
innerRoom = innerRoom, innerRoom = innerRoom,
slidingSyncRoom = slidingSyncRoom, roomListItem = roomListItem,
coroutineScope = coroutineScope, coroutineScope = coroutineScope,
coroutineDispatchers = coroutineDispatchers coroutineDispatchers = coroutineDispatchers
) )
} }
override fun syncUpdateFlow(): Flow<Long> { override fun syncUpdateFlow(): Flow<Long> {
return slidingSyncUpdateFlow //TODO branch this somehow...
.filter { return emptyFlow()
it.rooms.contains(roomId.value)
}
.map {
clock.epochMillis()
}
.onStart { emit(clock.epochMillis()) }
} }
override fun timeline(): MatrixTimeline { override fun timeline(): MatrixTimeline {
@ -92,14 +84,14 @@ class RustMatrixRoom(
override fun close() { override fun close() {
innerRoom.destroy() innerRoom.destroy()
slidingSyncRoom.destroy() roomListItem.destroy()
} }
override val roomId = RoomId(innerRoom.id()) override val roomId = RoomId(innerRoom.id())
override val name: String? override val name: String?
get() { get() {
return slidingSyncRoom.name() return roomListItem.name()
} }
override val bestName: String override val bestName: String

View file

@ -19,36 +19,27 @@ package io.element.android.libraries.matrix.impl.room
import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.matrix.api.room.RoomSummary import io.element.android.libraries.matrix.api.room.RoomSummary
import io.element.android.libraries.matrix.api.room.RoomSummaryDataSource import io.element.android.libraries.matrix.api.room.RoomSummaryDataSource
import io.element.android.libraries.matrix.impl.sync.roomListDiff
import io.element.android.libraries.matrix.impl.sync.state
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.matrix.rustcomponents.sdk.RoomList
import org.matrix.rustcomponents.sdk.RoomListEntriesUpdate
import org.matrix.rustcomponents.sdk.RoomListEntry import org.matrix.rustcomponents.sdk.RoomListEntry
import org.matrix.rustcomponents.sdk.SlidingSync import org.matrix.rustcomponents.sdk.RoomListInput
import org.matrix.rustcomponents.sdk.SlidingSyncList import org.matrix.rustcomponents.sdk.RoomListRange
import org.matrix.rustcomponents.sdk.SlidingSyncListRoomsListDiff
import org.matrix.rustcomponents.sdk.SlidingSyncSelectiveModeBuilder
import org.matrix.rustcomponents.sdk.SlidingSyncListLoadingState import org.matrix.rustcomponents.sdk.SlidingSyncListLoadingState
import org.matrix.rustcomponents.sdk.UpdateSummary
import timber.log.Timber import timber.log.Timber
import java.io.Closeable import java.io.Closeable
import java.util.UUID import java.util.UUID
internal class RustRoomSummaryDataSource( internal class RustRoomSummaryDataSource(
private val slidingSyncUpdateFlow: Flow<UpdateSummary>, private val roomList: RoomList,
private val slidingSync: SlidingSync,
private val slidingSyncListFlow: Flow<SlidingSyncList>,
private val coroutineDispatchers: CoroutineDispatchers, private val coroutineDispatchers: CoroutineDispatchers,
private val roomSummaryDetailsFactory: RoomSummaryDetailsFactory = RoomSummaryDetailsFactory(), private val roomSummaryDetailsFactory: RoomSummaryDetailsFactory = RoomSummaryDetailsFactory(),
) : RoomSummaryDataSource, Closeable { ) : RoomSummaryDataSource, Closeable {
@ -56,39 +47,24 @@ internal class RustRoomSummaryDataSource(
private val coroutineScope = CoroutineScope(SupervisorJob() + coroutineDispatchers.io) private val coroutineScope = CoroutineScope(SupervisorJob() + coroutineDispatchers.io)
private val roomSummaries = MutableStateFlow<List<RoomSummary>>(emptyList()) private val roomSummaries = MutableStateFlow<List<RoomSummary>>(emptyList())
private val state = MutableStateFlow(SlidingSyncListLoadingState.NOT_LOADED) private val loadingState = MutableStateFlow(RoomSummaryDataSource.LoadingState.NotLoaded)
fun init() { fun subscribeIfNeeded() {
coroutineScope.launch { coroutineScope.launch {
val slidingSyncList = slidingSyncListFlow.first() roomList.roomListEntriesUpdateFlow { roomListEntries ->
val summaries = slidingSyncList.currentRoomList().map(::buildSummaryForRoomListEntry) val summaries = roomListEntries.map(::buildSummaryForRoomListEntry)
updateRoomSummaries { updateRoomSummaries {
addAll(summaries) addAll(summaries)
}
slidingSyncList.roomListDiff(this)
.onEach { diffs ->
updateRoomSummaries {
applyDiff(diffs)
}
} }
.launchIn(this) }.onEach {
updateRoomSummaries {
slidingSyncList.state(this) applyUpdate(it)
.onEach { slidingSyncState -> }
Timber.v("New sliding sync state: $slidingSyncState") }.launchIn(this)
state.value = slidingSyncState
}.launchIn(this)
} }
slidingSyncUpdateFlow
.onEach {
didReceiveSyncUpdate(it)
}.launchIn(coroutineScope)
} }
override fun close() { override fun close() {
runBlocking { slidingSyncListFlow.firstOrNull() }?.close()
coroutineScope.cancel() coroutineScope.cancel()
} }
@ -96,77 +72,64 @@ internal class RustRoomSummaryDataSource(
return roomSummaries return roomSummaries
} }
override fun loadingState(): StateFlow<RoomSummaryDataSource.LoadingState> {
return loadingState
}
override fun setSlidingSyncRange(range: IntRange) { override fun setSlidingSyncRange(range: IntRange) {
Timber.v("setVisibleRange=$range") Timber.v("setVisibleRange=$range")
coroutineScope.launch { coroutineScope.launch {
val slidingSyncMode = SlidingSyncSelectiveModeBuilder() val ranges = listOf(RoomListRange(range.first.toUInt(), range.last.toUInt()))
.addRange(range.first.toUInt(), range.last.toUInt()) roomList.applyInput(
slidingSyncListFlow.first().setSyncMode(slidingSyncMode) RoomListInput.Viewport(ranges)
)
} }
} }
private suspend fun didReceiveSyncUpdate(summary: UpdateSummary) { private fun MutableList<RoomSummary>.applyUpdate(update: RoomListEntriesUpdate) {
Timber.v("UpdateRooms with identifiers: ${summary.rooms}")
if (state.value != SlidingSyncListLoadingState.FULLY_LOADED) {
return
}
updateRoomSummaries {
for (identifier in summary.rooms) {
val index = indexOfFirst { it.identifier() == identifier }
if (index == -1) {
continue
}
val updatedRoomSummary = buildRoomSummaryForIdentifier(identifier)
set(index, updatedRoomSummary)
}
}
}
private fun MutableList<RoomSummary>.applyDiff(diff: SlidingSyncListRoomsListDiff) {
fun MutableList<RoomSummary>.fillUntil(untilIndex: Int) { fun MutableList<RoomSummary>.fillUntil(untilIndex: Int) {
repeat((size - 1 until untilIndex).count()) { repeat((size - 1 until untilIndex).count()) {
add(buildEmptyRoomSummary()) add(buildEmptyRoomSummary())
} }
} }
Timber.v("ApplyDiff: $diff for list with size: $size") when (update) {
when (diff) { is RoomListEntriesUpdate.Append -> {
is SlidingSyncListRoomsListDiff.Append -> { val roomSummaries = update.values.map {
val roomSummaries = diff.values.map {
buildSummaryForRoomListEntry(it) buildSummaryForRoomListEntry(it)
} }
addAll(roomSummaries) addAll(roomSummaries)
} }
is SlidingSyncListRoomsListDiff.PushBack -> { is RoomListEntriesUpdate.PushBack -> {
val roomSummary = buildSummaryForRoomListEntry(diff.value) val roomSummary = buildSummaryForRoomListEntry(update.value)
add(roomSummary) add(roomSummary)
} }
is SlidingSyncListRoomsListDiff.PushFront -> { is RoomListEntriesUpdate.PushFront -> {
val roomSummary = buildSummaryForRoomListEntry(diff.value) val roomSummary = buildSummaryForRoomListEntry(update.value)
add(0, roomSummary) add(0, roomSummary)
} }
is SlidingSyncListRoomsListDiff.Set -> { is RoomListEntriesUpdate.Set -> {
fillUntil(diff.index.toInt()) fillUntil(update.index.toInt())
val roomSummary = buildSummaryForRoomListEntry(diff.value) val roomSummary = buildSummaryForRoomListEntry(update.value)
set(diff.index.toInt(), roomSummary) set(update.index.toInt(), roomSummary)
} }
is SlidingSyncListRoomsListDiff.Insert -> { is RoomListEntriesUpdate.Insert -> {
val roomSummary = buildSummaryForRoomListEntry(diff.value) val roomSummary = buildSummaryForRoomListEntry(update.value)
add(diff.index.toInt(), roomSummary) add(update.index.toInt(), roomSummary)
} }
is SlidingSyncListRoomsListDiff.Remove -> { is RoomListEntriesUpdate.Remove -> {
removeAt(diff.index.toInt()) removeAt(update.index.toInt())
} }
is SlidingSyncListRoomsListDiff.Reset -> { is RoomListEntriesUpdate.Reset -> {
clear() clear()
addAll(diff.values.map { buildSummaryForRoomListEntry(it) }) addAll(update.values.map { buildSummaryForRoomListEntry(it) })
} }
SlidingSyncListRoomsListDiff.PopBack -> { RoomListEntriesUpdate.PopBack -> {
removeFirstOrNull() removeFirstOrNull()
} }
SlidingSyncListRoomsListDiff.PopFront -> { RoomListEntriesUpdate.PopFront -> {
removeLastOrNull() removeLastOrNull()
} }
SlidingSyncListRoomsListDiff.Clear -> { RoomListEntriesUpdate.Clear -> {
clear() clear()
} }
} }
@ -185,14 +148,13 @@ internal class RustRoomSummaryDataSource(
} }
private fun buildRoomSummaryForIdentifier(identifier: String): RoomSummary { private fun buildRoomSummaryForIdentifier(identifier: String): RoomSummary {
val slidingSyncRoom = slidingSync.getRoom(identifier) ?: return RoomSummary.Empty(identifier) return roomList.room(identifier).use { roomListItem ->
val fullRoom = slidingSyncRoom.fullRoom() roomListItem.fullRoom().use { fullRoom ->
val roomSummary = RoomSummary.Filled( RoomSummary.Filled(
details = roomSummaryDetailsFactory.create(slidingSyncRoom, fullRoom) details = roomSummaryDetailsFactory.create(roomListItem, fullRoom)
) )
fullRoom?.destroy() }
slidingSyncRoom.destroy() }
return roomSummary
} }
private suspend fun updateRoomSummaries(block: MutableList<RoomSummary>.() -> Unit) = private suspend fun updateRoomSummaries(block: MutableList<RoomSummary>.() -> Unit) =

View file

@ -1,62 +0,0 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.libraries.matrix.impl.sync
import io.element.android.libraries.matrix.impl.util.mxCallbackFlow
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.launch
import org.matrix.rustcomponents.sdk.SlidingSyncList
import org.matrix.rustcomponents.sdk.SlidingSyncListLoadingState
import org.matrix.rustcomponents.sdk.SlidingSyncListRoomListObserver
import org.matrix.rustcomponents.sdk.SlidingSyncListRoomsCountObserver
import org.matrix.rustcomponents.sdk.SlidingSyncListRoomsListDiff
import org.matrix.rustcomponents.sdk.SlidingSyncListStateObserver
fun SlidingSyncList.roomListDiff(scope: CoroutineScope): Flow<SlidingSyncListRoomsListDiff> =
mxCallbackFlow {
val observer = object : SlidingSyncListRoomListObserver {
override fun didReceiveUpdate(diff: SlidingSyncListRoomsListDiff) {
scope.launch {
send(diff)
}
}
}
observeRoomList(observer)
}
fun SlidingSyncList.state(scope: CoroutineScope): Flow<SlidingSyncListLoadingState> = mxCallbackFlow {
val observer = object : SlidingSyncListStateObserver {
override fun didReceiveUpdate(newState: SlidingSyncListLoadingState) {
scope.launch {
send(newState)
}
}
}
observeState(observer)
}
fun SlidingSyncList.roomsCount(scope: CoroutineScope): Flow<UInt> = mxCallbackFlow {
val observer = object : SlidingSyncListRoomsCountObserver {
override fun didReceiveUpdate(count: UInt) {
scope.launch {
send(count)
}
}
}
observeRoomsCount(observer)
}

View file

@ -1,43 +0,0 @@
/*
* Copyright (c) 2022 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.libraries.matrix.impl.sync
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.launch
import org.matrix.rustcomponents.sdk.SlidingSyncObserver
import org.matrix.rustcomponents.sdk.UpdateSummary
// Sounds like a reasonable buffer size before it suspends emitting new items.
private const val BUFFER_SIZE = 64
class SlidingSyncObserverProxy(
private val coroutineScope: CoroutineScope,
) : SlidingSyncObserver {
private val updateSummaryMutableFlow =
MutableSharedFlow<UpdateSummary>(extraBufferCapacity = BUFFER_SIZE)
val updateSummaryFlow: SharedFlow<UpdateSummary> = updateSummaryMutableFlow.asSharedFlow()
override fun didReceiveSyncUpdate(summary: UpdateSummary) {
coroutineScope.launch {
updateSummaryMutableFlow.emit(summary)
}
}
}

View file

@ -37,10 +37,10 @@ internal class MatrixTimelineDiffProcessor(
private val timelineItemFactory: MatrixTimelineItemMapper, private val timelineItemFactory: MatrixTimelineItemMapper,
) : TimelineListener { ) : TimelineListener {
override fun onUpdate(update: TimelineDiff) { override fun onUpdate(diff: TimelineDiff) {
coroutineScope.launch { coroutineScope.launch {
updateTimelineItems { updateTimelineItems {
applyDiff(update) applyDiff(diff)
} }
when (val firstItem = timelineItems.value.firstOrNull()) { when (val firstItem = timelineItems.value.firstOrNull()) {
is MatrixTimelineItem.Virtual -> updateBackPaginationState(firstItem.virtual) is MatrixTimelineItem.Virtual -> updateBackPaginationState(firstItem.virtual)

View file

@ -38,6 +38,7 @@ import kotlinx.coroutines.withContext
import org.matrix.rustcomponents.sdk.PaginationOptions import org.matrix.rustcomponents.sdk.PaginationOptions
import org.matrix.rustcomponents.sdk.RequiredState import org.matrix.rustcomponents.sdk.RequiredState
import org.matrix.rustcomponents.sdk.Room import org.matrix.rustcomponents.sdk.Room
import org.matrix.rustcomponents.sdk.RoomListItem
import org.matrix.rustcomponents.sdk.RoomSubscription import org.matrix.rustcomponents.sdk.RoomSubscription
import org.matrix.rustcomponents.sdk.SlidingSyncRoom import org.matrix.rustcomponents.sdk.SlidingSyncRoom
import org.matrix.rustcomponents.sdk.TimelineItem import org.matrix.rustcomponents.sdk.TimelineItem
@ -48,7 +49,7 @@ import java.util.concurrent.atomic.AtomicBoolean
class RustMatrixTimeline( class RustMatrixTimeline(
private val matrixRoom: MatrixRoom, private val matrixRoom: MatrixRoom,
private val innerRoom: Room, private val innerRoom: Room,
private val slidingSyncRoom: SlidingSyncRoom, private val roomListItem: RoomListItem,
private val coroutineScope: CoroutineScope, private val coroutineScope: CoroutineScope,
private val coroutineDispatchers: CoroutineDispatchers, private val coroutineDispatchers: CoroutineDispatchers,
) : MatrixTimeline { ) : MatrixTimeline {
@ -166,12 +167,12 @@ class RustMatrixTimeline(
), ),
timelineLimit = null timelineLimit = null
) )
slidingSyncRoom.subscribeToRoom(settings) roomListItem.subscribe(settings)
val result = slidingSyncRoom.addTimelineListener(timelineListener) val result = innerRoom.addTimelineListener(timelineListener)
launch { launch {
fetchMembers() fetchMembers()
} }
listenerTokens += result.taskHandle listenerTokens += result.itemsStream
result.items result.items
} }
} }