diff --git a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt index 21f87e4b83..b26b025eb6 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt @@ -179,7 +179,7 @@ class LoggedInFlowNode @AssistedInject constructor( lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.RESUMED) { combine( - syncService.syncState.debounce(100), + syncService.syncState, networkMonitor.connectivity ) { syncState, networkStatus -> syncState == SyncState.InError && networkStatus == NetworkStatus.Online diff --git a/features/invitelist/impl/src/main/kotlin/io/element/android/features/invitelist/impl/InviteListPresenter.kt b/features/invitelist/impl/src/main/kotlin/io/element/android/features/invitelist/impl/InviteListPresenter.kt index d4b4b23c8f..fd649f76be 100644 --- a/features/invitelist/impl/src/main/kotlin/io/element/android/features/invitelist/impl/InviteListPresenter.kt +++ b/features/invitelist/impl/src/main/kotlin/io/element/android/features/invitelist/impl/InviteListPresenter.kt @@ -50,7 +50,7 @@ class InviteListPresenter @Inject constructor( override fun present(): InviteListState { val invites by client .roomSummaryDataSource - .inviteList() + .inviteRooms() .collectAsState() var seenInvites by remember { mutableStateOf>(emptySet()) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/forward/ForwardMessagesPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/forward/ForwardMessagesPresenter.kt index 8a5cfa6bf8..3c94eea33c 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/forward/ForwardMessagesPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/forward/ForwardMessagesPresenter.kt @@ -24,7 +24,6 @@ import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import dagger.assisted.Assisted import dagger.assisted.AssistedFactory @@ -67,7 +66,7 @@ class ForwardMessagesPresenter @AssistedInject constructor( var results: SearchBarResultState> by remember { mutableStateOf(SearchBarResultState.NotSearching()) } val forwardingActionState: MutableState>> = remember { mutableStateOf(Async.Uninitialized) } - val summaries by client.roomSummaryDataSource.roomList().collectAsState() + val summaries by client.roomSummaryDataSource.allRooms().collectAsState() LaunchedEffect(query, summaries) { val filteredSummaries = summaries.filterIsInstance() diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/DefaultInviteStateDataSource.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/DefaultInviteStateDataSource.kt index 2f387e654b..a20039355f 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/DefaultInviteStateDataSource.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/DefaultInviteStateDataSource.kt @@ -44,7 +44,7 @@ class DefaultInviteStateDataSource @Inject constructor( override fun inviteState(): InvitesState { val invites by client .roomSummaryDataSource - .inviteList() + .inviteRooms() .collectAsState() val seenInvites by seenInvitesStore diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt index b51a5dec3d..2f3c17fb3a 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt @@ -77,7 +77,7 @@ class RoomListPresenter @Inject constructor( var filter by rememberSaveable { mutableStateOf("") } val roomSummaries by client .roomSummaryDataSource - .roomList() + .allRooms() .collectAsState() val networkConnectionStatus by networkMonitor.connectivity.collectAsState() diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomSummaryDataSource.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomSummaryDataSource.kt index 4315bced9e..d1fa6fc454 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomSummaryDataSource.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomSummaryDataSource.kt @@ -28,7 +28,7 @@ interface RoomSummaryDataSource { } fun loadingState(): StateFlow - fun roomList(): StateFlow> - fun inviteList(): StateFlow> + fun allRooms(): StateFlow> + fun inviteRooms(): StateFlow> fun updateRoomListVisibleRange(range: IntRange) } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt index 5f83b995ca..baba3fd638 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt @@ -123,7 +123,6 @@ class RustMatrixClient constructor( onSlidingSyncUpdate() } }.launchIn(sessionCoroutineScope) - rustRoomSummaryDataSource.init() } override fun getRoom(roomId: RoomId): MatrixRoom? { @@ -180,7 +179,7 @@ class RustMatrixClient constructor( // Wait to receive the room back from the sync withTimeout(30_000L) { - roomSummaryDataSource.roomList() + roomSummaryDataSource.allRooms() .filter { roomSummaries -> roomSummaries.map { it.identifier() }.contains(roomId.value) }.first() diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomSummaryListProcessor.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomSummaryListProcessor.kt new file mode 100644 index 0000000000..8e4f608286 --- /dev/null +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomSummaryListProcessor.kt @@ -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>, + private val roomListService: RoomListService, + private val roomSummaryDetailsFactory: RoomSummaryDetailsFactory = RoomSummaryDetailsFactory(), +) { + + private val roomSummariesByIdentifier = HashMap() + private val mutex = Mutex() + + suspend fun postEntries(entries: List) { + 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.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.() -> Unit) = + mutex.withLock { + val mutableRoomSummaries = roomSummaries.value.toMutableList() + block(mutableRoomSummaries) + roomSummaries.value = mutableRoomSummaries + } +} diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustRoomSummaryDataSource.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustRoomSummaryDataSource.kt index 5704f9a3b6..e7fd3025bd 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustRoomSummaryDataSource.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustRoomSummaryDataSource.kt @@ -22,48 +22,44 @@ import io.element.android.libraries.matrix.api.room.RoomSummaryDataSource import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.getAndUpdate import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach 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.RoomListInput import org.matrix.rustcomponents.sdk.RoomListRange import org.matrix.rustcomponents.sdk.RoomListService import timber.log.Timber -import java.util.UUID internal class RustRoomSummaryDataSource( private val roomListService: RoomListService, private val sessionCoroutineScope: CoroutineScope, - private val coroutineDispatchers: CoroutineDispatchers, - private val roomSummaryDetailsFactory: RoomSummaryDetailsFactory = RoomSummaryDetailsFactory(), + coroutineDispatchers: CoroutineDispatchers, + roomSummaryDetailsFactory: RoomSummaryDetailsFactory = RoomSummaryDetailsFactory(), ) : RoomSummaryDataSource { - private val roomList = MutableStateFlow>(emptyList()) - private val inviteList = MutableStateFlow>(emptyList()) - private val loadingState = MutableStateFlow(RoomSummaryDataSource.LoadingState.NotLoaded) + private val allRooms = MutableStateFlow>(emptyList()) + private val inviteRooms = MutableStateFlow>(emptyList()) - fun init() { + private val loadingState = MutableStateFlow(RoomSummaryDataSource.LoadingState.NotLoaded) + private val allRoomsListProcessor = RoomSummaryListProcessor(allRooms, roomListService, roomSummaryDetailsFactory) + + init { sessionCoroutineScope.launch(coroutineDispatchers.computation) { roomListService.allRooms().entriesFlow { roomListEntries -> - roomList.value = roomListEntries.map(::buildSummaryForRoomListEntry) + allRoomsListProcessor.postEntries(roomListEntries) }.onEach { update -> - roomList.getAndUpdate { - it.applyUpdate(update) - } + allRoomsListProcessor.postUpdate(update) }.launchIn(this) } } - override fun roomList(): StateFlow> { - return roomList + override fun allRooms(): StateFlow> { + return allRooms } - override fun inviteList(): StateFlow> { - return inviteList + override fun inviteRooms(): StateFlow> { + return inviteRooms } override fun loadingState(): StateFlow { @@ -83,72 +79,4 @@ internal class RustRoomSummaryDataSource( } } } - - private fun List.applyUpdate(update: RoomListEntriesUpdate): List { - 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) - ) - } - } - } } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/sync/RustSyncService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/sync/RustSyncService.kt index 006b869aa3..12af4a262e 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/sync/RustSyncService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/sync/RustSyncService.kt @@ -30,7 +30,7 @@ import org.matrix.rustcomponents.sdk.RoomListServiceState class RustSyncService( private val roomListService: RoomListService, - private val sessionCoroutineScope: CoroutineScope + sessionCoroutineScope: CoroutineScope ) : SyncService { override fun startSync() { diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeRoomSummaryDataSource.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeRoomSummaryDataSource.kt index 85f860ea06..a407c0954f 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeRoomSummaryDataSource.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeRoomSummaryDataSource.kt @@ -29,7 +29,7 @@ class FakeRoomSummaryDataSource : RoomSummaryDataSource { roomSummariesFlow.emit(roomSummaries) } - override fun roomList(): StateFlow> { + override fun allRooms(): StateFlow> { return roomSummariesFlow }