RoomList: more rework on RoomSummaryDataSource

This commit is contained in:
ganfra 2023-06-26 18:02:53 +02:00
parent e392f15872
commit e5c86675a4
11 changed files with 152 additions and 98 deletions

View file

@ -179,7 +179,7 @@ class LoggedInFlowNode @AssistedInject constructor(
lifecycleScope.launch { lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.RESUMED) { repeatOnLifecycle(Lifecycle.State.RESUMED) {
combine( combine(
syncService.syncState.debounce(100), syncService.syncState,
networkMonitor.connectivity networkMonitor.connectivity
) { syncState, networkStatus -> ) { syncState, networkStatus ->
syncState == SyncState.InError && networkStatus == NetworkStatus.Online syncState == SyncState.InError && networkStatus == NetworkStatus.Online

View file

@ -50,7 +50,7 @@ class InviteListPresenter @Inject constructor(
override fun present(): InviteListState { override fun present(): InviteListState {
val invites by client val invites by client
.roomSummaryDataSource .roomSummaryDataSource
.inviteList() .inviteRooms()
.collectAsState() .collectAsState()
var seenInvites by remember { mutableStateOf<Set<RoomId>>(emptySet()) } var seenInvites by remember { mutableStateOf<Set<RoomId>>(emptySet()) }

View file

@ -24,7 +24,6 @@ import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import dagger.assisted.Assisted import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory import dagger.assisted.AssistedFactory
@ -67,7 +66,7 @@ class ForwardMessagesPresenter @AssistedInject constructor(
var results: SearchBarResultState<ImmutableList<RoomSummaryDetails>> by remember { mutableStateOf(SearchBarResultState.NotSearching()) } var results: SearchBarResultState<ImmutableList<RoomSummaryDetails>> by remember { mutableStateOf(SearchBarResultState.NotSearching()) }
val forwardingActionState: MutableState<Async<ImmutableList<RoomId>>> = remember { mutableStateOf(Async.Uninitialized) } val forwardingActionState: MutableState<Async<ImmutableList<RoomId>>> = remember { mutableStateOf(Async.Uninitialized) }
val summaries by client.roomSummaryDataSource.roomList().collectAsState() val summaries by client.roomSummaryDataSource.allRooms().collectAsState()
LaunchedEffect(query, summaries) { LaunchedEffect(query, summaries) {
val filteredSummaries = summaries.filterIsInstance<RoomSummary.Filled>() val filteredSummaries = summaries.filterIsInstance<RoomSummary.Filled>()

View file

@ -44,7 +44,7 @@ class DefaultInviteStateDataSource @Inject constructor(
override fun inviteState(): InvitesState { override fun inviteState(): InvitesState {
val invites by client val invites by client
.roomSummaryDataSource .roomSummaryDataSource
.inviteList() .inviteRooms()
.collectAsState() .collectAsState()
val seenInvites by seenInvitesStore val seenInvites by seenInvitesStore

View file

@ -77,7 +77,7 @@ class RoomListPresenter @Inject constructor(
var filter by rememberSaveable { mutableStateOf("") } var filter by rememberSaveable { mutableStateOf("") }
val roomSummaries by client val roomSummaries by client
.roomSummaryDataSource .roomSummaryDataSource
.roomList() .allRooms()
.collectAsState() .collectAsState()
val networkConnectionStatus by networkMonitor.connectivity.collectAsState() val networkConnectionStatus by networkMonitor.connectivity.collectAsState()

View file

@ -28,7 +28,7 @@ interface RoomSummaryDataSource {
} }
fun loadingState(): StateFlow<LoadingState> fun loadingState(): StateFlow<LoadingState>
fun roomList(): StateFlow<List<RoomSummary>> fun allRooms(): StateFlow<List<RoomSummary>>
fun inviteList(): StateFlow<List<RoomSummary>> fun inviteRooms(): StateFlow<List<RoomSummary>>
fun updateRoomListVisibleRange(range: IntRange) fun updateRoomListVisibleRange(range: IntRange)
} }

View file

@ -123,7 +123,6 @@ class RustMatrixClient constructor(
onSlidingSyncUpdate() onSlidingSyncUpdate()
} }
}.launchIn(sessionCoroutineScope) }.launchIn(sessionCoroutineScope)
rustRoomSummaryDataSource.init()
} }
override fun getRoom(roomId: RoomId): MatrixRoom? { override fun getRoom(roomId: RoomId): MatrixRoom? {
@ -180,7 +179,7 @@ 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) {
roomSummaryDataSource.roomList() roomSummaryDataSource.allRooms()
.filter { roomSummaries -> .filter { roomSummaries ->
roomSummaries.map { it.identifier() }.contains(roomId.value) roomSummaries.map { it.identifier() }.contains(roomId.value)
}.first() }.first()

View file

@ -0,0 +1,128 @@
/*
* 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.room
import io.element.android.libraries.matrix.api.room.RoomSummary
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import org.matrix.rustcomponents.sdk.RoomListEntriesUpdate
import org.matrix.rustcomponents.sdk.RoomListEntry
import org.matrix.rustcomponents.sdk.RoomListService
import timber.log.Timber
import java.util.UUID
class RoomSummaryListProcessor(
private val roomSummaries: MutableStateFlow<List<RoomSummary>>,
private val roomListService: RoomListService,
private val roomSummaryDetailsFactory: RoomSummaryDetailsFactory = RoomSummaryDetailsFactory(),
) {
private val roomSummariesByIdentifier = HashMap<String, RoomSummary>()
private val mutex = Mutex()
suspend fun postEntries(entries: List<RoomListEntry>) {
updateRoomSummaries {
Timber.v("Update rooms from postEntries (with ${entries.size} items) on ${Thread.currentThread()}")
addAll(entries.map(::buildSummaryForRoomListEntry))
}
}
suspend fun postUpdate(update: RoomListEntriesUpdate) {
updateRoomSummaries {
Timber.v("Update rooms from postUpdate ($update) on ${Thread.currentThread()}")
applyUpdate(update)
}
}
private fun MutableList<RoomSummary>.applyUpdate(update: RoomListEntriesUpdate) {
when (update) {
is RoomListEntriesUpdate.Append -> {
val roomSummaries = update.values.map {
buildSummaryForRoomListEntry(it)
}
addAll(roomSummaries)
}
is RoomListEntriesUpdate.PushBack -> {
val roomSummary = buildSummaryForRoomListEntry(update.value)
add(roomSummary)
}
is RoomListEntriesUpdate.PushFront -> {
val roomSummary = buildSummaryForRoomListEntry(update.value)
add(0, roomSummary)
}
is RoomListEntriesUpdate.Set -> {
val roomSummary = buildSummaryForRoomListEntry(update.value)
this[update.index.toInt()] = roomSummary
}
is RoomListEntriesUpdate.Insert -> {
val roomSummary = buildSummaryForRoomListEntry(update.value)
add(update.index.toInt(), roomSummary)
}
is RoomListEntriesUpdate.Remove -> {
removeAt(update.index.toInt())
}
is RoomListEntriesUpdate.Reset -> {
Timber.v("Reset size: ${update.values.size}")
clear()
addAll(update.values.map { buildSummaryForRoomListEntry(it) })
}
RoomListEntriesUpdate.PopBack -> {
removeFirstOrNull()
}
RoomListEntriesUpdate.PopFront -> {
removeLastOrNull()
}
RoomListEntriesUpdate.Clear -> {
clear()
}
}
}
private fun buildSummaryForRoomListEntry(entry: RoomListEntry): RoomSummary {
return when (entry) {
RoomListEntry.Empty -> buildEmptyRoomSummary()
is RoomListEntry.Filled -> buildAndCacheRoomSummaryForIdentifier(entry.roomId)
is RoomListEntry.Invalidated -> {
roomSummariesByIdentifier[entry.roomId] ?: buildEmptyRoomSummary()
}
}
}
private fun buildEmptyRoomSummary(): RoomSummary {
return RoomSummary.Empty(UUID.randomUUID().toString())
}
private fun buildAndCacheRoomSummaryForIdentifier(identifier: String): RoomSummary {
val builtRoomSummary = roomListService.roomOrNull(identifier)?.use { roomListItem ->
roomListItem.fullRoom().use { fullRoom ->
RoomSummary.Filled(
details = roomSummaryDetailsFactory.create(roomListItem, fullRoom)
)
}
} ?: buildEmptyRoomSummary()
roomSummariesByIdentifier[builtRoomSummary.identifier()] = builtRoomSummary
return builtRoomSummary
}
private suspend fun updateRoomSummaries(block: MutableList<RoomSummary>.() -> Unit) =
mutex.withLock {
val mutableRoomSummaries = roomSummaries.value.toMutableList()
block(mutableRoomSummaries)
roomSummaries.value = mutableRoomSummaries
}
}

View file

@ -22,48 +22,44 @@ import io.element.android.libraries.matrix.api.room.RoomSummaryDataSource
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.getAndUpdate
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 org.matrix.rustcomponents.sdk.RoomListEntriesUpdate
import org.matrix.rustcomponents.sdk.RoomListEntry
import org.matrix.rustcomponents.sdk.RoomListException import org.matrix.rustcomponents.sdk.RoomListException
import org.matrix.rustcomponents.sdk.RoomListInput import org.matrix.rustcomponents.sdk.RoomListInput
import org.matrix.rustcomponents.sdk.RoomListRange import org.matrix.rustcomponents.sdk.RoomListRange
import org.matrix.rustcomponents.sdk.RoomListService import org.matrix.rustcomponents.sdk.RoomListService
import timber.log.Timber import timber.log.Timber
import java.util.UUID
internal class RustRoomSummaryDataSource( internal class RustRoomSummaryDataSource(
private val roomListService: RoomListService, private val roomListService: RoomListService,
private val sessionCoroutineScope: CoroutineScope, private val sessionCoroutineScope: CoroutineScope,
private val coroutineDispatchers: CoroutineDispatchers, coroutineDispatchers: CoroutineDispatchers,
private val roomSummaryDetailsFactory: RoomSummaryDetailsFactory = RoomSummaryDetailsFactory(), roomSummaryDetailsFactory: RoomSummaryDetailsFactory = RoomSummaryDetailsFactory(),
) : RoomSummaryDataSource { ) : RoomSummaryDataSource {
private val roomList = MutableStateFlow<List<RoomSummary>>(emptyList()) private val allRooms = MutableStateFlow<List<RoomSummary>>(emptyList())
private val inviteList = MutableStateFlow<List<RoomSummary>>(emptyList()) private val inviteRooms = MutableStateFlow<List<RoomSummary>>(emptyList())
private val loadingState = MutableStateFlow(RoomSummaryDataSource.LoadingState.NotLoaded)
fun init() { private val loadingState = MutableStateFlow(RoomSummaryDataSource.LoadingState.NotLoaded)
private val allRoomsListProcessor = RoomSummaryListProcessor(allRooms, roomListService, roomSummaryDetailsFactory)
init {
sessionCoroutineScope.launch(coroutineDispatchers.computation) { sessionCoroutineScope.launch(coroutineDispatchers.computation) {
roomListService.allRooms().entriesFlow { roomListEntries -> roomListService.allRooms().entriesFlow { roomListEntries ->
roomList.value = roomListEntries.map(::buildSummaryForRoomListEntry) allRoomsListProcessor.postEntries(roomListEntries)
}.onEach { update -> }.onEach { update ->
roomList.getAndUpdate { allRoomsListProcessor.postUpdate(update)
it.applyUpdate(update)
}
}.launchIn(this) }.launchIn(this)
} }
} }
override fun roomList(): StateFlow<List<RoomSummary>> { override fun allRooms(): StateFlow<List<RoomSummary>> {
return roomList return allRooms
} }
override fun inviteList(): StateFlow<List<RoomSummary>> { override fun inviteRooms(): StateFlow<List<RoomSummary>> {
return inviteList return inviteRooms
} }
override fun loadingState(): StateFlow<RoomSummaryDataSource.LoadingState> { override fun loadingState(): StateFlow<RoomSummaryDataSource.LoadingState> {
@ -83,72 +79,4 @@ internal class RustRoomSummaryDataSource(
} }
} }
} }
private fun List<RoomSummary>.applyUpdate(update: RoomListEntriesUpdate): List<RoomSummary> {
val newList = toMutableList()
when (update) {
is RoomListEntriesUpdate.Append -> {
val roomSummaries = update.values.map {
buildSummaryForRoomListEntry(it)
}
newList.addAll(roomSummaries)
}
is RoomListEntriesUpdate.PushBack -> {
val roomSummary = buildSummaryForRoomListEntry(update.value)
newList.add(roomSummary)
}
is RoomListEntriesUpdate.PushFront -> {
val roomSummary = buildSummaryForRoomListEntry(update.value)
newList.add(0, roomSummary)
}
is RoomListEntriesUpdate.Set -> {
val roomSummary = buildSummaryForRoomListEntry(update.value)
newList[update.index.toInt()] = roomSummary
}
is RoomListEntriesUpdate.Insert -> {
val roomSummary = buildSummaryForRoomListEntry(update.value)
newList.add(update.index.toInt(), roomSummary)
}
is RoomListEntriesUpdate.Remove -> {
newList.removeAt(update.index.toInt())
}
is RoomListEntriesUpdate.Reset -> {
newList.clear()
newList.addAll(update.values.map { buildSummaryForRoomListEntry(it) })
}
RoomListEntriesUpdate.PopBack -> {
newList.removeFirstOrNull()
}
RoomListEntriesUpdate.PopFront -> {
newList.removeLastOrNull()
}
RoomListEntriesUpdate.Clear -> {
newList.clear()
}
}
return newList
}
private fun buildSummaryForRoomListEntry(entry: RoomListEntry): RoomSummary {
return when (entry) {
RoomListEntry.Empty -> buildEmptyRoomSummary()
is RoomListEntry.Invalidated -> buildRoomSummaryForIdentifier(entry.roomId)
is RoomListEntry.Filled -> buildRoomSummaryForIdentifier(entry.roomId)
}
}
private fun buildEmptyRoomSummary(): RoomSummary {
return RoomSummary.Empty(UUID.randomUUID().toString())
}
private fun buildRoomSummaryForIdentifier(identifier: String): RoomSummary {
val roomListItem = roomListService.roomOrNull(identifier) ?: return RoomSummary.Empty(identifier)
return roomListItem.use {
roomListItem.fullRoom().use { fullRoom ->
RoomSummary.Filled(
details = roomSummaryDetailsFactory.create(roomListItem, fullRoom)
)
}
}
}
} }

View file

@ -30,7 +30,7 @@ import org.matrix.rustcomponents.sdk.RoomListServiceState
class RustSyncService( class RustSyncService(
private val roomListService: RoomListService, private val roomListService: RoomListService,
private val sessionCoroutineScope: CoroutineScope sessionCoroutineScope: CoroutineScope
) : SyncService { ) : SyncService {
override fun startSync() { override fun startSync() {

View file

@ -29,7 +29,7 @@ class FakeRoomSummaryDataSource : RoomSummaryDataSource {
roomSummariesFlow.emit(roomSummaries) roomSummariesFlow.emit(roomSummaries)
} }
override fun roomList(): StateFlow<List<RoomSummary>> { override fun allRooms(): StateFlow<List<RoomSummary>> {
return roomSummariesFlow return roomSummariesFlow
} }