From 4c0507a75773f16e346c44398c3587b7a8783ee0 Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 20 Jun 2023 23:22:58 +0200 Subject: [PATCH 01/21] RoomList: make the main room list working (WIP) --- .../matrix/api/room/RoomSummaryDataSource.kt | 9 ++ .../libraries/matrix/impl/RustMatrixClient.kt | 114 ++------------ .../matrix/impl/room/RoomListFlows.kt | 48 ++++++ .../impl/room/RoomSummaryDetailsFactory.kt | 15 +- .../matrix/impl/room/RustMatrixRoom.kt | 26 ++-- .../impl/room/RustRoomSummaryDataSource.kt | 144 +++++++----------- .../matrix/impl/sync/SlidingSyncListFlows.kt | 62 -------- .../impl/sync/SlidingSyncObserverProxy.kt | 43 ------ .../timeline/MatrixTimelineDiffProcessor.kt | 4 +- .../impl/timeline/RustMatrixTimeline.kt | 9 +- 10 files changed, 146 insertions(+), 328 deletions(-) create mode 100644 libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomListFlows.kt delete mode 100644 libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/sync/SlidingSyncListFlows.kt delete mode 100644 libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/sync/SlidingSyncObserverProxy.kt 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 0e71dd2558..6473a45bc7 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 @@ -19,6 +19,15 @@ package io.element.android.libraries.matrix.api.room import kotlinx.coroutines.flow.StateFlow interface RoomSummaryDataSource { + + enum class LoadingState { + NotLoaded, + PreLoaded, + PartiallyLoaded, + FullyLoaded, + } + + fun loadingState(): StateFlow fun roomSummaries(): StateFlow> fun setSlidingSyncRange(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 d8cd8c480f..e0cf58d3de 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 @@ -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.RoomMembershipObserver 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.MatrixUser 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.room.RustMatrixRoom 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.UserSearchResultMapper import io.element.android.libraries.matrix.impl.verification.RustSessionVerificationService @@ -50,21 +48,12 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.Job -import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.withContext import kotlinx.coroutines.withTimeout import org.matrix.rustcomponents.sdk.Client 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.use import timber.log.Timber @@ -101,75 +90,11 @@ class RustMatrixClient constructor( } } - private val visibleRoomsSlidingSyncFilters = SlidingSyncRequestListFilters( - isDm = null, - spaces = emptyList(), - isEncrypted = null, - isInvite = false, - isTombstoned = false, - roomTypes = emptyList(), - notRoomTypes = listOf("m.space"), - roomNameLike = null, - tags = emptyList(), - notTags = emptyList() - ) + private val roomList = client.roomList() - private val visibleRoomsSlidingSyncList = MutableSharedFlow(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(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 = RustRoomSummaryDataSource( - slidingSyncObserverProxy.updateSummaryFlow, - slidingSync, - visibleRoomsSlidingSyncList, + roomList, dispatchers, ) @@ -178,9 +103,7 @@ class RustMatrixClient constructor( private val rustInvitesDataSource: RustRoomSummaryDataSource = RustRoomSummaryDataSource( - slidingSyncObserverProxy.updateSummaryFlow, - slidingSync, - invitesSlidingSyncList, + roomList, dispatchers, ) @@ -199,25 +122,19 @@ class RustMatrixClient constructor( init { client.setDelegate(clientDelegate) - rustRoomSummaryDataSource.init() - rustInvitesDataSource.init() - slidingSync.setObserver(slidingSyncObserverProxy) - slidingSyncUpdateJob = slidingSyncObserverProxy.updateSummaryFlow - .onEach { onSlidingSyncUpdate() } - .launchIn(coroutineScope) + rustRoomSummaryDataSource.subscribeIfNeeded() + //rustInvitesDataSource.init() } override fun getRoom(roomId: RoomId): MatrixRoom? { - val slidingSyncRoom = slidingSync.getRoom(roomId.value) ?: return null - val fullRoom = slidingSyncRoom.fullRoom() ?: return null + val roomListItem = roomList.room(roomId.value) + val fullRoom = roomListItem.fullRoom() return RustMatrixRoom( sessionId = sessionId, - slidingSyncUpdateFlow = slidingSyncObserverProxy.updateSummaryFlow, - slidingSyncRoom = slidingSyncRoom, + roomListItem = roomListItem, innerRoom = fullRoom, coroutineScope = coroutineScope, coroutineDispatchers = dispatchers, - clock = clock, ) } @@ -261,9 +178,11 @@ class RustMatrixClient constructor( // Wait to receive the room back from the sync 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 } } @@ -301,7 +220,7 @@ class RustMatrixClient constructor( override fun startSync() { if (isSyncing.compareAndSet(false, true)) { - slidingSyncObserverToken = slidingSync.sync() + slidingSyncObserverToken = roomList.sync() } } @@ -314,16 +233,11 @@ class RustMatrixClient constructor( override fun close() { slidingSyncUpdateJob?.cancel() stopSync() - slidingSync.setObserver(null) rustRoomSummaryDataSource.close() rustInvitesDataSource.close() client.setDelegate(null) - visibleRoomsSlidingSyncListBuilder.destroy() - invitesSlidingSyncListBuilder.destroy() - visibleRoomsSlidingSyncList.resetReplayCache() - invitesSlidingSyncList.resetReplayCache() - slidingSync.destroy() verificationService.destroy() + roomList.destroy() client.destroy() } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomListFlows.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomListFlows.kt new file mode 100644 index 0000000000..e9d34e468d --- /dev/null +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomListFlows.kt @@ -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 = + mxCallbackFlow { + val listener = object : RoomListStateListener { + override fun onUpdate(state: RoomListState) { + trySendBlocking(state) + } + } + state(listener) + } + +fun RoomList.loadingStateFlow(): Flow = + 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) -> Unit): Flow = + mxCallbackFlow { + val listener = object : RoomListEntriesListener { + override fun onUpdate(roomEntriesUpdate: RoomListEntriesUpdate) { + trySendBlocking(roomEntriesUpdate) + } + } + val result = entries(listener) + onInitialList(result.entries) + result.entriesStream + } + diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomSummaryDetailsFactory.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomSummaryDetailsFactory.kt index 1f8be2fa21..40ae88bb9a 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomSummaryDetailsFactory.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomSummaryDetailsFactory.kt @@ -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.impl.room.message.RoomMessageFactory 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()) { - fun create(slidingSyncRoom: SlidingSyncRoom, room: Room?): RoomSummaryDetails { - val latestRoomMessage = slidingSyncRoom.latestRoomMessage()?.use { + fun create(roomListItem: RoomListItem, room: Room?): RoomSummaryDetails { + val latestRoomMessage = roomListItem.latestEvent()?.use { roomMessageFactory.create(it) } - return RoomSummaryDetails( - roomId = RoomId(slidingSyncRoom.roomId()), - name = slidingSyncRoom.name() ?: slidingSyncRoom.roomId(), + roomId = RoomId(roomListItem.id()), + name = roomListItem.name() ?: roomListItem.id(), canonicalAlias = room?.canonicalAlias(), isDirect = room?.isDirect() ?: false, avatarURLString = room?.avatarUrl(), - unreadNotificationCount = slidingSyncRoom.unreadNotifications().use { it.notificationCount().toInt() }, + unreadNotificationCount = roomListItem.unreadNotifications().use { it.notificationCount().toInt() }, lastMessage = latestRoomMessage, lastMessageTimestamp = latestRoomMessage?.originServerTs, inviter = room?.inviter()?.let(RoomMemberMapper::map), ) } - - } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt index 8452ef193b..dd33c99d47 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt @@ -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.services.toolbox.api.systemclock.SystemClock import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.withContext 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.SlidingSyncRoom -import org.matrix.rustcomponents.sdk.UpdateSummary import org.matrix.rustcomponents.sdk.genTransactionId import org.matrix.rustcomponents.sdk.messageEventContentFromMarkdown import java.io.File class RustMatrixRoom( override val sessionId: SessionId, - private val slidingSyncUpdateFlow: Flow, - private val slidingSyncRoom: SlidingSyncRoom, + private val roomListItem: RoomListItem, private val innerRoom: Room, private val coroutineScope: CoroutineScope, private val coroutineDispatchers: CoroutineDispatchers, - private val clock: SystemClock, ) : MatrixRoom { override val membersStateFlow: StateFlow @@ -69,21 +67,15 @@ class RustMatrixRoom( RustMatrixTimeline( matrixRoom = this, innerRoom = innerRoom, - slidingSyncRoom = slidingSyncRoom, + roomListItem = roomListItem, coroutineScope = coroutineScope, coroutineDispatchers = coroutineDispatchers ) } override fun syncUpdateFlow(): Flow { - return slidingSyncUpdateFlow - .filter { - it.rooms.contains(roomId.value) - } - .map { - clock.epochMillis() - } - .onStart { emit(clock.epochMillis()) } + //TODO branch this somehow... + return emptyFlow() } override fun timeline(): MatrixTimeline { @@ -92,14 +84,14 @@ class RustMatrixRoom( override fun close() { innerRoom.destroy() - slidingSyncRoom.destroy() + roomListItem.destroy() } override val roomId = RoomId(innerRoom.id()) override val name: String? get() { - return slidingSyncRoom.name() + return roomListItem.name() } override val bestName: String 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 34c0a9cb48..bf32155db8 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 @@ -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.matrix.api.room.RoomSummary 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.SupervisorJob import kotlinx.coroutines.cancel -import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking 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.SlidingSync -import org.matrix.rustcomponents.sdk.SlidingSyncList -import org.matrix.rustcomponents.sdk.SlidingSyncListRoomsListDiff -import org.matrix.rustcomponents.sdk.SlidingSyncSelectiveModeBuilder +import org.matrix.rustcomponents.sdk.RoomListInput +import org.matrix.rustcomponents.sdk.RoomListRange import org.matrix.rustcomponents.sdk.SlidingSyncListLoadingState -import org.matrix.rustcomponents.sdk.UpdateSummary import timber.log.Timber import java.io.Closeable import java.util.UUID internal class RustRoomSummaryDataSource( - private val slidingSyncUpdateFlow: Flow, - private val slidingSync: SlidingSync, - private val slidingSyncListFlow: Flow, + private val roomList: RoomList, private val coroutineDispatchers: CoroutineDispatchers, private val roomSummaryDetailsFactory: RoomSummaryDetailsFactory = RoomSummaryDetailsFactory(), ) : RoomSummaryDataSource, Closeable { @@ -56,39 +47,24 @@ internal class RustRoomSummaryDataSource( private val coroutineScope = CoroutineScope(SupervisorJob() + coroutineDispatchers.io) private val roomSummaries = MutableStateFlow>(emptyList()) - private val state = MutableStateFlow(SlidingSyncListLoadingState.NOT_LOADED) + private val loadingState = MutableStateFlow(RoomSummaryDataSource.LoadingState.NotLoaded) - fun init() { + fun subscribeIfNeeded() { coroutineScope.launch { - val slidingSyncList = slidingSyncListFlow.first() - val summaries = slidingSyncList.currentRoomList().map(::buildSummaryForRoomListEntry) - updateRoomSummaries { - addAll(summaries) - } - - slidingSyncList.roomListDiff(this) - .onEach { diffs -> - updateRoomSummaries { - applyDiff(diffs) - } + roomList.roomListEntriesUpdateFlow { roomListEntries -> + val summaries = roomListEntries.map(::buildSummaryForRoomListEntry) + updateRoomSummaries { + addAll(summaries) } - .launchIn(this) - - slidingSyncList.state(this) - .onEach { slidingSyncState -> - Timber.v("New sliding sync state: $slidingSyncState") - state.value = slidingSyncState - }.launchIn(this) + }.onEach { + updateRoomSummaries { + applyUpdate(it) + } + }.launchIn(this) } - - slidingSyncUpdateFlow - .onEach { - didReceiveSyncUpdate(it) - }.launchIn(coroutineScope) } override fun close() { - runBlocking { slidingSyncListFlow.firstOrNull() }?.close() coroutineScope.cancel() } @@ -96,77 +72,64 @@ internal class RustRoomSummaryDataSource( return roomSummaries } + override fun loadingState(): StateFlow { + return loadingState + } + override fun setSlidingSyncRange(range: IntRange) { Timber.v("setVisibleRange=$range") coroutineScope.launch { - val slidingSyncMode = SlidingSyncSelectiveModeBuilder() - .addRange(range.first.toUInt(), range.last.toUInt()) - slidingSyncListFlow.first().setSyncMode(slidingSyncMode) + val ranges = listOf(RoomListRange(range.first.toUInt(), range.last.toUInt())) + roomList.applyInput( + RoomListInput.Viewport(ranges) + ) } } - private suspend fun didReceiveSyncUpdate(summary: UpdateSummary) { - 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.applyDiff(diff: SlidingSyncListRoomsListDiff) { + private fun MutableList.applyUpdate(update: RoomListEntriesUpdate) { fun MutableList.fillUntil(untilIndex: Int) { repeat((size - 1 until untilIndex).count()) { add(buildEmptyRoomSummary()) } } - Timber.v("ApplyDiff: $diff for list with size: $size") - when (diff) { - is SlidingSyncListRoomsListDiff.Append -> { - val roomSummaries = diff.values.map { + when (update) { + is RoomListEntriesUpdate.Append -> { + val roomSummaries = update.values.map { buildSummaryForRoomListEntry(it) } addAll(roomSummaries) } - is SlidingSyncListRoomsListDiff.PushBack -> { - val roomSummary = buildSummaryForRoomListEntry(diff.value) + is RoomListEntriesUpdate.PushBack -> { + val roomSummary = buildSummaryForRoomListEntry(update.value) add(roomSummary) } - is SlidingSyncListRoomsListDiff.PushFront -> { - val roomSummary = buildSummaryForRoomListEntry(diff.value) + is RoomListEntriesUpdate.PushFront -> { + val roomSummary = buildSummaryForRoomListEntry(update.value) add(0, roomSummary) } - is SlidingSyncListRoomsListDiff.Set -> { - fillUntil(diff.index.toInt()) - val roomSummary = buildSummaryForRoomListEntry(diff.value) - set(diff.index.toInt(), roomSummary) + is RoomListEntriesUpdate.Set -> { + fillUntil(update.index.toInt()) + val roomSummary = buildSummaryForRoomListEntry(update.value) + set(update.index.toInt(), roomSummary) } - is SlidingSyncListRoomsListDiff.Insert -> { - val roomSummary = buildSummaryForRoomListEntry(diff.value) - add(diff.index.toInt(), roomSummary) + is RoomListEntriesUpdate.Insert -> { + val roomSummary = buildSummaryForRoomListEntry(update.value) + add(update.index.toInt(), roomSummary) } - is SlidingSyncListRoomsListDiff.Remove -> { - removeAt(diff.index.toInt()) + is RoomListEntriesUpdate.Remove -> { + removeAt(update.index.toInt()) } - is SlidingSyncListRoomsListDiff.Reset -> { + is RoomListEntriesUpdate.Reset -> { clear() - addAll(diff.values.map { buildSummaryForRoomListEntry(it) }) + addAll(update.values.map { buildSummaryForRoomListEntry(it) }) } - SlidingSyncListRoomsListDiff.PopBack -> { + RoomListEntriesUpdate.PopBack -> { removeFirstOrNull() } - SlidingSyncListRoomsListDiff.PopFront -> { + RoomListEntriesUpdate.PopFront -> { removeLastOrNull() } - SlidingSyncListRoomsListDiff.Clear -> { + RoomListEntriesUpdate.Clear -> { clear() } } @@ -185,14 +148,13 @@ internal class RustRoomSummaryDataSource( } private fun buildRoomSummaryForIdentifier(identifier: String): RoomSummary { - val slidingSyncRoom = slidingSync.getRoom(identifier) ?: return RoomSummary.Empty(identifier) - val fullRoom = slidingSyncRoom.fullRoom() - val roomSummary = RoomSummary.Filled( - details = roomSummaryDetailsFactory.create(slidingSyncRoom, fullRoom) - ) - fullRoom?.destroy() - slidingSyncRoom.destroy() - return roomSummary + return roomList.room(identifier).use { roomListItem -> + roomListItem.fullRoom().use { fullRoom -> + RoomSummary.Filled( + details = roomSummaryDetailsFactory.create(roomListItem, fullRoom) + ) + } + } } private suspend fun updateRoomSummaries(block: MutableList.() -> Unit) = diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/sync/SlidingSyncListFlows.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/sync/SlidingSyncListFlows.kt deleted file mode 100644 index eb8019a79d..0000000000 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/sync/SlidingSyncListFlows.kt +++ /dev/null @@ -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 = - mxCallbackFlow { - val observer = object : SlidingSyncListRoomListObserver { - override fun didReceiveUpdate(diff: SlidingSyncListRoomsListDiff) { - scope.launch { - send(diff) - } - } - } - observeRoomList(observer) - } - -fun SlidingSyncList.state(scope: CoroutineScope): Flow = mxCallbackFlow { - val observer = object : SlidingSyncListStateObserver { - override fun didReceiveUpdate(newState: SlidingSyncListLoadingState) { - scope.launch { - send(newState) - } - } - } - observeState(observer) -} - -fun SlidingSyncList.roomsCount(scope: CoroutineScope): Flow = mxCallbackFlow { - val observer = object : SlidingSyncListRoomsCountObserver { - override fun didReceiveUpdate(count: UInt) { - scope.launch { - send(count) - } - } - } - observeRoomsCount(observer) -} diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/sync/SlidingSyncObserverProxy.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/sync/SlidingSyncObserverProxy.kt deleted file mode 100644 index e37c6e0854..0000000000 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/sync/SlidingSyncObserverProxy.kt +++ /dev/null @@ -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(extraBufferCapacity = BUFFER_SIZE) - val updateSummaryFlow: SharedFlow = updateSummaryMutableFlow.asSharedFlow() - - override fun didReceiveSyncUpdate(summary: UpdateSummary) { - coroutineScope.launch { - updateSummaryMutableFlow.emit(summary) - } - } -} diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/MatrixTimelineDiffProcessor.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/MatrixTimelineDiffProcessor.kt index 7d299519a5..d44f112f38 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/MatrixTimelineDiffProcessor.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/MatrixTimelineDiffProcessor.kt @@ -37,10 +37,10 @@ internal class MatrixTimelineDiffProcessor( private val timelineItemFactory: MatrixTimelineItemMapper, ) : TimelineListener { - override fun onUpdate(update: TimelineDiff) { + override fun onUpdate(diff: TimelineDiff) { coroutineScope.launch { updateTimelineItems { - applyDiff(update) + applyDiff(diff) } when (val firstItem = timelineItems.value.firstOrNull()) { is MatrixTimelineItem.Virtual -> updateBackPaginationState(firstItem.virtual) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustMatrixTimeline.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustMatrixTimeline.kt index 4b07d59970..980c427737 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustMatrixTimeline.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustMatrixTimeline.kt @@ -38,6 +38,7 @@ import kotlinx.coroutines.withContext import org.matrix.rustcomponents.sdk.PaginationOptions import org.matrix.rustcomponents.sdk.RequiredState import org.matrix.rustcomponents.sdk.Room +import org.matrix.rustcomponents.sdk.RoomListItem import org.matrix.rustcomponents.sdk.RoomSubscription import org.matrix.rustcomponents.sdk.SlidingSyncRoom import org.matrix.rustcomponents.sdk.TimelineItem @@ -48,7 +49,7 @@ import java.util.concurrent.atomic.AtomicBoolean class RustMatrixTimeline( private val matrixRoom: MatrixRoom, private val innerRoom: Room, - private val slidingSyncRoom: SlidingSyncRoom, + private val roomListItem: RoomListItem, private val coroutineScope: CoroutineScope, private val coroutineDispatchers: CoroutineDispatchers, ) : MatrixTimeline { @@ -166,12 +167,12 @@ class RustMatrixTimeline( ), timelineLimit = null ) - slidingSyncRoom.subscribeToRoom(settings) - val result = slidingSyncRoom.addTimelineListener(timelineListener) + roomListItem.subscribe(settings) + val result = innerRoom.addTimelineListener(timelineListener) launch { fetchMembers() } - listenerTokens += result.taskHandle + listenerTokens += result.itemsStream result.items } } From 9afadaf4060893bdfcd8abf96be7fef9b671c016 Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 20 Jun 2023 23:53:55 +0200 Subject: [PATCH 02/21] RoomList: fix timeline api --- .../timeline/MatrixTimelineDiffProcessor.kt | 6 +-- .../impl/timeline/RustMatrixTimeline.kt | 31 ++++++---------- .../matrix/impl/timeline/TimelineDiffFlow.kt | 37 +++++++++++++++++++ 3 files changed, 51 insertions(+), 23 deletions(-) create mode 100644 libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/TimelineDiffFlow.kt diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/MatrixTimelineDiffProcessor.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/MatrixTimelineDiffProcessor.kt index d44f112f38..58a6f75ef1 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/MatrixTimelineDiffProcessor.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/MatrixTimelineDiffProcessor.kt @@ -27,7 +27,6 @@ import kotlinx.coroutines.withContext import org.matrix.rustcomponents.sdk.TimelineChange import org.matrix.rustcomponents.sdk.TimelineDiff import org.matrix.rustcomponents.sdk.TimelineItem -import org.matrix.rustcomponents.sdk.TimelineListener internal class MatrixTimelineDiffProcessor( private val paginationState: MutableStateFlow, @@ -35,9 +34,9 @@ internal class MatrixTimelineDiffProcessor( private val coroutineScope: CoroutineScope, private val diffDispatcher: CoroutineDispatcher, private val timelineItemFactory: MatrixTimelineItemMapper, -) : TimelineListener { +) { - override fun onUpdate(diff: TimelineDiff) { + fun onUpdate(diff: TimelineDiff) { coroutineScope.launch { updateTimelineItems { applyDiff(diff) @@ -123,5 +122,4 @@ internal class MatrixTimelineDiffProcessor( private fun TimelineItem.asMatrixTimelineItem(): MatrixTimelineItem { return timelineItemFactory.map(this) } - } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustMatrixTimeline.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustMatrixTimeline.kt index 980c427737..36044e8c11 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustMatrixTimeline.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustMatrixTimeline.kt @@ -32,6 +32,8 @@ import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.sample import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -40,9 +42,6 @@ import org.matrix.rustcomponents.sdk.RequiredState import org.matrix.rustcomponents.sdk.Room import org.matrix.rustcomponents.sdk.RoomListItem import org.matrix.rustcomponents.sdk.RoomSubscription -import org.matrix.rustcomponents.sdk.SlidingSyncRoom -import org.matrix.rustcomponents.sdk.TimelineItem -import org.matrix.rustcomponents.sdk.TimelineListener import timber.log.Timber import java.util.concurrent.atomic.AtomicBoolean @@ -74,7 +73,7 @@ class RustMatrixTimeline( ) ) - private val innerTimelineListener = MatrixTimelineDiffProcessor( + private val timelineDiffProcessor = MatrixTimelineDiffProcessor( paginationState = paginationState, timelineItems = timelineItems, coroutineScope = coroutineScope, @@ -95,13 +94,8 @@ class RustMatrixTimeline( override fun initialize() { Timber.v("Init timeline for room ${matrixRoom.roomId}") coroutineScope.launch { - val result = addListener(innerTimelineListener) - result - .onSuccess { timelineItems -> - val matrixTimelineItems = timelineItems.map(timelineItemFactory::map) - withContext(coroutineDispatchers.diffUpdateDispatcher) { - this@RustMatrixTimeline.timelineItems.value = matrixTimelineItems - } + subscribeAndAddListener(this) + .onSuccess { isInit.set(true) } .onFailure { @@ -156,8 +150,8 @@ class RustMatrixTimeline( } } - private suspend fun addListener(timelineListener: TimelineListener): Result> = withContext(coroutineDispatchers.io) { - runCatching { + private fun subscribeAndAddListener(coroutineScope: CoroutineScope): Result { + return runCatching { val settings = RoomSubscription( requiredState = listOf( RequiredState(key = EventType.STATE_ROOM_CANONICAL_ALIAS, value = ""), @@ -168,12 +162,11 @@ class RustMatrixTimeline( timelineLimit = null ) roomListItem.subscribe(settings) - val result = innerRoom.addTimelineListener(timelineListener) - launch { - fetchMembers() - } - listenerTokens += result.itemsStream - result.items + innerRoom.timelineDiffFlow { initialList -> + timelineItems.value = initialList.map(timelineItemFactory::map) + }.onEach { + timelineDiffProcessor.onUpdate(it) + }.launchIn(coroutineScope) } } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/TimelineDiffFlow.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/TimelineDiffFlow.kt new file mode 100644 index 0000000000..92ddac8f0a --- /dev/null +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/TimelineDiffFlow.kt @@ -0,0 +1,37 @@ +/* + * 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.timeline + +import io.element.android.libraries.matrix.impl.util.mxCallbackFlow +import kotlinx.coroutines.channels.trySendBlocking +import kotlinx.coroutines.flow.Flow +import org.matrix.rustcomponents.sdk.Room +import org.matrix.rustcomponents.sdk.TimelineDiff +import org.matrix.rustcomponents.sdk.TimelineItem +import org.matrix.rustcomponents.sdk.TimelineListener + +internal fun Room.timelineDiffFlow(onInitialList: suspend (List) -> Unit): Flow = + mxCallbackFlow { + val listener = object : TimelineListener { + override fun onUpdate(diff: TimelineDiff) { + trySendBlocking(diff) + } + } + val result = addTimelineListener(listener) + onInitialList(result.items) + result.itemsStream + } From cb219e629289e8f26d363e5931d9338b54852347 Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 21 Jun 2023 16:25:18 +0200 Subject: [PATCH 03/21] Room/Timeline: simplify the apis --- .../io/element/android/appnav/RoomFlowNode.kt | 12 +-- .../libraries/core/coroutine/childScopeOf.kt | 33 ++++++++ .../libraries/matrix/api/room/MatrixRoom.kt | 2 + .../matrix/api/timeline/MatrixTimeline.kt | 14 +--- .../libraries/matrix/impl/RustMatrixClient.kt | 19 +++-- .../auth/RustMatrixAuthenticationService.kt | 4 +- ...RoomListFlows.kt => RoomListExtensions.kt} | 10 +++ .../matrix/impl/room/RustMatrixRoom.kt | 73 +++++++++++++---- .../impl/room/RustRoomSummaryDataSource.kt | 20 ++--- .../timeline/MatrixTimelineDiffProcessor.kt | 3 +- .../impl/timeline/RustMatrixTimeline.kt | 80 ++----------------- .../test/timeline/FakeMatrixTimeline.kt | 12 --- .../android/samples/minimal/MainActivity.kt | 2 +- 13 files changed, 134 insertions(+), 150 deletions(-) create mode 100644 libraries/core/src/main/kotlin/io/element/android/libraries/core/coroutine/childScopeOf.kt rename libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/{RoomListFlows.kt => RoomListExtensions.kt} (86%) diff --git a/appnav/src/main/kotlin/io/element/android/appnav/RoomFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/RoomFlowNode.kt index 0bcf9000e7..b69162b3ce 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/RoomFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/RoomFlowNode.kt @@ -18,7 +18,6 @@ package io.element.android.appnav import android.os.Parcelable import androidx.compose.runtime.Composable -import androidx.compose.runtime.DisposableEffect import androidx.compose.ui.Modifier import androidx.lifecycle.lifecycleScope import com.bumble.appyx.core.composable.Children @@ -83,6 +82,7 @@ class RoomFlowNode @AssistedInject constructor( lifecycle.subscribe( onCreate = { Timber.v("OnCreate") + inputs.room.open() plugins().forEach { it.onFlowCreated(id, inputs.room) } appNavigationStateService.onNavigateToRoom(id, inputs.room.roomId) fetchRoomMembers() @@ -149,18 +149,8 @@ class RoomFlowNode @AssistedInject constructor( data class RoomMemberDetails(val userId: UserId) : NavTarget } - private val timeline = inputs.room.timeline() - @Composable override fun View(modifier: Modifier) { - - DisposableEffect(Unit) { - timeline.initialize() - onDispose { - timeline.dispose() - } - } - Children( navModel = backstack, modifier = modifier, diff --git a/libraries/core/src/main/kotlin/io/element/android/libraries/core/coroutine/childScopeOf.kt b/libraries/core/src/main/kotlin/io/element/android/libraries/core/coroutine/childScopeOf.kt new file mode 100644 index 0000000000..060431fdee --- /dev/null +++ b/libraries/core/src/main/kotlin/io/element/android/libraries/core/coroutine/childScopeOf.kt @@ -0,0 +1,33 @@ +/* + * 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.core.coroutine + +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineName +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.job +import kotlinx.coroutines.plus + +fun childScopeOf( + parentScope: CoroutineScope, + dispatcher: CoroutineDispatcher, + name: String, +): CoroutineScope = run { + val supervisorJob = SupervisorJob(parent = parentScope.coroutineContext.job) + parentScope + dispatcher + supervisorJob + CoroutineName(name) +} diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt index 0ddd75966b..b6f685a775 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt @@ -61,6 +61,8 @@ interface MatrixRoom : Closeable { fun timeline(): MatrixTimeline + fun open(): Result + suspend fun userDisplayName(userId: UserId): Result suspend fun userAvatarUrl(userId: UserId): Result diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/MatrixTimeline.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/MatrixTimeline.kt index 4eaf04d775..1f45a0ddac 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/MatrixTimeline.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/MatrixTimeline.kt @@ -28,20 +28,8 @@ interface MatrixTimeline { ) fun paginationState(): StateFlow - fun timelineItems(): Flow> + suspend fun paginateBackwards(requestSize: Int, untilNumberOfItems: Int): Result - fun initialize() - fun dispose() - - /** - * @param message markdown message - */ - suspend fun sendMessage(message: String): Result - - suspend fun editMessage(originalEventId: EventId, message: String): Result - - suspend fun replyMessage(inReplyToEventId: EventId, message: String): Result - suspend fun fetchDetailsForEvent(eventId: EventId): Result } 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 e0cf58d3de..b01cca4481 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 @@ -19,6 +19,7 @@ package io.element.android.libraries.matrix.impl import io.element.android.libraries.core.coroutine.CoroutineDispatchers +import io.element.android.libraries.core.coroutine.childScopeOf import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.UserId @@ -39,6 +40,7 @@ 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.room.RustMatrixRoom import io.element.android.libraries.matrix.impl.room.RustRoomSummaryDataSource +import io.element.android.libraries.matrix.impl.room.roomOrNull 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.verification.RustSessionVerificationService @@ -47,7 +49,7 @@ import io.element.android.services.toolbox.api.systemclock.SystemClock import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.Job +import kotlinx.coroutines.cancel import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.first import kotlinx.coroutines.withContext @@ -66,7 +68,7 @@ import org.matrix.rustcomponents.sdk.RoomVisibility as RustRoomVisibility class RustMatrixClient constructor( private val client: Client, private val sessionStore: SessionStore, - private val coroutineScope: CoroutineScope, + private val appCoroutineScope: CoroutineScope, private val dispatchers: CoroutineDispatchers, private val baseDirectory: File, private val baseCacheDirectory: File, @@ -75,13 +77,13 @@ class RustMatrixClient constructor( override val sessionId: UserId = UserId(client.userId()) + private val sessionCoroutineScope = childScopeOf(appCoroutineScope, dispatchers.main, "Session-${sessionId}") private val verificationService = RustSessionVerificationService() private val pushersService = RustPushersService( client = client, dispatchers = dispatchers, ) private val notificationService = RustNotificationService(client) - private var slidingSyncUpdateJob: Job? = null private val clientDelegate = object : ClientDelegate { override fun didReceiveAuthError(isSoftLogout: Boolean) { @@ -95,6 +97,7 @@ class RustMatrixClient constructor( private val rustRoomSummaryDataSource: RustRoomSummaryDataSource = RustRoomSummaryDataSource( roomList, + sessionCoroutineScope, dispatchers, ) @@ -104,6 +107,7 @@ class RustMatrixClient constructor( private val rustInvitesDataSource: RustRoomSummaryDataSource = RustRoomSummaryDataSource( roomList, + sessionCoroutineScope, dispatchers, ) @@ -127,14 +131,15 @@ class RustMatrixClient constructor( } override fun getRoom(roomId: RoomId): MatrixRoom? { - val roomListItem = roomList.room(roomId.value) + val roomListItem = roomList.roomOrNull(roomId.value) ?: return null val fullRoom = roomListItem.fullRoom() return RustMatrixRoom( sessionId = sessionId, roomListItem = roomListItem, innerRoom = fullRoom, - coroutineScope = coroutineScope, + sessionCoroutineScope = sessionCoroutineScope, coroutineDispatchers = dispatchers, + systemClock = clock ) } @@ -231,10 +236,8 @@ class RustMatrixClient constructor( } override fun close() { - slidingSyncUpdateJob?.cancel() stopSync() - rustRoomSummaryDataSource.close() - rustInvitesDataSource.close() + sessionCoroutineScope.cancel() client.setDelegate(null) verificationService.destroy() roomList.destroy() diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt index d599029923..5b7b17dce6 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt @@ -52,7 +52,7 @@ import org.matrix.rustcomponents.sdk.AuthenticationService as RustAuthentication class RustMatrixAuthenticationService @Inject constructor( @ApplicationContext private val context: Context, private val baseDirectory: File, - private val coroutineScope: CoroutineScope, + private val appCoroutineScope: CoroutineScope, private val coroutineDispatchers: CoroutineDispatchers, private val sessionStore: SessionStore, private val clock: SystemClock, @@ -179,7 +179,7 @@ class RustMatrixAuthenticationService @Inject constructor( return RustMatrixClient( client = client, sessionStore = sessionStore, - coroutineScope = coroutineScope, + appCoroutineScope = appCoroutineScope, dispatchers = coroutineDispatchers, baseDirectory = baseDirectory, baseCacheDirectory = context.cacheDir, diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomListFlows.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomListExtensions.kt similarity index 86% rename from libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomListFlows.kt rename to libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomListExtensions.kt index e9d34e468d..b77bd7fee3 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomListFlows.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomListExtensions.kt @@ -7,10 +7,12 @@ 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.RoomListItem import org.matrix.rustcomponents.sdk.RoomListState import org.matrix.rustcomponents.sdk.RoomListStateListener import org.matrix.rustcomponents.sdk.SlidingSyncListLoadingState import org.matrix.rustcomponents.sdk.SlidingSyncListStateObserver +import timber.log.Timber fun RoomList.stateFlow(): Flow = mxCallbackFlow { @@ -46,3 +48,11 @@ fun RoomList.roomListEntriesUpdateFlow(onInitialList: suspend (List get() = _membersStateFlow private var _membersStateFlow = MutableStateFlow(MatrixRoomMembersState.Unknown) + private val isInit = MutableStateFlow(false) + private val syncUpdateFlow = MutableStateFlow(systemClock.epochMillis()) private val timeline by lazy { RustMatrixTimeline( matrixRoom = this, innerRoom = innerRoom, - roomListItem = roomListItem, - coroutineScope = coroutineScope, + coroutineScope = roomCoroutineScope, coroutineDispatchers = coroutineDispatchers ) } override fun syncUpdateFlow(): Flow { - //TODO branch this somehow... - return emptyFlow() + return syncUpdateFlow } override fun timeline(): MatrixTimeline { return timeline } - override fun close() { - innerRoom.destroy() - roomListItem.destroy() + override fun open(): Result { + if (isInit.value) return Result.failure(IllegalStateException("Listener already registered")) + val settings = RoomSubscription( + requiredState = listOf( + RequiredState(key = EventType.STATE_ROOM_CANONICAL_ALIAS, value = ""), + RequiredState(key = EventType.STATE_ROOM_TOPIC, value = ""), + RequiredState(key = EventType.STATE_ROOM_JOIN_RULES, value = ""), + RequiredState(key = EventType.STATE_ROOM_POWER_LEVELS, value = ""), + ), + timelineLimit = null + ) + roomListItem.subscribe(settings) + innerRoom.timelineDiffFlow { initialList -> + timeline.postItems(initialList) + }.onEach { + syncUpdateFlow.value = systemClock.epochMillis() + timeline.postDiff(it) + }.launchIn(roomCoroutineScope) + roomCoroutineScope.launch { + fetchMembers() + } + isInit.value = true + return Result.success(Unit) } - override val roomId = RoomId(innerRoom.id()) + override fun close() { + if(isInit.value) { + isInit.value = false + roomCoroutineScope.cancel() + roomListItem.unsubscribe() + innerRoom.destroy() + roomListItem.destroy() + } + } override val name: String? get() { @@ -264,7 +301,7 @@ class RustMatrixRoom( } } - override suspend fun cancelSend(transactionId: String): Result = + override suspend fun cancelSend(transactionId: String): Result = withContext(coroutineDispatchers.io) { runCatching { innerRoom.cancelSend(transactionId) @@ -299,4 +336,10 @@ class RustMatrixRoom( innerRoom.setTopic(topic) } } + + private suspend fun fetchMembers() = withContext(coroutineDispatchers.io) { + runCatching { + innerRoom.fetchMembers() + } + } } 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 bf32155db8..9c0ea49dd5 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 @@ -20,8 +20,6 @@ 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.RoomSummaryDataSource import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.SupervisorJob -import kotlinx.coroutines.cancel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.launchIn @@ -33,24 +31,21 @@ import org.matrix.rustcomponents.sdk.RoomListEntriesUpdate import org.matrix.rustcomponents.sdk.RoomListEntry import org.matrix.rustcomponents.sdk.RoomListInput import org.matrix.rustcomponents.sdk.RoomListRange -import org.matrix.rustcomponents.sdk.SlidingSyncListLoadingState import timber.log.Timber -import java.io.Closeable import java.util.UUID internal class RustRoomSummaryDataSource( private val roomList: RoomList, + private val sessionCoroutineScope: CoroutineScope, private val coroutineDispatchers: CoroutineDispatchers, private val roomSummaryDetailsFactory: RoomSummaryDetailsFactory = RoomSummaryDetailsFactory(), -) : RoomSummaryDataSource, Closeable { - - private val coroutineScope = CoroutineScope(SupervisorJob() + coroutineDispatchers.io) +) : RoomSummaryDataSource { private val roomSummaries = MutableStateFlow>(emptyList()) private val loadingState = MutableStateFlow(RoomSummaryDataSource.LoadingState.NotLoaded) fun subscribeIfNeeded() { - coroutineScope.launch { + sessionCoroutineScope.launch { roomList.roomListEntriesUpdateFlow { roomListEntries -> val summaries = roomListEntries.map(::buildSummaryForRoomListEntry) updateRoomSummaries { @@ -64,10 +59,6 @@ internal class RustRoomSummaryDataSource( } } - override fun close() { - coroutineScope.cancel() - } - override fun roomSummaries(): StateFlow> { return roomSummaries } @@ -78,7 +69,7 @@ internal class RustRoomSummaryDataSource( override fun setSlidingSyncRange(range: IntRange) { Timber.v("setVisibleRange=$range") - coroutineScope.launch { + sessionCoroutineScope.launch { val ranges = listOf(RoomListRange(range.first.toUInt(), range.last.toUInt())) roomList.applyInput( RoomListInput.Viewport(ranges) @@ -148,7 +139,8 @@ internal class RustRoomSummaryDataSource( } private fun buildRoomSummaryForIdentifier(identifier: String): RoomSummary { - return roomList.room(identifier).use { roomListItem -> + val roomListItem = roomList.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/timeline/MatrixTimelineDiffProcessor.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/MatrixTimelineDiffProcessor.kt index 58a6f75ef1..bd30d826f2 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/MatrixTimelineDiffProcessor.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/MatrixTimelineDiffProcessor.kt @@ -36,7 +36,7 @@ internal class MatrixTimelineDiffProcessor( private val timelineItemFactory: MatrixTimelineItemMapper, ) { - fun onUpdate(diff: TimelineDiff) { + fun postDiff(diff: TimelineDiff) { coroutineScope.launch { updateTimelineItems { applyDiff(diff) @@ -122,4 +122,5 @@ internal class MatrixTimelineDiffProcessor( private fun TimelineItem.asMatrixTimelineItem(): MatrixTimelineItem { return timelineItemFactory.map(this) } + } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustMatrixTimeline.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustMatrixTimeline.kt index 36044e8c11..dcb518bf98 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustMatrixTimeline.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustMatrixTimeline.kt @@ -21,40 +21,30 @@ import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.timeline.MatrixTimeline import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem -import io.element.android.libraries.matrix.api.timeline.item.event.EventType import io.element.android.libraries.matrix.impl.timeline.item.event.EventMessageMapper import io.element.android.libraries.matrix.impl.timeline.item.event.EventTimelineItemMapper import io.element.android.libraries.matrix.impl.timeline.item.event.TimelineEventContentMapper import io.element.android.libraries.matrix.impl.timeline.item.virtual.VirtualTimelineItemMapper -import io.element.android.libraries.matrix.impl.util.TaskHandleBag import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.sample -import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.matrix.rustcomponents.sdk.PaginationOptions -import org.matrix.rustcomponents.sdk.RequiredState import org.matrix.rustcomponents.sdk.Room -import org.matrix.rustcomponents.sdk.RoomListItem -import org.matrix.rustcomponents.sdk.RoomSubscription +import org.matrix.rustcomponents.sdk.TimelineDiff +import org.matrix.rustcomponents.sdk.TimelineItem import timber.log.Timber -import java.util.concurrent.atomic.AtomicBoolean class RustMatrixTimeline( + coroutineScope: CoroutineScope, private val matrixRoom: MatrixRoom, private val innerRoom: Room, - private val roomListItem: RoomListItem, - private val coroutineScope: CoroutineScope, private val coroutineDispatchers: CoroutineDispatchers, ) : MatrixTimeline { - private val isInit = AtomicBoolean(false) - private val timelineItems: MutableStateFlow> = MutableStateFlow(emptyList()) @@ -81,7 +71,6 @@ class RustMatrixTimeline( timelineItemFactory = timelineItemFactory, ) - private val listenerTokens = TaskHandleBag() override fun paginationState(): StateFlow { return paginationState } @@ -91,38 +80,12 @@ class RustMatrixTimeline( return timelineItems.sample(50) } - override fun initialize() { - Timber.v("Init timeline for room ${matrixRoom.roomId}") - coroutineScope.launch { - subscribeAndAddListener(this) - .onSuccess { - isInit.set(true) - } - .onFailure { - Timber.e("Failed adding timeline listener on room with identifier: ${matrixRoom.roomId})") - } - } + internal fun postItems(items: List) { + timelineItems.value = items.map(timelineItemFactory::map) } - override fun dispose() { - Timber.v("Dispose timeline for room ${matrixRoom.roomId}") - listenerTokens.dispose() - isInit.set(false) - } - - /** - * @param message markdown message - */ - override suspend fun sendMessage(message: String): Result { - return matrixRoom.sendMessage(message) - } - - override suspend fun editMessage(originalEventId: EventId, message: String): Result { - return matrixRoom.editMessage(originalEventId, message = message) - } - - override suspend fun replyMessage(inReplyToEventId: EventId, message: String): Result { - return matrixRoom.replyMessage(inReplyToEventId, message) + internal fun postDiff(timelineDiff: TimelineDiff) { + timelineDiffProcessor.postDiff(timelineDiff) } override suspend fun fetchDetailsForEvent(eventId: EventId): Result = withContext(coroutineDispatchers.io) { @@ -134,9 +97,6 @@ class RustMatrixTimeline( override suspend fun paginateBackwards(requestSize: Int, untilNumberOfItems: Int): Result = withContext(coroutineDispatchers.io) { runCatching { Timber.v("Start back paginating for room ${matrixRoom.roomId} ") - if (!isInit.get()) { - throw IllegalStateException("Timeline is not init yet") - } val paginationOptions = PaginationOptions.UntilNumItems( eventLimit = requestSize.toUShort(), items = untilNumberOfItems.toUShort(), @@ -149,30 +109,4 @@ class RustMatrixTimeline( Timber.v("Success back paginating for room ${matrixRoom.roomId}") } } - - private fun subscribeAndAddListener(coroutineScope: CoroutineScope): Result { - return runCatching { - val settings = RoomSubscription( - requiredState = listOf( - RequiredState(key = EventType.STATE_ROOM_CANONICAL_ALIAS, value = ""), - RequiredState(key = EventType.STATE_ROOM_TOPIC, value = ""), - RequiredState(key = EventType.STATE_ROOM_JOIN_RULES, value = ""), - RequiredState(key = EventType.STATE_ROOM_POWER_LEVELS, value = ""), - ), - timelineLimit = null - ) - roomListItem.subscribe(settings) - innerRoom.timelineDiffFlow { initialList -> - timelineItems.value = initialList.map(timelineItemFactory::map) - }.onEach { - timelineDiffProcessor.onUpdate(it) - }.launchIn(coroutineScope) - } - } - - private suspend fun fetchMembers() = withContext(coroutineDispatchers.io) { - runCatching { - innerRoom.fetchMembers() - } - } } diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/FakeMatrixTimeline.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/FakeMatrixTimeline.kt index 696a778df2..5adc8b8404 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/FakeMatrixTimeline.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/FakeMatrixTimeline.kt @@ -71,18 +71,6 @@ class FakeMatrixTimeline( isInitialized = false } - override suspend fun sendMessage(message: String): Result { - return Result.success(Unit) - } - - override suspend fun editMessage(originalEventId: EventId, message: String): Result { - return Result.success(Unit) - } - - override suspend fun replyMessage(inReplyToEventId: EventId, message: String): Result { - return Result.success(Unit) - } - override suspend fun fetchDetailsForEvent(eventId: EventId): Result { return Result.success(Unit) } diff --git a/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/MainActivity.kt b/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/MainActivity.kt index 62602a475b..39a655f06c 100644 --- a/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/MainActivity.kt +++ b/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/MainActivity.kt @@ -42,7 +42,7 @@ class MainActivity : ComponentActivity() { RustMatrixAuthenticationService( context = applicationContext, baseDirectory = baseDirectory, - coroutineScope = Singleton.appScope, + appCoroutineScope = Singleton.appScope, coroutineDispatchers = Singleton.coroutineDispatchers, sessionStore = InMemorySessionStore(), clock = DefaultSystemClock() From 4656af6ce82ea4923dcd8a52c51fe3cc21af8c5f Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 22 Jun 2023 12:21:10 +0200 Subject: [PATCH 04/21] RoomList: start/stop sync in resume/pause callbacks --- .../element/android/appnav/LoggedInFlowNode.kt | 7 ++++++- .../libraries/matrix/impl/RustMatrixClient.kt | 17 ++++++++++------- 2 files changed, 16 insertions(+), 8 deletions(-) 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 ba002b78ac..97e2e8b7a2 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt @@ -141,12 +141,17 @@ class LoggedInFlowNode @AssistedInject constructor( plugins().forEach { it.onFlowCreated(id, inputs.matrixClient) } val imageLoaderFactory = bindings().loggedInImageLoaderFactory() Coil.setImageLoader(imageLoaderFactory) - inputs.matrixClient.startSync() appNavigationStateService.onNavigateToSession(id, inputs.matrixClient.sessionId) // TODO We do not support Space yet, so directly navigate to main space appNavigationStateService.onNavigateToSpace(id, MAIN_SPACE) loggedInFlowProcessor.observeEvents(coroutineScope) }, + onResume = { + inputs.matrixClient.startSync() + }, + onPause = { + inputs.matrixClient.stopSync() + }, onDestroy = { val imageLoaderFactory = bindings().notLoggedInImageLoaderFactory() Coil.setImageLoader(imageLoaderFactory) 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 f45c5c44f0..54aa60e020 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 @@ -14,8 +14,6 @@ * limitations under the License. */ -@file:OptIn(ExperimentalCoroutinesApi::class) - package io.element.android.libraries.matrix.impl import io.element.android.libraries.core.coroutine.CoroutineDispatchers @@ -43,6 +41,7 @@ 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.RustRoomSummaryDataSource import io.element.android.libraries.matrix.impl.room.roomOrNull +import io.element.android.libraries.matrix.impl.room.stateFlow 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.verification.RustSessionVerificationService @@ -54,6 +53,8 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.cancel import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.withContext import kotlinx.coroutines.withTimeout import org.matrix.rustcomponents.sdk.Client @@ -61,7 +62,6 @@ import org.matrix.rustcomponents.sdk.ClientDelegate import org.matrix.rustcomponents.sdk.use import timber.log.Timber import java.io.File -import java.util.concurrent.atomic.AtomicBoolean import org.matrix.rustcomponents.sdk.CreateRoomParameters as RustCreateRoomParameters import org.matrix.rustcomponents.sdk.RoomPreset as RustRoomPreset import org.matrix.rustcomponents.sdk.RoomVisibility as RustRoomVisibility @@ -119,13 +119,16 @@ class RustMatrixClient constructor( override val mediaLoader: MatrixMediaLoader get() = rustMediaLoader - private val isSyncing = AtomicBoolean(false) - private val roomMembershipObserver = RoomMembershipObserver() init { client.setDelegate(clientDelegate) rustRoomSummaryDataSource.subscribeIfNeeded() + roomList.stateFlow() + .onEach { + Timber.v("onRoomList state change: $it") + } + .launchIn(sessionCoroutineScope) //rustInvitesDataSource.init() } @@ -223,13 +226,13 @@ class RustMatrixClient constructor( override fun notificationService(): NotificationService = notificationService override fun startSync() { - if (isSyncing.compareAndSet(false, true)) { + if (!roomList.isSyncing()) { roomList.sync() } } override fun stopSync() { - if (isSyncing.compareAndSet(true, false)) { + if (roomList.isSyncing()) { roomList.stopSync() } } From ef8dc6ea7a590a59f3e2b05588a351af98f091a7 Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 22 Jun 2023 12:34:07 +0200 Subject: [PATCH 05/21] RoomList: change a bit the api of RoomSummaryDataSource --- .../invitelist/impl/InviteListPresenter.kt | 4 +- .../impl/DefaultInviteStateDataSource.kt | 4 +- .../roomlist/impl/RoomListPresenter.kt | 4 +- .../libraries/matrix/api/MatrixClient.kt | 1 - .../matrix/api/room/RoomSummaryDataSource.kt | 5 +- .../libraries/matrix/impl/RustMatrixClient.kt | 37 +++----- .../matrix/impl/room/RoomListExtensions.kt | 9 +- .../matrix/impl/room/RustMatrixRoom.kt | 2 +- .../impl/room/RustRoomSummaryDataSource.kt | 90 +++++++++---------- .../impl/timeline/MatrixTimelineItemMapper.kt | 4 +- .../impl/timeline/RustMatrixTimeline.kt | 6 +- .../test/room/FakeRoomSummaryDataSource.kt | 4 +- 12 files changed, 78 insertions(+), 92 deletions(-) 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 db9fec3153..d4b4b23c8f 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 @@ -49,8 +49,8 @@ class InviteListPresenter @Inject constructor( @Composable override fun present(): InviteListState { val invites by client - .invitesDataSource - .roomSummaries() + .roomSummaryDataSource + .inviteList() .collectAsState() var seenInvites by remember { mutableStateOf>(emptySet()) } 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 74697a5c06..2f387e654b 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 @@ -43,8 +43,8 @@ class DefaultInviteStateDataSource @Inject constructor( @Composable override fun inviteState(): InvitesState { val invites by client - .invitesDataSource - .roomSummaries() + .roomSummaryDataSource + .inviteList() .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 9448ff4b1b..30396da74d 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 - .roomSummaries() + .roomList() .collectAsState() val networkConnectionStatus by networkMonitor.connectivity.collectAsState(initial = networkMonitor.currentConnectivityStatus) @@ -177,7 +177,7 @@ class RoomListPresenter @Inject constructor( // Safe to give bigger size than room list val extendedRangeEnd = range.last + midExtendedRangeSize val extendedRange = IntRange(extendedRangeStart, extendedRangeEnd) - client.roomSummaryDataSource.setSlidingSyncRange(extendedRange) + client.roomSummaryDataSource.updateRoomListVisibleRange(extendedRange) } private suspend fun mapRoomSummaries( diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt index 4a018e18da..485049585c 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt @@ -35,7 +35,6 @@ import java.io.Closeable interface MatrixClient : Closeable { val sessionId: SessionId val roomSummaryDataSource: RoomSummaryDataSource - val invitesDataSource: RoomSummaryDataSource val mediaLoader: MatrixMediaLoader fun getRoom(roomId: RoomId): MatrixRoom? fun findDM(userId: UserId): MatrixRoom? 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 6473a45bc7..4315bced9e 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,6 +28,7 @@ interface RoomSummaryDataSource { } fun loadingState(): StateFlow - fun roomSummaries(): StateFlow> - fun setSlidingSyncRange(range: IntRange) + fun roomList(): StateFlow> + fun inviteList(): 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 54aa60e020..17b36f49a9 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 @@ -93,28 +93,18 @@ class RustMatrixClient constructor( } } - private val roomList = client.roomList() + private val roomListService = client.roomList() private val rustRoomSummaryDataSource: RustRoomSummaryDataSource = RustRoomSummaryDataSource( - roomList, - sessionCoroutineScope, - dispatchers, + roomListService = roomListService, + sessionCoroutineScope = sessionCoroutineScope, + coroutineDispatchers = dispatchers, ) override val roomSummaryDataSource: RoomSummaryDataSource get() = rustRoomSummaryDataSource - private val rustInvitesDataSource: RustRoomSummaryDataSource = - RustRoomSummaryDataSource( - roomList, - sessionCoroutineScope, - dispatchers, - ) - - override val invitesDataSource: RoomSummaryDataSource - get() = rustInvitesDataSource - private val rustMediaLoader = RustMediaLoader(baseCacheDirectory, dispatchers, client) override val mediaLoader: MatrixMediaLoader get() = rustMediaLoader @@ -123,17 +113,16 @@ class RustMatrixClient constructor( init { client.setDelegate(clientDelegate) - rustRoomSummaryDataSource.subscribeIfNeeded() - roomList.stateFlow() + rustRoomSummaryDataSource.init() + roomListService.stateFlow() .onEach { Timber.v("onRoomList state change: $it") } .launchIn(sessionCoroutineScope) - //rustInvitesDataSource.init() } override fun getRoom(roomId: RoomId): MatrixRoom? { - val roomListItem = roomList.roomOrNull(roomId.value) ?: return null + val roomListItem = roomListService.roomOrNull(roomId.value) ?: return null val fullRoom = roomListItem.fullRoom() return RustMatrixRoom( sessionId = sessionId, @@ -185,7 +174,7 @@ class RustMatrixClient constructor( // Wait to receive the room back from the sync withTimeout(30_000L) { - roomSummaryDataSource.roomSummaries() + roomSummaryDataSource.roomList() .filter { roomSummaries -> roomSummaries.map { it.identifier() }.contains(roomId.value) }.first() @@ -226,14 +215,14 @@ class RustMatrixClient constructor( override fun notificationService(): NotificationService = notificationService override fun startSync() { - if (!roomList.isSyncing()) { - roomList.sync() + if (!roomListService.isSyncing()) { + roomListService.sync() } } override fun stopSync() { - if (roomList.isSyncing()) { - roomList.stopSync() + if (roomListService.isSyncing()) { + roomListService.stopSync() } } @@ -242,7 +231,7 @@ class RustMatrixClient constructor( sessionCoroutineScope.cancel() client.setDelegate(null) verificationService.destroy() - roomList.destroy() + roomListService.destroy() client.destroy() } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomListExtensions.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomListExtensions.kt index b77bd7fee3..c45d8967b4 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomListExtensions.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomListExtensions.kt @@ -7,6 +7,7 @@ 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.RoomListInterface import org.matrix.rustcomponents.sdk.RoomListItem import org.matrix.rustcomponents.sdk.RoomListState import org.matrix.rustcomponents.sdk.RoomListStateListener @@ -14,7 +15,7 @@ import org.matrix.rustcomponents.sdk.SlidingSyncListLoadingState import org.matrix.rustcomponents.sdk.SlidingSyncListStateObserver import timber.log.Timber -fun RoomList.stateFlow(): Flow = +fun RoomListInterface.stateFlow(): Flow = mxCallbackFlow { val listener = object : RoomListStateListener { override fun onUpdate(state: RoomListState) { @@ -24,7 +25,7 @@ fun RoomList.stateFlow(): Flow = state(listener) } -fun RoomList.loadingStateFlow(): Flow = +fun RoomListInterface.loadingStateFlow(): Flow = mxCallbackFlow { val listener = object : SlidingSyncListStateObserver { override fun didReceiveUpdate(newState: SlidingSyncListLoadingState) { @@ -36,7 +37,7 @@ fun RoomList.loadingStateFlow(): Flow = result.entriesLoadingStateStream } -fun RoomList.roomListEntriesUpdateFlow(onInitialList: suspend (List) -> Unit): Flow = +fun RoomListInterface.roomListEntriesUpdateFlow(onInitialList: suspend (List) -> Unit): Flow = mxCallbackFlow { val listener = object : RoomListEntriesListener { override fun onUpdate(roomEntriesUpdate: RoomListEntriesUpdate) { @@ -48,7 +49,7 @@ fun RoomList.roomListEntriesUpdateFlow(onInitialList: suspend (List>(emptyList()) + private val roomList = MutableStateFlow>(emptyList()) + private val inviteList = MutableStateFlow>(emptyList()) private val loadingState = MutableStateFlow(RoomSummaryDataSource.LoadingState.NotLoaded) - fun subscribeIfNeeded() { - sessionCoroutineScope.launch { - roomList.roomListEntriesUpdateFlow { roomListEntries -> - val summaries = roomListEntries.map(::buildSummaryForRoomListEntry) - updateRoomSummaries { - addAll(summaries) - } - }.onEach { - updateRoomSummaries { - applyUpdate(it) + fun init() { + sessionCoroutineScope.launch(coroutineDispatchers.computation) { + roomListService.roomListEntriesUpdateFlow { roomListEntries -> + roomList.value = roomListEntries.map(::buildSummaryForRoomListEntry) + }.onEach { update -> + roomList.getAndUpdate { + it.applyUpdate(update) } }.launchIn(this) } } - override fun roomSummaries(): StateFlow> { - return roomSummaries + override fun roomList(): StateFlow> { + return roomList + } + + override fun inviteList(): StateFlow> { + return inviteList } override fun loadingState(): StateFlow { return loadingState } - override fun setSlidingSyncRange(range: IntRange) { + override fun updateRoomListVisibleRange(range: IntRange) { Timber.v("setVisibleRange=$range") sessionCoroutineScope.launch { - val ranges = listOf(RoomListRange(range.first.toUInt(), range.last.toUInt())) - roomList.applyInput( - RoomListInput.Viewport(ranges) - ) + try { + val ranges = listOf(RoomListRange(range.first.toUInt(), range.last.toUInt())) + roomListService.applyInput( + RoomListInput.Viewport(ranges) + ) + } catch (exception: RoomListException) { + Timber.e(exception, "Failed updating visible range") + } } } - private fun MutableList.applyUpdate(update: RoomListEntriesUpdate) { - fun MutableList.fillUntil(untilIndex: Int) { - repeat((size - 1 until untilIndex).count()) { - add(buildEmptyRoomSummary()) - } - } + private fun List.applyUpdate(update: RoomListEntriesUpdate): List { + val newList = toMutableList() when (update) { is RoomListEntriesUpdate.Append -> { val roomSummaries = update.values.map { buildSummaryForRoomListEntry(it) } - addAll(roomSummaries) + newList.addAll(roomSummaries) } is RoomListEntriesUpdate.PushBack -> { val roomSummary = buildSummaryForRoomListEntry(update.value) - add(roomSummary) + newList.add(roomSummary) } is RoomListEntriesUpdate.PushFront -> { val roomSummary = buildSummaryForRoomListEntry(update.value) - add(0, roomSummary) + newList.add(0, roomSummary) } is RoomListEntriesUpdate.Set -> { - fillUntil(update.index.toInt()) val roomSummary = buildSummaryForRoomListEntry(update.value) - set(update.index.toInt(), roomSummary) + newList[update.index.toInt()] = roomSummary } is RoomListEntriesUpdate.Insert -> { val roomSummary = buildSummaryForRoomListEntry(update.value) - add(update.index.toInt(), roomSummary) + newList.add(update.index.toInt(), roomSummary) } is RoomListEntriesUpdate.Remove -> { - removeAt(update.index.toInt()) + newList.removeAt(update.index.toInt()) } is RoomListEntriesUpdate.Reset -> { - clear() - addAll(update.values.map { buildSummaryForRoomListEntry(it) }) + newList.clear() + newList.addAll(update.values.map { buildSummaryForRoomListEntry(it) }) } RoomListEntriesUpdate.PopBack -> { - removeFirstOrNull() + newList.removeFirstOrNull() } RoomListEntriesUpdate.PopFront -> { - removeLastOrNull() + newList.removeLastOrNull() } RoomListEntriesUpdate.Clear -> { - clear() + newList.clear() } } + return newList } private fun buildSummaryForRoomListEntry(entry: RoomListEntry): RoomSummary { @@ -139,7 +142,7 @@ internal class RustRoomSummaryDataSource( } private fun buildRoomSummaryForIdentifier(identifier: String): RoomSummary { - val roomListItem = roomList.roomOrNull(identifier) ?: return RoomSummary.Empty(identifier) + val roomListItem = roomListService.roomOrNull(identifier) ?: return RoomSummary.Empty(identifier) return roomListItem.use { roomListItem.fullRoom().use { fullRoom -> RoomSummary.Filled( @@ -148,11 +151,4 @@ internal class RustRoomSummaryDataSource( } } } - - private suspend fun updateRoomSummaries(block: MutableList.() -> Unit) = - withContext(coroutineDispatchers.diffUpdateDispatcher) { - 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/timeline/MatrixTimelineItemMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/MatrixTimelineItemMapper.kt index f7cf728691..f448873ab7 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/MatrixTimelineItemMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/MatrixTimelineItemMapper.kt @@ -26,7 +26,7 @@ import org.matrix.rustcomponents.sdk.TimelineItem class MatrixTimelineItemMapper( private val fetchDetailsForEvent: suspend (EventId) -> Result, - private val coroutineScope: CoroutineScope, + private val roomCoroutineScope: CoroutineScope, private val virtualTimelineItemMapper: VirtualTimelineItemMapper = VirtualTimelineItemMapper(), private val eventTimelineItemMapper: EventTimelineItemMapper = EventTimelineItemMapper(), ) { @@ -51,7 +51,7 @@ class MatrixTimelineItemMapper( return MatrixTimelineItem.Other } - private fun fetchEventDetails(eventId: EventId) = coroutineScope.launch { + private fun fetchEventDetails(eventId: EventId) = roomCoroutineScope.launch { fetchDetailsForEvent(eventId) } } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustMatrixTimeline.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustMatrixTimeline.kt index dcb518bf98..906ffa0967 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustMatrixTimeline.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustMatrixTimeline.kt @@ -39,7 +39,7 @@ import org.matrix.rustcomponents.sdk.TimelineItem import timber.log.Timber class RustMatrixTimeline( - coroutineScope: CoroutineScope, + roomCoroutineScope: CoroutineScope, private val matrixRoom: MatrixRoom, private val innerRoom: Room, private val coroutineDispatchers: CoroutineDispatchers, @@ -54,7 +54,7 @@ class RustMatrixTimeline( private val timelineItemFactory = MatrixTimelineItemMapper( fetchDetailsForEvent = this::fetchDetailsForEvent, - coroutineScope = coroutineScope, + roomCoroutineScope = roomCoroutineScope, virtualTimelineItemMapper = VirtualTimelineItemMapper(), eventTimelineItemMapper = EventTimelineItemMapper( contentMapper = TimelineEventContentMapper( @@ -66,7 +66,7 @@ class RustMatrixTimeline( private val timelineDiffProcessor = MatrixTimelineDiffProcessor( paginationState = paginationState, timelineItems = timelineItems, - coroutineScope = coroutineScope, + coroutineScope = roomCoroutineScope, diffDispatcher = coroutineDispatchers.diffUpdateDispatcher, timelineItemFactory = timelineItemFactory, ) 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 969ad4b413..85f860ea06 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,14 +29,14 @@ class FakeRoomSummaryDataSource : RoomSummaryDataSource { roomSummariesFlow.emit(roomSummaries) } - override fun roomSummaries(): StateFlow> { + override fun roomList(): StateFlow> { return roomSummariesFlow } var latestSlidingSyncRange: IntRange? = null private set - override fun setSlidingSyncRange(range: IntRange) { + override fun updateRoomListVisibleRange(range: IntRange) { latestSlidingSyncRange = range } } From e0e50a97e9cbd669861beeecab11a1521eb7ad3a Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 22 Jun 2023 15:47:15 +0200 Subject: [PATCH 06/21] API: simplify NetworkMonitor api --- .../android/features/messages/impl/MessagesPresenter.kt | 2 +- .../android/features/networkmonitor/api/NetworkMonitor.kt | 5 ++--- .../features/networkmonitor/impl/NetworkMonitorImpl.kt | 5 ++--- .../features/networkmonitor/test/FakeNetworkMonitor.kt | 7 +------ .../android/features/roomlist/impl/RoomListPresenter.kt | 2 +- 5 files changed, 7 insertions(+), 14 deletions(-) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt index 7daf99283d..a39dc77db3 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt @@ -96,7 +96,7 @@ class MessagesPresenter @Inject constructor( mutableStateOf(null) } - val networkConnectionStatus by networkMonitor.connectivity.collectAsState(initial = networkMonitor.currentConnectivityStatus) + val networkConnectionStatus by networkMonitor.connectivity.collectAsState() val snackbarMessage = handleSnackbarMessage(snackbarDispatcher) diff --git a/features/networkmonitor/api/src/main/kotlin/io/element/android/features/networkmonitor/api/NetworkMonitor.kt b/features/networkmonitor/api/src/main/kotlin/io/element/android/features/networkmonitor/api/NetworkMonitor.kt index e85a61512d..9e217adb41 100644 --- a/features/networkmonitor/api/src/main/kotlin/io/element/android/features/networkmonitor/api/NetworkMonitor.kt +++ b/features/networkmonitor/api/src/main/kotlin/io/element/android/features/networkmonitor/api/NetworkMonitor.kt @@ -16,9 +16,8 @@ package io.element.android.features.networkmonitor.api -import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.StateFlow interface NetworkMonitor { - val connectivity: Flow - val currentConnectivityStatus: NetworkStatus + val connectivity: StateFlow } diff --git a/features/networkmonitor/impl/src/main/kotlin/io/element/android/features/networkmonitor/impl/NetworkMonitorImpl.kt b/features/networkmonitor/impl/src/main/kotlin/io/element/android/features/networkmonitor/impl/NetworkMonitorImpl.kt index 25819c6eb3..8c8736311e 100644 --- a/features/networkmonitor/impl/src/main/kotlin/io/element/android/features/networkmonitor/impl/NetworkMonitorImpl.kt +++ b/features/networkmonitor/impl/src/main/kotlin/io/element/android/features/networkmonitor/impl/NetworkMonitorImpl.kt @@ -29,6 +29,7 @@ import io.element.android.libraries.di.ApplicationContext import io.element.android.libraries.di.SingleIn import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow import timber.log.Timber import javax.inject.Inject @@ -61,9 +62,7 @@ class NetworkMonitorImpl @Inject constructor( } private val _connectivity = MutableStateFlow(NetworkStatus.Online) - override val connectivity: Flow = _connectivity - - override val currentConnectivityStatus: NetworkStatus get() = _connectivity.value + override val connectivity: StateFlow = _connectivity init { listenToConnectionChanges() diff --git a/features/networkmonitor/test/src/main/kotlin/io/element/android/features/networkmonitor/test/FakeNetworkMonitor.kt b/features/networkmonitor/test/src/main/kotlin/io/element/android/features/networkmonitor/test/FakeNetworkMonitor.kt index d7ccab0b26..7db4acaa32 100644 --- a/features/networkmonitor/test/src/main/kotlin/io/element/android/features/networkmonitor/test/FakeNetworkMonitor.kt +++ b/features/networkmonitor/test/src/main/kotlin/io/element/android/features/networkmonitor/test/FakeNetworkMonitor.kt @@ -18,13 +18,8 @@ package io.element.android.features.networkmonitor.test import io.element.android.features.networkmonitor.api.NetworkMonitor import io.element.android.features.networkmonitor.api.NetworkStatus -import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow class FakeNetworkMonitor(initialStatus: NetworkStatus = NetworkStatus.Online) : NetworkMonitor { - override val currentConnectivityStatus: NetworkStatus - get() = _connectivityStatus.value - - private val _connectivityStatus: MutableStateFlow = MutableStateFlow(initialStatus) - override val connectivity: Flow = _connectivityStatus + override val connectivity = MutableStateFlow(initialStatus) } 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 30396da74d..b51a5dec3d 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 @@ -80,7 +80,7 @@ class RoomListPresenter @Inject constructor( .roomList() .collectAsState() - val networkConnectionStatus by networkMonitor.connectivity.collectAsState(initial = networkMonitor.currentConnectivityStatus) + val networkConnectionStatus by networkMonitor.connectivity.collectAsState() Timber.v("RoomSummaries size = ${roomSummaries.size}") From ca080fd6af312cade9b33b10e8176c9997f7b4ba Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 22 Jun 2023 16:47:32 +0200 Subject: [PATCH 07/21] RoomList: introduces a SyncService --- .../android/appnav/LoggedInFlowNode.kt | 5 +- .../libraries/matrix/api/MatrixClient.kt | 5 +- .../libraries/matrix/api/sync/SyncService.kt | 36 ++++++++++++ .../libraries/matrix/api/sync/SyncState.kt | 24 ++++++++ .../libraries/matrix/impl/RustMatrixClient.kt | 37 +++++------- .../matrix/impl/room/RoomListExtensions.kt | 3 +- .../matrix/impl/sync/RoomListStateMapper.kt | 30 ++++++++++ .../matrix/impl/sync/RustSyncService.kt | 58 +++++++++++++++++++ .../libraries/matrix/test/FakeMatrixClient.kt | 9 +-- .../matrix/test/sync/FakeSyncService.kt | 43 ++++++++++++++ 10 files changed, 214 insertions(+), 36 deletions(-) create mode 100644 libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/sync/SyncService.kt create mode 100644 libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/sync/SyncState.kt create mode 100644 libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/sync/RoomListStateMapper.kt create mode 100644 libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/sync/RustSyncService.kt create mode 100644 libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/sync/FakeSyncService.kt 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 97e2e8b7a2..83465bfcd4 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt @@ -127,6 +127,7 @@ class LoggedInFlowNode @AssistedInject constructor( ) : NodeInputs private val inputs: Inputs = inputs() + private val syncService = inputs.matrixClient.syncService() private val loggedInFlowProcessor = LoggedInEventProcessor( snackbarDispatcher, inputs.matrixClient.roomMembershipObserver(), @@ -147,10 +148,10 @@ class LoggedInFlowNode @AssistedInject constructor( loggedInFlowProcessor.observeEvents(coroutineScope) }, onResume = { - inputs.matrixClient.startSync() + syncService.startSync() }, onPause = { - inputs.matrixClient.stopSync() + syncService.stopSync() }, onDestroy = { val imageLoaderFactory = bindings().notLoggedInImageLoaderFactory() diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt index 485049585c..93f21c226e 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt @@ -27,6 +27,7 @@ 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.RoomMembershipObserver import io.element.android.libraries.matrix.api.room.RoomSummaryDataSource +import io.element.android.libraries.matrix.api.sync.SyncService 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.verification.SessionVerificationService @@ -44,8 +45,7 @@ interface MatrixClient : Closeable { suspend fun createDM(userId: UserId): Result suspend fun getProfile(userId: UserId): Result suspend fun searchUsers(searchTerm: String, limit: Long): Result - fun startSync() - fun stopSync() + fun syncService(): SyncService fun sessionVerificationService(): SessionVerificationService fun pushersService(): PushersService fun notificationService(): NotificationService @@ -53,6 +53,5 @@ interface MatrixClient : Closeable { suspend fun loadUserDisplayName(): Result suspend fun loadUserAvatarURLString(): Result suspend fun uploadMedia(mimeType: String, data: ByteArray, progressCallback: ProgressCallback?): Result - fun onSlidingSyncUpdate() fun roomMembershipObserver(): RoomMembershipObserver } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/sync/SyncService.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/sync/SyncService.kt new file mode 100644 index 0000000000..616f4fd859 --- /dev/null +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/sync/SyncService.kt @@ -0,0 +1,36 @@ +/* + * 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.api.sync + +import kotlinx.coroutines.flow.StateFlow + +interface SyncService { + /** + * Tries to start the sync. If already syncing it has no effect. + */ + fun startSync() + + /** + * Tries to stop the sync. If service is not syncing it has no effect. + */ + fun stopSync() + + /** + * + */ + fun syncState(): StateFlow +} diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/sync/SyncState.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/sync/SyncState.kt new file mode 100644 index 0000000000..596c131420 --- /dev/null +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/sync/SyncState.kt @@ -0,0 +1,24 @@ +/* + * 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.api.sync + +enum class SyncState { + Idle, + Syncing, + InError, + Terminated, +} 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 17b36f49a9..0c7f9c75de 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 @@ -31,6 +31,8 @@ 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.RoomMembershipObserver import io.element.android.libraries.matrix.api.room.RoomSummaryDataSource +import io.element.android.libraries.matrix.api.sync.SyncService +import io.element.android.libraries.matrix.api.sync.SyncState 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.verification.SessionVerificationService @@ -41,7 +43,7 @@ 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.RustRoomSummaryDataSource import io.element.android.libraries.matrix.impl.room.roomOrNull -import io.element.android.libraries.matrix.impl.room.stateFlow +import io.element.android.libraries.matrix.impl.sync.RustSyncService 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.verification.RustSessionVerificationService @@ -49,7 +51,6 @@ import io.element.android.libraries.sessionstorage.api.SessionStore import io.element.android.services.toolbox.api.systemclock.SystemClock import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.cancel import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.first @@ -78,8 +79,10 @@ class RustMatrixClient constructor( override val sessionId: UserId = UserId(client.userId()) + private val roomListService = client.roomList() private val sessionCoroutineScope = childScopeOf(appCoroutineScope, dispatchers.main, "Session-${sessionId}") private val verificationService = RustSessionVerificationService() + private val syncService = RustSyncService(roomListService, sessionCoroutineScope) private val pushersService = RustPushersService( client = client, dispatchers = dispatchers, @@ -93,8 +96,6 @@ class RustMatrixClient constructor( } } - private val roomListService = client.roomList() - private val rustRoomSummaryDataSource: RustRoomSummaryDataSource = RustRoomSummaryDataSource( roomListService = roomListService, @@ -113,12 +114,13 @@ class RustMatrixClient constructor( init { client.setDelegate(clientDelegate) + syncService.syncState() + .onEach { syncState -> + if (syncState == SyncState.Syncing) { + onSlidingSyncUpdate() + } + }.launchIn(sessionCoroutineScope) rustRoomSummaryDataSource.init() - roomListService.stateFlow() - .onEach { - Timber.v("onRoomList state change: $it") - } - .launchIn(sessionCoroutineScope) } override fun getRoom(roomId: RoomId): MatrixRoom? { @@ -208,26 +210,15 @@ class RustMatrixClient constructor( } } + override fun syncService(): SyncService = syncService + override fun sessionVerificationService(): SessionVerificationService = verificationService override fun pushersService(): PushersService = pushersService override fun notificationService(): NotificationService = notificationService - override fun startSync() { - if (!roomListService.isSyncing()) { - roomListService.sync() - } - } - - override fun stopSync() { - if (roomListService.isSyncing()) { - roomListService.stopSync() - } - } - override fun close() { - stopSync() sessionCoroutineScope.cancel() client.setDelegate(null) verificationService.destroy() @@ -265,7 +256,7 @@ class RustMatrixClient constructor( } } - override fun onSlidingSyncUpdate() { + private fun onSlidingSyncUpdate() { if (!verificationService.isReady.value) { try { verificationService.verificationController = client.getSessionVerificationController() diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomListExtensions.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomListExtensions.kt index c45d8967b4..2da52b9e29 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomListExtensions.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomListExtensions.kt @@ -3,7 +3,6 @@ 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 @@ -15,7 +14,7 @@ import org.matrix.rustcomponents.sdk.SlidingSyncListLoadingState import org.matrix.rustcomponents.sdk.SlidingSyncListStateObserver import timber.log.Timber -fun RoomListInterface.stateFlow(): Flow = +fun RoomListInterface.roomListStateFlow(): Flow = mxCallbackFlow { val listener = object : RoomListStateListener { override fun onUpdate(state: RoomListState) { diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/sync/RoomListStateMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/sync/RoomListStateMapper.kt new file mode 100644 index 0000000000..b59dfce529 --- /dev/null +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/sync/RoomListStateMapper.kt @@ -0,0 +1,30 @@ +/* + * 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.api.sync.SyncState +import org.matrix.rustcomponents.sdk.RoomListState + +internal fun RoomListState.toSyncState(): SyncState { + return when (this) { + RoomListState.INIT, + RoomListState.SETTING_UP -> SyncState.Idle + RoomListState.RUNNING -> SyncState.Syncing + RoomListState.ERROR -> SyncState.InError + RoomListState.TERMINATED -> SyncState.Terminated + } +} 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 new file mode 100644 index 0000000000..97e3f4c865 --- /dev/null +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/sync/RustSyncService.kt @@ -0,0 +1,58 @@ +/* + * 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.api.sync.SyncService +import io.element.android.libraries.matrix.api.sync.SyncState +import io.element.android.libraries.matrix.impl.room.roomListStateFlow +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.stateIn +import org.matrix.rustcomponents.sdk.RoomList +import org.matrix.rustcomponents.sdk.RoomListState +import timber.log.Timber + +class RustSyncService( + private val roomListService: RoomList, + private val sessionCoroutineScope: CoroutineScope +) : SyncService { + + override fun startSync() { + if (!roomListService.isSyncing()) { + roomListService.sync() + } + } + + override fun stopSync() { + if (roomListService.isSyncing()) { + roomListService.stopSync() + } + } + + override fun syncState(): StateFlow { + return roomListService + .roomListStateFlow() + .map(RoomListState::toSyncState) + .onEach { syncState -> + Timber.d("OnSyncState updated = $syncState") + } + .stateIn(sessionCoroutineScope, SharingStarted.Eagerly, SyncState.Idle) + } +} diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt index 8b38a74457..58bc6c1ff1 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt @@ -36,6 +36,7 @@ import io.element.android.libraries.matrix.test.notification.FakeNotificationSer import io.element.android.libraries.matrix.test.pushers.FakePushersService import io.element.android.libraries.matrix.test.room.FakeMatrixRoom import io.element.android.libraries.matrix.test.room.FakeRoomSummaryDataSource +import io.element.android.libraries.matrix.test.sync.FakeSyncService import io.element.android.libraries.matrix.test.verification.FakeSessionVerificationService import kotlinx.coroutines.delay @@ -44,11 +45,11 @@ class FakeMatrixClient( private val userDisplayName: Result = Result.success(A_USER_NAME), private val userAvatarURLString: Result = Result.success(AN_AVATAR_URL), override val roomSummaryDataSource: RoomSummaryDataSource = FakeRoomSummaryDataSource(), - override val invitesDataSource: RoomSummaryDataSource = FakeRoomSummaryDataSource(), override val mediaLoader: MatrixMediaLoader = FakeMediaLoader(), private val sessionVerificationService: FakeSessionVerificationService = FakeSessionVerificationService(), private val pushersService: FakePushersService = FakePushersService(), private val notificationService: FakeNotificationService = FakeNotificationService(), + private val syncService: FakeSyncService = FakeSyncService(), ) : MatrixClient { private var ignoreUserResult: Result = Result.success(Unit) @@ -98,9 +99,7 @@ class FakeMatrixClient( return searchUserResults[searchTerm] ?: Result.failure(IllegalStateException("No response defined for $searchTerm")) } - override fun startSync() = Unit - - override fun stopSync() = Unit + override fun syncService() = syncService override suspend fun logout() { delay(100) @@ -131,8 +130,6 @@ class FakeMatrixClient( override fun notificationService(): NotificationService = notificationService - override fun onSlidingSyncUpdate() {} - override fun roomMembershipObserver(): RoomMembershipObserver { return RoomMembershipObserver() } diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/sync/FakeSyncService.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/sync/FakeSyncService.kt new file mode 100644 index 0000000000..04837efc90 --- /dev/null +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/sync/FakeSyncService.kt @@ -0,0 +1,43 @@ +/* + * 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.test.sync + +import io.element.android.libraries.matrix.api.sync.SyncService +import io.element.android.libraries.matrix.api.sync.SyncState +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow + +class FakeSyncService : SyncService { + + private val syncState = MutableStateFlow(SyncState.Idle) + + fun simulateError() { + syncState.value = SyncState.InError + } + + override fun startSync() { + syncState.value = SyncState.Syncing + } + + override fun stopSync() { + syncState.value = SyncState.Terminated + } + + override fun syncState(): StateFlow { + return syncState + } +} From 7846fa19b46069eb56c0574d00dbb763a6d780ad Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 23 Jun 2023 16:48:31 +0200 Subject: [PATCH 08/21] Network monitor : try to make it more reliable... --- .../networkmonitor/impl/NetworkMonitorImpl.kt | 85 ++++++++++--------- 1 file changed, 47 insertions(+), 38 deletions(-) diff --git a/features/networkmonitor/impl/src/main/kotlin/io/element/android/features/networkmonitor/impl/NetworkMonitorImpl.kt b/features/networkmonitor/impl/src/main/kotlin/io/element/android/features/networkmonitor/impl/NetworkMonitorImpl.kt index 8c8736311e..69b53f9083 100644 --- a/features/networkmonitor/impl/src/main/kotlin/io/element/android/features/networkmonitor/impl/NetworkMonitorImpl.kt +++ b/features/networkmonitor/impl/src/main/kotlin/io/element/android/features/networkmonitor/impl/NetworkMonitorImpl.kt @@ -14,6 +14,8 @@ * limitations under the License. */ +@file:OptIn(FlowPreview::class) + package io.element.android.features.networkmonitor.impl import android.content.Context @@ -27,62 +29,69 @@ import io.element.android.features.networkmonitor.api.NetworkStatus import io.element.android.libraries.di.AppScope import io.element.android.libraries.di.ApplicationContext import io.element.android.libraries.di.SingleIn -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.FlowPreview +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.channels.trySendBlocking +import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.flow.debounce +import kotlinx.coroutines.flow.stateIn import timber.log.Timber import javax.inject.Inject @ContributesBinding(scope = AppScope::class) @SingleIn(AppScope::class) class NetworkMonitorImpl @Inject constructor( - @ApplicationContext context: Context + @ApplicationContext context: Context, + appCoroutineScope: CoroutineScope, ) : NetworkMonitor { private val connectivityManager: ConnectivityManager = context.getSystemService(ConnectivityManager::class.java) - private val callback = object : ConnectivityManager.NetworkCallback() { - override fun onAvailable(network: Network) { - _connectivity.value = connectivityManager.currentConnectionStatus() - Timber.v("Connectivity status (available): ${connectivityManager.currentConnectionStatus()}") + override val connectivity: StateFlow = callbackFlow { + + /** + * Assume there is no network available as soon as onLost is called. + * Reading activeNetwork synchronously from the callback is not safe. + * If there is an other active network we'll get some callback in onCapabilitiesChanged. + * Debounce the result to avoid quick offline<->online changes. + */ + val callback = object : ConnectivityManager.NetworkCallback() { + override fun onLost(network: Network) { + trySendBlocking(NetworkStatus.Offline) + } + + override fun onCapabilitiesChanged( + network: Network, + networkCapabilities: NetworkCapabilities + ) { + trySendBlocking(networkCapabilities.getNetworkStatus()) + } } - - override fun onLost(network: Network) { - _connectivity.value = connectivityManager.currentConnectionStatus() - Timber.v("Connectivity status (lost): ${connectivityManager.currentConnectionStatus()}") - } - - override fun onCapabilitiesChanged( - network: Network, - networkCapabilities: NetworkCapabilities - ) { - _connectivity.value = connectivityManager.currentConnectionStatus() - Timber.v("Connectivity status (changed): ${connectivityManager.currentConnectionStatus()}") - } - } - - private val _connectivity = MutableStateFlow(NetworkStatus.Online) - override val connectivity: StateFlow = _connectivity - - init { - listenToConnectionChanges() - } - - private fun listenToConnectionChanges() { + trySendBlocking(connectivityManager.activeNetworkStatus()) val request = NetworkRequest.Builder() -// .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) -// .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) - .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) + .addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) .build() connectivityManager.registerNetworkCallback(request, callback) + Timber.d("Subscribe") + awaitClose { + Timber.d("Unsubscribe") + connectivityManager.unregisterNetworkCallback(callback) + } + } + .debounce(300) + .stateIn(appCoroutineScope, SharingStarted.WhileSubscribed(), connectivityManager.activeNetworkStatus()) - _connectivity.tryEmit(connectivityManager.currentConnectionStatus()) + private fun ConnectivityManager.activeNetworkStatus(): NetworkStatus { + return activeNetwork?.let { + getNetworkCapabilities(it)?.getNetworkStatus() + } ?: NetworkStatus.Offline } - private fun ConnectivityManager.currentConnectionStatus(): NetworkStatus { - val hasInternet = activeNetwork?.let(::getNetworkCapabilities) - ?.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) - ?: false + private fun NetworkCapabilities.getNetworkStatus(): NetworkStatus { + val hasInternet = hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) return if (hasInternet) { NetworkStatus.Online } else { From f3e072bdb086892489119216245a92ab6692c4ac Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 23 Jun 2023 17:07:27 +0200 Subject: [PATCH 09/21] RoomList: try syncing when network is back and inError state --- .../android/appnav/LoggedInFlowNode.kt | 30 +++++++++++++++++++ .../networkmonitor/impl/NetworkMonitorImpl.kt | 2 ++ .../libraries/matrix/api/sync/SyncService.kt | 4 +-- .../libraries/matrix/impl/RustMatrixClient.kt | 2 +- .../matrix/impl/sync/RustSyncService.kt | 12 ++++---- .../matrix/test/sync/FakeSyncService.kt | 12 ++++---- 6 files changed, 45 insertions(+), 17 deletions(-) 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 83465bfcd4..9d6db782ef 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt @@ -22,7 +22,9 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle import coil.Coil import com.bumble.appyx.core.composable.Children import com.bumble.appyx.core.lifecycle.subscribe @@ -43,6 +45,8 @@ import io.element.android.appnav.loggedin.LoggedInNode import io.element.android.features.analytics.api.AnalyticsEntryPoint import io.element.android.features.createroom.api.CreateRoomEntryPoint import io.element.android.features.invitelist.api.InviteListEntryPoint +import io.element.android.features.networkmonitor.api.NetworkMonitor +import io.element.android.features.networkmonitor.api.NetworkStatus import io.element.android.features.preferences.api.PreferencesEntryPoint import io.element.android.features.roomlist.api.RoomListEntryPoint import io.element.android.features.verifysession.api.VerifySessionEntryPoint @@ -59,13 +63,17 @@ import io.element.android.libraries.di.AppScope import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.core.MAIN_SPACE import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.api.sync.SyncState import io.element.android.libraries.matrix.ui.di.MatrixUIBindings import io.element.android.services.analytics.api.AnalyticsService import io.element.android.services.appnavstate.api.AppNavigationStateService import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch import kotlinx.parcelize.Parcelize @ContributesNode(AppScope::class) @@ -81,6 +89,7 @@ class LoggedInFlowNode @AssistedInject constructor( private val inviteListEntryPoint: InviteListEntryPoint, private val analyticsService: AnalyticsService, private val coroutineScope: CoroutineScope, + private val networkMonitor: NetworkMonitor, snackbarDispatcher: SnackbarDispatcher, ) : BackstackNode( backstack = BackStack( @@ -162,6 +171,27 @@ class LoggedInFlowNode @AssistedInject constructor( loggedInFlowProcessor.stopObserving() } ) + + observeSyncStateAndNetworkStatus() + } + + private fun observeSyncStateAndNetworkStatus() { + lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.RESUMED) { + combine( + syncService.syncState.debounce(100), + networkMonitor.connectivity + ) { syncState, networkStatus -> + syncState == SyncState.InError && networkStatus == NetworkStatus.Online + } + .distinctUntilChanged() + .collect { restartSync -> + if (restartSync) { + syncService.startSync() + } + } + } + } } sealed interface NavTarget : Parcelable { diff --git a/features/networkmonitor/impl/src/main/kotlin/io/element/android/features/networkmonitor/impl/NetworkMonitorImpl.kt b/features/networkmonitor/impl/src/main/kotlin/io/element/android/features/networkmonitor/impl/NetworkMonitorImpl.kt index 69b53f9083..3901ec4120 100644 --- a/features/networkmonitor/impl/src/main/kotlin/io/element/android/features/networkmonitor/impl/NetworkMonitorImpl.kt +++ b/features/networkmonitor/impl/src/main/kotlin/io/element/android/features/networkmonitor/impl/NetworkMonitorImpl.kt @@ -37,6 +37,7 @@ import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.debounce +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.stateIn import timber.log.Timber import javax.inject.Inject @@ -81,6 +82,7 @@ class NetworkMonitorImpl @Inject constructor( connectivityManager.unregisterNetworkCallback(callback) } } + .distinctUntilChanged() .debounce(300) .stateIn(appCoroutineScope, SharingStarted.WhileSubscribed(), connectivityManager.activeNetworkStatus()) diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/sync/SyncService.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/sync/SyncService.kt index 616f4fd859..b47c463dd8 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/sync/SyncService.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/sync/SyncService.kt @@ -30,7 +30,7 @@ interface SyncService { fun stopSync() /** - * + * Flow of [SyncState]. Will be updated as soon as the current [SyncState] changes. */ - fun syncState(): StateFlow + val syncState: StateFlow } 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 0c7f9c75de..7122617284 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 @@ -114,7 +114,7 @@ class RustMatrixClient constructor( init { client.setDelegate(clientDelegate) - syncService.syncState() + syncService.syncState .onEach { syncState -> if (syncState == SyncState.Syncing) { onSlidingSyncUpdate() 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 97e3f4c865..26b5789d70 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 @@ -22,6 +22,7 @@ import io.element.android.libraries.matrix.impl.room.roomListStateFlow import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.stateIn @@ -46,13 +47,10 @@ class RustSyncService( } } - override fun syncState(): StateFlow { - return roomListService + override val syncState: StateFlow = + roomListService .roomListStateFlow() .map(RoomListState::toSyncState) - .onEach { syncState -> - Timber.d("OnSyncState updated = $syncState") - } - .stateIn(sessionCoroutineScope, SharingStarted.Eagerly, SyncState.Idle) - } + .distinctUntilChanged() + .stateIn(sessionCoroutineScope, SharingStarted.WhileSubscribed(), SyncState.Idle) } diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/sync/FakeSyncService.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/sync/FakeSyncService.kt index 04837efc90..cd452ce591 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/sync/FakeSyncService.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/sync/FakeSyncService.kt @@ -23,21 +23,19 @@ import kotlinx.coroutines.flow.StateFlow class FakeSyncService : SyncService { - private val syncState = MutableStateFlow(SyncState.Idle) + private val syncStateFlow = MutableStateFlow(SyncState.Idle) fun simulateError() { - syncState.value = SyncState.InError + syncStateFlow.value = SyncState.InError } override fun startSync() { - syncState.value = SyncState.Syncing + syncStateFlow.value = SyncState.Syncing } override fun stopSync() { - syncState.value = SyncState.Terminated + syncStateFlow.value = SyncState.Terminated } - override fun syncState(): StateFlow { - return syncState - } + override val syncState: StateFlow = syncStateFlow } From fa48c294860178a87940eca6600769e270dbb921 Mon Sep 17 00:00:00 2001 From: ganfra Date: Mon, 26 Jun 2023 18:02:53 +0200 Subject: [PATCH 10/21] RoomList: more rework on RoomSummaryDataSource --- .../android/appnav/LoggedInFlowNode.kt | 2 +- .../invitelist/impl/InviteListPresenter.kt | 2 +- .../impl/forward/ForwardMessagesPresenter.kt | 3 +- .../impl/DefaultInviteStateDataSource.kt | 2 +- .../roomlist/impl/RoomListPresenter.kt | 2 +- .../matrix/api/room/RoomSummaryDataSource.kt | 4 +- .../libraries/matrix/impl/RustMatrixClient.kt | 3 +- .../impl/room/RoomSummaryListProcessor.kt | 128 ++++++++++++++++++ .../impl/room/RustRoomSummaryDataSource.kt | 100 ++------------ .../matrix/impl/sync/RustSyncService.kt | 2 +- .../test/room/FakeRoomSummaryDataSource.kt | 2 +- 11 files changed, 152 insertions(+), 98 deletions(-) create mode 100644 libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomSummaryListProcessor.kt 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 } From 8c66924be9686e186162e8c3c7e51bfc5a461e19 Mon Sep 17 00:00:00 2001 From: ganfra Date: Mon, 26 Jun 2023 18:18:48 +0200 Subject: [PATCH 11/21] Timeline: refactor a bit --- .../matrix/impl/room/RustMatrixRoom.kt | 17 ++++---- .../timeline/MatrixTimelineDiffProcessor.kt | 41 +++++++++++-------- .../impl/timeline/RustMatrixTimeline.kt | 8 ++-- 3 files changed, 35 insertions(+), 31 deletions(-) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt index 66c30be0da..39d829c6a8 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt @@ -108,13 +108,13 @@ class RustMatrixRoom( timelineLimit = null ) roomListItem.subscribe(settings) - innerRoom.timelineDiffFlow { initialList -> - timeline.postItems(initialList) - }.onEach { - syncUpdateFlow.value = systemClock.epochMillis() - timeline.postDiff(it) - }.launchIn(roomCoroutineScope) - roomCoroutineScope.launch { + roomCoroutineScope.launch(coroutineDispatchers.computation) { + innerRoom.timelineDiffFlow { initialList -> + timeline.postItems(initialList) + }.onEach { + syncUpdateFlow.value = systemClock.epochMillis() + timeline.postDiff(it) + }.launchIn(this) fetchMembers() } isInit.value = true @@ -122,7 +122,7 @@ class RustMatrixRoom( } override fun close() { - if(isInit.value) { + if (isInit.value) { isInit.value = false roomCoroutineScope.cancel() roomListItem.unsubscribe() @@ -360,7 +360,6 @@ class RustMatrixRoom( } } - private suspend fun fetchMembers() = withContext(coroutineDispatchers.io) { runCatching { innerRoom.fetchMembers() diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/MatrixTimelineDiffProcessor.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/MatrixTimelineDiffProcessor.kt index bd30d826f2..02b34757da 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/MatrixTimelineDiffProcessor.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/MatrixTimelineDiffProcessor.kt @@ -19,11 +19,9 @@ package io.element.android.libraries.matrix.impl.timeline import io.element.android.libraries.matrix.api.timeline.MatrixTimeline import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem import io.element.android.libraries.matrix.api.timeline.item.virtual.VirtualTimelineItem -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock import org.matrix.rustcomponents.sdk.TimelineChange import org.matrix.rustcomponents.sdk.TimelineDiff import org.matrix.rustcomponents.sdk.TimelineItem @@ -31,20 +29,23 @@ import org.matrix.rustcomponents.sdk.TimelineItem internal class MatrixTimelineDiffProcessor( private val paginationState: MutableStateFlow, private val timelineItems: MutableStateFlow>, - private val coroutineScope: CoroutineScope, - private val diffDispatcher: CoroutineDispatcher, private val timelineItemFactory: MatrixTimelineItemMapper, ) { - fun postDiff(diff: TimelineDiff) { - coroutineScope.launch { - updateTimelineItems { - applyDiff(diff) - } - when (val firstItem = timelineItems.value.firstOrNull()) { - is MatrixTimelineItem.Virtual -> updateBackPaginationState(firstItem.virtual) - else -> updateBackPaginationState(null) - } + private val mutex = Mutex() + + suspend fun postItems(items: List) { + updateTimelineItems { + val mappedItems = items.map { it.asMatrixTimelineItem() } + addAll(mappedItems) + updateBackPaginationState() + } + } + + suspend fun postDiff(diff: TimelineDiff) { + updateTimelineItems { + applyDiff(diff) + updateBackPaginationState() } } @@ -68,7 +69,7 @@ internal class MatrixTimelineDiffProcessor( } private suspend fun updateTimelineItems(block: MutableList.() -> Unit) = - withContext(diffDispatcher) { + mutex.withLock { val mutableTimelineItems = timelineItems.value.toMutableList() block(mutableTimelineItems) timelineItems.value = mutableTimelineItems @@ -119,8 +120,14 @@ internal class MatrixTimelineDiffProcessor( } } + private fun List.updateBackPaginationState() { + when (val firstItem = firstOrNull()) { + is MatrixTimelineItem.Virtual -> updateBackPaginationState(firstItem.virtual) + else -> updateBackPaginationState(null) + } + } + private fun TimelineItem.asMatrixTimelineItem(): MatrixTimelineItem { return timelineItemFactory.map(this) } - } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustMatrixTimeline.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustMatrixTimeline.kt index 906ffa0967..1893c452bb 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustMatrixTimeline.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustMatrixTimeline.kt @@ -66,8 +66,6 @@ class RustMatrixTimeline( private val timelineDiffProcessor = MatrixTimelineDiffProcessor( paginationState = paginationState, timelineItems = timelineItems, - coroutineScope = roomCoroutineScope, - diffDispatcher = coroutineDispatchers.diffUpdateDispatcher, timelineItemFactory = timelineItemFactory, ) @@ -80,11 +78,11 @@ class RustMatrixTimeline( return timelineItems.sample(50) } - internal fun postItems(items: List) { - timelineItems.value = items.map(timelineItemFactory::map) + internal suspend fun postItems(items: List) { + timelineDiffProcessor.postItems(items) } - internal fun postDiff(timelineDiff: TimelineDiff) { + internal suspend fun postDiff(timelineDiff: TimelineDiff) { timelineDiffProcessor.postDiff(timelineDiff) } From 19e2c104af7c22cede27db604a969031439c2407 Mon Sep 17 00:00:00 2001 From: ganfra Date: Mon, 26 Jun 2023 22:30:12 +0200 Subject: [PATCH 12/21] RoomList: update LoadingState and fix a crash --- .../matrix/api/room/RoomSummaryDataSource.kt | 10 ++--- .../libraries/matrix/api/sync/SyncService.kt | 4 +- .../impl/room/RustRoomSummaryDataSource.kt | 41 +++++++++++++++---- .../matrix/impl/sync/RustSyncService.kt | 4 +- .../matrix/test/sync/FakeSyncService.kt | 6 ++- 5 files changed, 45 insertions(+), 20 deletions(-) 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 d1fa6fc454..e0aaecd6a9 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 @@ -20,14 +20,12 @@ import kotlinx.coroutines.flow.StateFlow interface RoomSummaryDataSource { - enum class LoadingState { - NotLoaded, - PreLoaded, - PartiallyLoaded, - FullyLoaded, + sealed class LoadingState { + object NotLoaded : LoadingState() + data class Loaded(val numberOfRooms: Int): LoadingState() } - fun loadingState(): StateFlow + fun allRoomsLoadingState(): StateFlow fun allRooms(): StateFlow> fun inviteRooms(): StateFlow> fun updateRoomListVisibleRange(range: IntRange) diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/sync/SyncService.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/sync/SyncService.kt index b47c463dd8..9ed74c48e6 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/sync/SyncService.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/sync/SyncService.kt @@ -22,12 +22,12 @@ interface SyncService { /** * Tries to start the sync. If already syncing it has no effect. */ - fun startSync() + fun startSync(): Result /** * Tries to stop the sync. If service is not syncing it has no effect. */ - fun stopSync() + fun stopSync(): Result /** * Flow of [SyncState]. Will be updated as soon as the current [SyncState] changes. 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 e7fd3025bd..ed1a060086 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 @@ -20,13 +20,18 @@ 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.RoomSummaryDataSource import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch +import org.matrix.rustcomponents.sdk.RoomList +import org.matrix.rustcomponents.sdk.RoomListEntriesUpdate import org.matrix.rustcomponents.sdk.RoomListException import org.matrix.rustcomponents.sdk.RoomListInput +import org.matrix.rustcomponents.sdk.RoomListLoadingState import org.matrix.rustcomponents.sdk.RoomListRange import org.matrix.rustcomponents.sdk.RoomListService import timber.log.Timber @@ -41,16 +46,20 @@ internal class RustRoomSummaryDataSource( private val allRooms = MutableStateFlow>(emptyList()) private val inviteRooms = MutableStateFlow>(emptyList()) - private val loadingState = MutableStateFlow(RoomSummaryDataSource.LoadingState.NotLoaded) + private val allRoomsLoadingState: MutableStateFlow = MutableStateFlow(RoomSummaryDataSource.LoadingState.NotLoaded) private val allRoomsListProcessor = RoomSummaryListProcessor(allRooms, roomListService, roomSummaryDetailsFactory) init { sessionCoroutineScope.launch(coroutineDispatchers.computation) { - roomListService.allRooms().entriesFlow { roomListEntries -> - allRoomsListProcessor.postEntries(roomListEntries) - }.onEach { update -> - allRoomsListProcessor.postUpdate(update) - }.launchIn(this) + val allRooms = roomListService.allRooms() + allRooms.observeEntriesWithProcessor(allRoomsListProcessor) + .launchIn(this) + + allRooms.loadingStateFlow() + .map { it.toRoomSummaryDataSourceLoadingState() } + .onEach { + allRoomsLoadingState.value = it + }.launchIn(this) } } @@ -62,8 +71,8 @@ internal class RustRoomSummaryDataSource( return inviteRooms } - override fun loadingState(): StateFlow { - return loadingState + override fun allRoomsLoadingState(): StateFlow { + return allRoomsLoadingState } override fun updateRoomListVisibleRange(range: IntRange) { @@ -80,3 +89,19 @@ internal class RustRoomSummaryDataSource( } } } + +private fun RoomListLoadingState.toRoomSummaryDataSourceLoadingState(): RoomSummaryDataSource.LoadingState { + return when (this) { + is RoomListLoadingState.Loaded -> RoomSummaryDataSource.LoadingState.Loaded(maximumNumberOfRooms?.toInt() ?: 0) + is RoomListLoadingState.NotLoaded -> RoomSummaryDataSource.LoadingState.NotLoaded + } +} + +fun RoomList.observeEntriesWithProcessor(processor: RoomSummaryListProcessor): Flow { + return entriesFlow { roomListEntries -> + processor.postEntries(roomListEntries) + }.onEach { update -> + processor.postUpdate(update) + } +} + 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 12af4a262e..2103833704 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 @@ -33,13 +33,13 @@ class RustSyncService( sessionCoroutineScope: CoroutineScope ) : SyncService { - override fun startSync() { + override fun startSync() = runCatching { if (!roomListService.isSyncing()) { roomListService.sync() } } - override fun stopSync() { + override fun stopSync() = runCatching { if (roomListService.isSyncing()) { roomListService.stopSync() } diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/sync/FakeSyncService.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/sync/FakeSyncService.kt index cd452ce591..a0e57997d3 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/sync/FakeSyncService.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/sync/FakeSyncService.kt @@ -29,12 +29,14 @@ class FakeSyncService : SyncService { syncStateFlow.value = SyncState.InError } - override fun startSync() { + override fun startSync(): Result { syncStateFlow.value = SyncState.Syncing + return Result.success(Unit) } - override fun stopSync() { + override fun stopSync(): Result { syncStateFlow.value = SyncState.Terminated + return Result.success(Unit) } override val syncState: StateFlow = syncStateFlow From 7c76a74a301a43f6b9da762df6ff1a580592a564 Mon Sep 17 00:00:00 2001 From: ganfra Date: Mon, 26 Jun 2023 22:32:59 +0200 Subject: [PATCH 13/21] RoomList/Timeline : Add Buffer(Unlimited) to avoid blocking rust listener thread --- .../libraries/matrix/impl/room/RoomListExtensions.kt | 8 +++++--- .../libraries/matrix/impl/timeline/TimelineDiffFlow.kt | 4 +++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomListExtensions.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomListExtensions.kt index 176538c08a..2697fa87fb 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomListExtensions.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomListExtensions.kt @@ -1,8 +1,10 @@ package io.element.android.libraries.matrix.impl.room import io.element.android.libraries.matrix.impl.util.mxCallbackFlow +import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.trySendBlocking import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.buffer import org.matrix.rustcomponents.sdk.RoomList import org.matrix.rustcomponents.sdk.RoomListEntriesListener import org.matrix.rustcomponents.sdk.RoomListEntriesUpdate @@ -25,7 +27,7 @@ fun RoomList.loadingStateFlow(): Flow = val result = loadingState(listener) send(result.state) result.stateStream - } + }.buffer(Channel.UNLIMITED) fun RoomList.entriesFlow(onInitialList: suspend (List) -> Unit): Flow = mxCallbackFlow { @@ -37,7 +39,7 @@ fun RoomList.entriesFlow(onInitialList: suspend (List) -> Unit): val result = entries(listener) onInitialList(result.entries) result.entriesStream - } + }.buffer(Channel.UNLIMITED) fun RoomListService.roomOrNull(roomId: String): RoomListItem? { return try { @@ -56,4 +58,4 @@ fun RoomListService.stateFlow(): Flow = } } state(listener) - } + }.buffer(Channel.UNLIMITED) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/TimelineDiffFlow.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/TimelineDiffFlow.kt index 92ddac8f0a..ac75b9d074 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/TimelineDiffFlow.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/TimelineDiffFlow.kt @@ -17,8 +17,10 @@ package io.element.android.libraries.matrix.impl.timeline import io.element.android.libraries.matrix.impl.util.mxCallbackFlow +import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.trySendBlocking import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.buffer import org.matrix.rustcomponents.sdk.Room import org.matrix.rustcomponents.sdk.TimelineDiff import org.matrix.rustcomponents.sdk.TimelineItem @@ -34,4 +36,4 @@ internal fun Room.timelineDiffFlow(onInitialList: suspend (List) - val result = addTimelineListener(listener) onInitialList(result.items) result.itemsStream - } + }.buffer(Channel.UNLIMITED) From eb00ef3c06312599abe0835e3e4b5c7cce9cf4c0 Mon Sep 17 00:00:00 2001 From: ganfra Date: Mon, 26 Jun 2023 22:58:54 +0200 Subject: [PATCH 14/21] RoomList: fix small error in update processing --- .../libraries/matrix/impl/room/RoomSummaryListProcessor.kt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) 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 index 8e4f608286..5ddb53421c 100644 --- 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 @@ -77,15 +77,14 @@ class RoomSummaryListProcessor( removeAt(update.index.toInt()) } is RoomListEntriesUpdate.Reset -> { - Timber.v("Reset size: ${update.values.size}") clear() addAll(update.values.map { buildSummaryForRoomListEntry(it) }) } RoomListEntriesUpdate.PopBack -> { - removeFirstOrNull() + removeLastOrNull() } RoomListEntriesUpdate.PopFront -> { - removeLastOrNull() + removeFirstOrNull() } RoomListEntriesUpdate.Clear -> { clear() From 8f5fb64ba547eaf844921d23fcc9280e075a67dc Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 27 Jun 2023 10:47:14 +0200 Subject: [PATCH 15/21] RoomList/Timeline: makes sure initial values are set before computing diff/update --- .../libraries/matrix/impl/room/RoomSummaryListProcessor.kt | 5 +++++ .../matrix/impl/timeline/MatrixTimelineDiffProcessor.kt | 5 +++++ 2 files changed, 10 insertions(+) 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 index 5ddb53421c..1c166960f6 100644 --- 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 @@ -17,6 +17,7 @@ package io.element.android.libraries.matrix.impl.room import io.element.android.libraries.matrix.api.room.RoomSummary +import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock @@ -33,6 +34,7 @@ class RoomSummaryListProcessor( ) { private val roomSummariesByIdentifier = HashMap() + private val initLatch = CompletableDeferred() private val mutex = Mutex() suspend fun postEntries(entries: List) { @@ -40,9 +42,12 @@ class RoomSummaryListProcessor( Timber.v("Update rooms from postEntries (with ${entries.size} items) on ${Thread.currentThread()}") addAll(entries.map(::buildSummaryForRoomListEntry)) } + initLatch.complete(Unit) } suspend fun postUpdate(update: RoomListEntriesUpdate) { + // Makes sure to process first entries before update. + initLatch.await() updateRoomSummaries { Timber.v("Update rooms from postUpdate ($update) on ${Thread.currentThread()}") applyUpdate(update) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/MatrixTimelineDiffProcessor.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/MatrixTimelineDiffProcessor.kt index 02b34757da..f85db5a347 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/MatrixTimelineDiffProcessor.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/MatrixTimelineDiffProcessor.kt @@ -19,6 +19,7 @@ package io.element.android.libraries.matrix.impl.timeline import io.element.android.libraries.matrix.api.timeline.MatrixTimeline import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem import io.element.android.libraries.matrix.api.timeline.item.virtual.VirtualTimelineItem +import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock @@ -32,6 +33,7 @@ internal class MatrixTimelineDiffProcessor( private val timelineItemFactory: MatrixTimelineItemMapper, ) { + private val initLatch = CompletableDeferred() private val mutex = Mutex() suspend fun postItems(items: List) { @@ -40,9 +42,12 @@ internal class MatrixTimelineDiffProcessor( addAll(mappedItems) updateBackPaginationState() } + initLatch.complete(Unit) } suspend fun postDiff(diff: TimelineDiff) { + // Makes sure to process first items before diff. + initLatch.await() updateTimelineItems { applyDiff(diff) updateBackPaginationState() From d9559af2cf88e86c46372ef6310cfeb7cd004ce8 Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 27 Jun 2023 12:52:06 +0200 Subject: [PATCH 16/21] RoomList: branch invites --- .../impl/room/RustRoomSummaryDataSource.kt | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) 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 ed1a060086..45e399e2d5 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 @@ -23,6 +23,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach @@ -34,6 +35,7 @@ import org.matrix.rustcomponents.sdk.RoomListInput import org.matrix.rustcomponents.sdk.RoomListLoadingState import org.matrix.rustcomponents.sdk.RoomListRange import org.matrix.rustcomponents.sdk.RoomListService +import org.matrix.rustcomponents.sdk.RoomListServiceState import timber.log.Timber internal class RustRoomSummaryDataSource( @@ -48,18 +50,31 @@ internal class RustRoomSummaryDataSource( private val allRoomsLoadingState: MutableStateFlow = MutableStateFlow(RoomSummaryDataSource.LoadingState.NotLoaded) private val allRoomsListProcessor = RoomSummaryListProcessor(allRooms, roomListService, roomSummaryDetailsFactory) + private val inviteRoomsListProcessor = RoomSummaryListProcessor(inviteRooms, roomListService, roomSummaryDetailsFactory) init { sessionCoroutineScope.launch(coroutineDispatchers.computation) { val allRooms = roomListService.allRooms() - allRooms.observeEntriesWithProcessor(allRoomsListProcessor) + allRooms + .observeEntriesWithProcessor(allRoomsListProcessor) .launchIn(this) - allRooms.loadingStateFlow() + allRooms + .loadingStateFlow() .map { it.toRoomSummaryDataSourceLoadingState() } .onEach { allRoomsLoadingState.value = it }.launchIn(this) + + launch { + // Wait until running, as invites is only available after that + roomListService.stateFlow().first { + it == RoomListServiceState.RUNNING + } + roomListService.invites() + .observeEntriesWithProcessor(inviteRoomsListProcessor) + .launchIn(this) + } } } From 812880a148682a4abf75991bbcab5bb0d6e9bd1f Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 27 Jun 2023 14:06:58 +0200 Subject: [PATCH 17/21] RoomList: fix tests and sample --- .../impl/InviteListPresenterTests.kt | 72 +++++++++---------- .../forward/ForwardMessagesPresenterTests.kt | 2 +- .../impl/DefaultInviteStateDataSourceTest.kt | 32 ++++----- .../roomlist/impl/RoomListPresenterTests.kt | 6 +- .../impl/room/RustRoomSummaryDataSource.kt | 2 +- .../matrix/test/room/FakeMatrixRoom.kt | 4 ++ .../test/room/FakeRoomSummaryDataSource.kt | 26 +++++-- .../test/timeline/FakeMatrixTimeline.kt | 8 --- .../android/samples/minimal/RoomListScreen.kt | 10 ++- 9 files changed, 87 insertions(+), 75 deletions(-) diff --git a/features/invitelist/impl/src/test/kotlin/io/element/android/features/invitelist/impl/InviteListPresenterTests.kt b/features/invitelist/impl/src/test/kotlin/io/element/android/features/invitelist/impl/InviteListPresenterTests.kt index cf4f1058e5..cbfcbc3003 100644 --- a/features/invitelist/impl/src/test/kotlin/io/element/android/features/invitelist/impl/InviteListPresenterTests.kt +++ b/features/invitelist/impl/src/test/kotlin/io/element/android/features/invitelist/impl/InviteListPresenterTests.kt @@ -35,8 +35,8 @@ import io.element.android.libraries.matrix.test.A_ROOM_NAME import io.element.android.libraries.matrix.test.A_USER_ID import io.element.android.libraries.matrix.test.A_USER_NAME import io.element.android.libraries.matrix.test.FakeMatrixClient -import io.element.android.libraries.matrix.test.room.FakeRoomSummaryDataSource import io.element.android.libraries.matrix.test.room.FakeMatrixRoom +import io.element.android.libraries.matrix.test.room.FakeRoomSummaryDataSource import kotlinx.coroutines.test.runTest import org.junit.Test @@ -44,10 +44,10 @@ class InviteListPresenterTests { @Test fun `present - starts empty, adds invites when received`() = runTest { - val invitesDataSource = FakeRoomSummaryDataSource() + val roomSummaryDataSource = FakeRoomSummaryDataSource() val presenter = InviteListPresenter( FakeMatrixClient( - invitesDataSource = invitesDataSource, + roomSummaryDataSource = roomSummaryDataSource, ), FakeSeenInvitesStore(), ) @@ -57,7 +57,7 @@ class InviteListPresenterTests { val initialState = awaitItem() Truth.assertThat(initialState.inviteList).isEmpty() - invitesDataSource.postRoomSummary(listOf(aRoomSummary())) + roomSummaryDataSource.postInviteRooms(listOf(aRoomSummary())) val withInviteState = awaitItem() Truth.assertThat(withInviteState.inviteList.size).isEqualTo(1) @@ -68,10 +68,10 @@ class InviteListPresenterTests { @Test fun `present - uses user ID and avatar for direct invites`() = runTest { - val invitesDataSource = FakeRoomSummaryDataSource().withDirectChatInvitation() + val roomSummaryDataSource = FakeRoomSummaryDataSource().withDirectChatInvitation() val presenter = InviteListPresenter( FakeMatrixClient( - invitesDataSource = invitesDataSource, + roomSummaryDataSource = roomSummaryDataSource, ), FakeSeenInvitesStore(), ) @@ -96,10 +96,10 @@ class InviteListPresenterTests { @Test fun `present - includes sender details for room invites`() = runTest { - val invitesDataSource = FakeRoomSummaryDataSource().withRoomInvitation() + val roomSummaryDataSource = FakeRoomSummaryDataSource().withRoomInvitation() val presenter = InviteListPresenter( FakeMatrixClient( - invitesDataSource = invitesDataSource, + roomSummaryDataSource = roomSummaryDataSource, ), FakeSeenInvitesStore(), ) @@ -122,10 +122,10 @@ class InviteListPresenterTests { @Test fun `present - shows confirm dialog for declining direct chat invites`() = runTest { - val invitesDataSource = FakeRoomSummaryDataSource().withDirectChatInvitation() + val roomSummaryDataSource = FakeRoomSummaryDataSource().withDirectChatInvitation() val presenter = InviteListPresenter( FakeMatrixClient( - invitesDataSource = invitesDataSource, + roomSummaryDataSource = roomSummaryDataSource, ), FakeSeenInvitesStore(), ) @@ -146,10 +146,10 @@ class InviteListPresenterTests { @Test fun `present - shows confirm dialog for declining room invites`() = runTest { - val invitesDataSource = FakeRoomSummaryDataSource().withRoomInvitation() + val roomSummaryDataSource = FakeRoomSummaryDataSource().withRoomInvitation() val presenter = InviteListPresenter( FakeMatrixClient( - invitesDataSource = invitesDataSource, + roomSummaryDataSource = roomSummaryDataSource, ), FakeSeenInvitesStore(), ) @@ -170,10 +170,10 @@ class InviteListPresenterTests { @Test fun `present - hides confirm dialog when cancelling`() = runTest { - val invitesDataSource = FakeRoomSummaryDataSource().withRoomInvitation() + val roomSummaryDataSource = FakeRoomSummaryDataSource().withRoomInvitation() val presenter = InviteListPresenter( FakeMatrixClient( - invitesDataSource = invitesDataSource, + roomSummaryDataSource = roomSummaryDataSource, ), FakeSeenInvitesStore(), ) @@ -194,9 +194,9 @@ class InviteListPresenterTests { @Test fun `present - declines invite after confirming`() = runTest { - val invitesDataSource = FakeRoomSummaryDataSource().withRoomInvitation() + val roomSummaryDataSource = FakeRoomSummaryDataSource().withRoomInvitation() val client = FakeMatrixClient( - invitesDataSource = invitesDataSource, + roomSummaryDataSource = roomSummaryDataSource, ) val room = FakeMatrixRoom() val presenter = InviteListPresenter(client, FakeSeenInvitesStore()) @@ -220,9 +220,9 @@ class InviteListPresenterTests { @Test fun `present - declines invite after confirming and sets state on error`() = runTest { - val invitesDataSource = FakeRoomSummaryDataSource().withRoomInvitation() + val roomSummaryDataSource = FakeRoomSummaryDataSource().withRoomInvitation() val client = FakeMatrixClient( - invitesDataSource = invitesDataSource, + roomSummaryDataSource = roomSummaryDataSource, ) val room = FakeMatrixRoom() val presenter = InviteListPresenter(client, FakeSeenInvitesStore()) @@ -251,9 +251,9 @@ class InviteListPresenterTests { @Test fun `present - dismisses declining error state`() = runTest { - val invitesDataSource = FakeRoomSummaryDataSource().withRoomInvitation() + val roomSummaryDataSource = FakeRoomSummaryDataSource().withRoomInvitation() val client = FakeMatrixClient( - invitesDataSource = invitesDataSource, + roomSummaryDataSource = roomSummaryDataSource, ) val room = FakeMatrixRoom() val presenter = InviteListPresenter(client, FakeSeenInvitesStore()) @@ -283,9 +283,9 @@ class InviteListPresenterTests { @Test fun `present - accepts invites and sets state on success`() = runTest { - val invitesDataSource = FakeRoomSummaryDataSource().withRoomInvitation() + val roomSummaryDataSource = FakeRoomSummaryDataSource().withRoomInvitation() val client = FakeMatrixClient( - invitesDataSource = invitesDataSource, + roomSummaryDataSource = roomSummaryDataSource, ) val room = FakeMatrixRoom() val presenter = InviteListPresenter(client, FakeSeenInvitesStore()) @@ -306,9 +306,9 @@ class InviteListPresenterTests { @Test fun `present - accepts invites and sets state on error`() = runTest { - val invitesDataSource = FakeRoomSummaryDataSource().withRoomInvitation() + val roomSummaryDataSource = FakeRoomSummaryDataSource().withRoomInvitation() val client = FakeMatrixClient( - invitesDataSource = invitesDataSource, + roomSummaryDataSource = roomSummaryDataSource, ) val room = FakeMatrixRoom() val presenter = InviteListPresenter(client, FakeSeenInvitesStore()) @@ -331,9 +331,9 @@ class InviteListPresenterTests { @Test fun `present - dismisses accepting error state`() = runTest { - val invitesDataSource = FakeRoomSummaryDataSource().withRoomInvitation() + val roomSummaryDataSource = FakeRoomSummaryDataSource().withRoomInvitation() val client = FakeMatrixClient( - invitesDataSource = invitesDataSource, + roomSummaryDataSource = roomSummaryDataSource, ) val room = FakeMatrixRoom() val presenter = InviteListPresenter(client, FakeSeenInvitesStore()) @@ -358,11 +358,11 @@ class InviteListPresenterTests { @Test fun `present - stores seen invites when received`() = runTest { - val invitesDataSource = FakeRoomSummaryDataSource() + val roomSummaryDataSource = FakeRoomSummaryDataSource() val store = FakeSeenInvitesStore() val presenter = InviteListPresenter( FakeMatrixClient( - invitesDataSource = invitesDataSource, + roomSummaryDataSource = roomSummaryDataSource, ), store, ) @@ -372,19 +372,19 @@ class InviteListPresenterTests { awaitItem() // When one invite is received, that ID is saved - invitesDataSource.postRoomSummary(listOf(aRoomSummary())) + roomSummaryDataSource.postInviteRooms(listOf(aRoomSummary())) awaitItem() Truth.assertThat(store.getProvidedRoomIds()).isEqualTo(setOf(A_ROOM_ID)) // When a second is added, both are saved - invitesDataSource.postRoomSummary(listOf(aRoomSummary(), aRoomSummary(A_ROOM_ID_2))) + roomSummaryDataSource.postInviteRooms(listOf(aRoomSummary(), aRoomSummary(A_ROOM_ID_2))) awaitItem() Truth.assertThat(store.getProvidedRoomIds()).isEqualTo(setOf(A_ROOM_ID, A_ROOM_ID_2)) // When they're both dismissed, an empty set is saved - invitesDataSource.postRoomSummary(listOf()) + roomSummaryDataSource.postInviteRooms(listOf()) awaitItem() Truth.assertThat(store.getProvidedRoomIds()).isEmpty() @@ -393,12 +393,12 @@ class InviteListPresenterTests { @Test fun `present - marks invite as new if they're unseen`() = runTest { - val invitesDataSource = FakeRoomSummaryDataSource() + val roomSummaryDataSource = FakeRoomSummaryDataSource() val store = FakeSeenInvitesStore() store.publishRoomIds(setOf(A_ROOM_ID)) val presenter = InviteListPresenter( FakeMatrixClient( - invitesDataSource = invitesDataSource, + roomSummaryDataSource = roomSummaryDataSource, ), store, ) @@ -407,7 +407,7 @@ class InviteListPresenterTests { }.test { awaitItem() - invitesDataSource.postRoomSummary(listOf(aRoomSummary(), aRoomSummary(A_ROOM_ID_2))) + roomSummaryDataSource.postInviteRooms(listOf(aRoomSummary(), aRoomSummary(A_ROOM_ID_2))) skipItems(1) val withInviteState = awaitItem() @@ -420,7 +420,7 @@ class InviteListPresenterTests { } private suspend fun FakeRoomSummaryDataSource.withRoomInvitation(): FakeRoomSummaryDataSource { - postRoomSummary( + postInviteRooms( listOf( RoomSummary.Filled( RoomSummaryDetails( @@ -449,7 +449,7 @@ class InviteListPresenterTests { } private suspend fun FakeRoomSummaryDataSource.withDirectChatInvitation(): FakeRoomSummaryDataSource { - postRoomSummary( + postInviteRooms( listOf( RoomSummary.Filled( RoomSummaryDetails( diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/forward/ForwardMessagesPresenterTests.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/forward/ForwardMessagesPresenterTests.kt index b4efaca864..82526e100d 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/forward/ForwardMessagesPresenterTests.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/forward/ForwardMessagesPresenterTests.kt @@ -78,7 +78,7 @@ class ForwardMessagesPresenterTests { @Test fun `present - update query`() = runTest { val roomSummaryDataSource = FakeRoomSummaryDataSource().apply { - postRoomSummary(listOf(RoomSummary.Filled(aRoomSummaryDetail()))) + postAllRooms(listOf(RoomSummary.Filled(aRoomSummaryDetail()))) } val client = FakeMatrixClient(roomSummaryDataSource = roomSummaryDataSource) val presenter = aPresenter(client = client) diff --git a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/DefaultInviteStateDataSourceTest.kt b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/DefaultInviteStateDataSourceTest.kt index 389aee1e92..1f3f0105da 100644 --- a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/DefaultInviteStateDataSourceTest.kt +++ b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/DefaultInviteStateDataSourceTest.kt @@ -34,8 +34,8 @@ internal class DefaultInviteStateDataSourceTest { @Test fun `emits NoInvites state if invites list is empty`() = runTest { - val matrixDataSource = FakeRoomSummaryDataSource() - val client = FakeMatrixClient(invitesDataSource = matrixDataSource) + val roomSummaryDataSource = FakeRoomSummaryDataSource() + val client = FakeMatrixClient(roomSummaryDataSource = roomSummaryDataSource) val seenStore = FakeSeenInvitesStore() val dataSource = DefaultInviteStateDataSource(client, seenStore, testCoroutineDispatchers()) @@ -48,9 +48,9 @@ internal class DefaultInviteStateDataSourceTest { @Test fun `emits NewInvites state if unseen invite exists`() = runTest { - val matrixDataSource = FakeRoomSummaryDataSource() - matrixDataSource.postRoomSummary(listOf(aRoomSummaryFilled(roomId = A_ROOM_ID))) - val client = FakeMatrixClient(invitesDataSource = matrixDataSource) + val roomSummaryDataSource = FakeRoomSummaryDataSource() + roomSummaryDataSource.postInviteRooms(listOf(aRoomSummaryFilled(roomId = A_ROOM_ID))) + val client = FakeMatrixClient(roomSummaryDataSource = roomSummaryDataSource) val seenStore = FakeSeenInvitesStore() val dataSource = DefaultInviteStateDataSource(client, seenStore, testCoroutineDispatchers()) @@ -64,9 +64,9 @@ internal class DefaultInviteStateDataSourceTest { @Test fun `emits NewInvites state if multiple invites exist and at least one is unseen`() = runTest { - val matrixDataSource = FakeRoomSummaryDataSource() - matrixDataSource.postRoomSummary(listOf(aRoomSummaryFilled(roomId = A_ROOM_ID), aRoomSummaryFilled(roomId = A_ROOM_ID_2))) - val client = FakeMatrixClient(invitesDataSource = matrixDataSource) + val roomSummaryDataSource = FakeRoomSummaryDataSource() + roomSummaryDataSource.postInviteRooms(listOf(aRoomSummaryFilled(roomId = A_ROOM_ID), aRoomSummaryFilled(roomId = A_ROOM_ID_2))) + val client = FakeMatrixClient(roomSummaryDataSource = roomSummaryDataSource) val seenStore = FakeSeenInvitesStore() seenStore.publishRoomIds(setOf(A_ROOM_ID)) val dataSource = DefaultInviteStateDataSource(client, seenStore, testCoroutineDispatchers(useUnconfinedTestDispatcher = true)) @@ -81,9 +81,9 @@ internal class DefaultInviteStateDataSourceTest { @Test fun `emits SeenInvites state if invite exists in seen store`() = runTest { - val matrixDataSource = FakeRoomSummaryDataSource() - matrixDataSource.postRoomSummary(listOf(aRoomSummaryFilled(roomId = A_ROOM_ID))) - val client = FakeMatrixClient(invitesDataSource = matrixDataSource) + val roomSummaryDataSource = FakeRoomSummaryDataSource() + roomSummaryDataSource.postInviteRooms(listOf(aRoomSummaryFilled(roomId = A_ROOM_ID))) + val client = FakeMatrixClient(roomSummaryDataSource = roomSummaryDataSource) val seenStore = FakeSeenInvitesStore() seenStore.publishRoomIds(setOf(A_ROOM_ID)) val dataSource = DefaultInviteStateDataSource(client, seenStore, testCoroutineDispatchers(useUnconfinedTestDispatcher = true)) @@ -99,8 +99,8 @@ internal class DefaultInviteStateDataSourceTest { @Test fun `emits new state in response to upstream events`() = runTest { - val matrixDataSource = FakeRoomSummaryDataSource() - val client = FakeMatrixClient(invitesDataSource = matrixDataSource) + val roomSummaryDataSource = FakeRoomSummaryDataSource() + val client = FakeMatrixClient(roomSummaryDataSource = roomSummaryDataSource) val seenStore = FakeSeenInvitesStore() val dataSource = DefaultInviteStateDataSource(client, seenStore, testCoroutineDispatchers()) @@ -111,7 +111,7 @@ internal class DefaultInviteStateDataSourceTest { Truth.assertThat(awaitItem()).isEqualTo(InvitesState.NoInvites) // When a single invite is received, state should be NewInvites - matrixDataSource.postRoomSummary(listOf(aRoomSummaryFilled(roomId = A_ROOM_ID))) + roomSummaryDataSource.postInviteRooms(listOf(aRoomSummaryFilled(roomId = A_ROOM_ID))) skipItems(1) Truth.assertThat(awaitItem()).isEqualTo(InvitesState.NewInvites) @@ -121,12 +121,12 @@ internal class DefaultInviteStateDataSourceTest { Truth.assertThat(awaitItem()).isEqualTo(InvitesState.SeenInvites) // Another new invite resets it to NewInvites - matrixDataSource.postRoomSummary(listOf(aRoomSummaryFilled(roomId = A_ROOM_ID), aRoomSummaryFilled(roomId = A_ROOM_ID_2))) + roomSummaryDataSource.postInviteRooms(listOf(aRoomSummaryFilled(roomId = A_ROOM_ID), aRoomSummaryFilled(roomId = A_ROOM_ID_2))) skipItems(1) Truth.assertThat(awaitItem()).isEqualTo(InvitesState.NewInvites) // All of the invites going away reverts to NoInvites - matrixDataSource.postRoomSummary(emptyList()) + roomSummaryDataSource.postInviteRooms(emptyList()) skipItems(1) Truth.assertThat(awaitItem()).isEqualTo(InvitesState.NoInvites) } diff --git a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt index 37623b4f28..c18b52f1d5 100644 --- a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt +++ b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt @@ -146,7 +146,7 @@ class RoomListPresenterTests { // Room list is loaded with 16 placeholders Truth.assertThat(withUserState.roomList.size).isEqualTo(16) Truth.assertThat(withUserState.roomList.all { it.isPlaceholder }).isTrue() - roomSummaryDataSource.postRoomSummary(listOf(aRoomSummaryFilled())) + roomSummaryDataSource.postAllRooms(listOf(aRoomSummaryFilled())) skipItems(1) val withRoomState = awaitItem() Truth.assertThat(withRoomState.roomList.size).isEqualTo(1) @@ -173,7 +173,7 @@ class RoomListPresenterTests { moleculeFlow(RecompositionClock.Immediate) { presenter.present() }.test { - roomSummaryDataSource.postRoomSummary(listOf(aRoomSummaryFilled())) + roomSummaryDataSource.postAllRooms(listOf(aRoomSummaryFilled())) skipItems(3) val loadedState = awaitItem() // Test filtering with result @@ -211,7 +211,7 @@ class RoomListPresenterTests { moleculeFlow(RecompositionClock.Immediate) { presenter.present() }.test { - roomSummaryDataSource.postRoomSummary(listOf(aRoomSummaryFilled())) + roomSummaryDataSource.postAllRooms(listOf(aRoomSummaryFilled())) skipItems(3) val loadedState = awaitItem() // check initial value 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 45e399e2d5..a1bd5daf8c 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 @@ -112,7 +112,7 @@ private fun RoomListLoadingState.toRoomSummaryDataSourceLoadingState(): RoomSumm } } -fun RoomList.observeEntriesWithProcessor(processor: RoomSummaryListProcessor): Flow { +private fun RoomList.observeEntriesWithProcessor(processor: RoomSummaryListProcessor): Flow { return entriesFlow { roomListEntries -> processor.postEntries(roomListEntries) }.onEach { update -> diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt index 6fab4cb053..bf1ac3544d 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt @@ -134,6 +134,10 @@ class FakeMatrixRoom( return matrixTimeline } + override fun open(): Result { + return Result.success(Unit) + } + override suspend fun userDisplayName(userId: UserId): Result = simulateLongTask { userDisplayNameResult } 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 a407c0954f..87169aa498 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 @@ -23,14 +23,32 @@ import kotlinx.coroutines.flow.StateFlow class FakeRoomSummaryDataSource : RoomSummaryDataSource { - private val roomSummariesFlow = MutableStateFlow>(emptyList()) + private val allRoomSummariesFlow = MutableStateFlow>(emptyList()) + private val inviteRoomSummariesFlow = MutableStateFlow>(emptyList()) + private val allRoomsLoadingStateFlow = MutableStateFlow(RoomSummaryDataSource.LoadingState.NotLoaded) - suspend fun postRoomSummary(roomSummaries: List) { - roomSummariesFlow.emit(roomSummaries) + suspend fun postAllRooms(roomSummaries: List) { + allRoomSummariesFlow.emit(roomSummaries) + } + + suspend fun postInviteRooms(roomSummaries: List) { + inviteRoomSummariesFlow.emit(roomSummaries) + } + + suspend fun postLoadingState(loadingState: RoomSummaryDataSource.LoadingState) { + allRoomsLoadingStateFlow.emit(loadingState) + } + + override fun allRoomsLoadingState(): StateFlow { + return allRoomsLoadingStateFlow } override fun allRooms(): StateFlow> { - return roomSummariesFlow + return allRoomSummariesFlow + } + + override fun inviteRooms(): StateFlow> { + return inviteRoomSummariesFlow } var latestSlidingSyncRange: IntRange? = null diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/FakeMatrixTimeline.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/FakeMatrixTimeline.kt index 5adc8b8404..f26905f5b1 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/FakeMatrixTimeline.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/FakeMatrixTimeline.kt @@ -31,7 +31,6 @@ class FakeMatrixTimeline( private val paginationState: MutableStateFlow = MutableStateFlow(initialPaginationState) private val timelineItems: MutableStateFlow> = MutableStateFlow(initialTimelineItems) - var isInitialized = false fun updatePaginationState(update: (MatrixTimeline.PaginationState.() -> MatrixTimeline.PaginationState)) { paginationState.value = update(paginationState.value) @@ -63,13 +62,6 @@ class FakeMatrixTimeline( return Result.success(Unit) } - override fun initialize() { - isInitialized = true - } - - override fun dispose() { - isInitialized = false - } override suspend fun fetchDetailsForEvent(eventId: EventId): Result { return Result.success(Unit) diff --git a/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/RoomListScreen.kt b/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/RoomListScreen.kt index 5d8e0bfd1c..9c4235ac2e 100644 --- a/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/RoomListScreen.kt +++ b/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/RoomListScreen.kt @@ -69,7 +69,7 @@ class RoomListScreen( stateContentFormatter = StateContentFormatter(stringProvider), ), sessionVerificationService = sessionVerificationService, - networkMonitor = NetworkMonitorImpl(context), + networkMonitor = NetworkMonitorImpl(context, Singleton.appScope), snackbarDispatcher = SnackbarDispatcher(), inviteStateDataSource = DefaultInviteStateDataSource(matrixClient, DefaultSeenInvitesStore(context), coroutineDispatchers), leaveRoomPresenter = LeaveRoomPresenterImpl(matrixClient, RoomMembershipObserver(), coroutineDispatchers) @@ -81,13 +81,11 @@ class RoomListScreen( Singleton.appScope.launch { withContext(coroutineDispatchers.io) { matrixClient.getRoom(roomId)!!.use { room -> + room.open() val timeline = room.timeline() - timeline.apply { // TODO This doesn't work reliably as initialize is asynchronous, and the timeline can't be used until it's finished - initialize() paginateBackwards(20, 50) - dispose() } } } @@ -108,10 +106,10 @@ class RoomListScreen( DisposableEffect(Unit) { Timber.w("Start sync!") - matrixClient.startSync() + matrixClient.syncService().startSync() onDispose { Timber.w("Stop sync!") - matrixClient.stopSync() + matrixClient.syncService().stopSync() } } } From f6a1db44a6a836a7179151f25a5a66683e3730cd Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 27 Jun 2023 14:39:28 +0200 Subject: [PATCH 18/21] Fix some code quality/formatting --- .../{childScopeOf.kt => ChildScopeOf.kt} | 0 .../matrix/impl/room/RoomContentForwarder.kt | 2 +- .../matrix/impl/room/RoomListExtensions.kt | 16 ++++++++++++++++ 3 files changed, 17 insertions(+), 1 deletion(-) rename libraries/core/src/main/kotlin/io/element/android/libraries/core/coroutine/{childScopeOf.kt => ChildScopeOf.kt} (100%) diff --git a/libraries/core/src/main/kotlin/io/element/android/libraries/core/coroutine/childScopeOf.kt b/libraries/core/src/main/kotlin/io/element/android/libraries/core/coroutine/ChildScopeOf.kt similarity index 100% rename from libraries/core/src/main/kotlin/io/element/android/libraries/core/coroutine/childScopeOf.kt rename to libraries/core/src/main/kotlin/io/element/android/libraries/core/coroutine/ChildScopeOf.kt diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomContentForwarder.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomContentForwarder.kt index 8e99fad147..73abea0d77 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomContentForwarder.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomContentForwarder.kt @@ -32,7 +32,7 @@ import kotlin.time.Duration.Companion.milliseconds /** * Helper to forward event contents from a room to a set of other rooms. - * @param slidingSync the [SlidingSync] to fetch room instances to forward the event to + * @param roomListService the [RoomListService] to fetch room instances to forward the event to */ class RoomContentForwarder( private val roomListService: RoomListService, diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomListExtensions.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomListExtensions.kt index 2697fa87fb..a6569158d0 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomListExtensions.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomListExtensions.kt @@ -1,3 +1,19 @@ +/* + * 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.impl.util.mxCallbackFlow From 98c146c63376c19b69b45f093ba54a2b072f7ea6 Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 27 Jun 2023 17:58:45 +0200 Subject: [PATCH 19/21] NetworkMonitor: new strategy, looks like it works. --- .../networkmonitor/impl/NetworkMonitorImpl.kt | 27 ++++++++++++------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/features/networkmonitor/impl/src/main/kotlin/io/element/android/features/networkmonitor/impl/NetworkMonitorImpl.kt b/features/networkmonitor/impl/src/main/kotlin/io/element/android/features/networkmonitor/impl/NetworkMonitorImpl.kt index 3901ec4120..ce881163f1 100644 --- a/features/networkmonitor/impl/src/main/kotlin/io/element/android/features/networkmonitor/impl/NetworkMonitorImpl.kt +++ b/features/networkmonitor/impl/src/main/kotlin/io/element/android/features/networkmonitor/impl/NetworkMonitorImpl.kt @@ -38,8 +38,10 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.stateIn import timber.log.Timber +import java.util.concurrent.atomic.AtomicInteger import javax.inject.Inject @ContributesBinding(scope = AppScope::class) @@ -54,27 +56,31 @@ class NetworkMonitorImpl @Inject constructor( override val connectivity: StateFlow = callbackFlow { /** - * Assume there is no network available as soon as onLost is called. - * Reading activeNetwork synchronously from the callback is not safe. - * If there is an other active network we'll get some callback in onCapabilitiesChanged. + * Calling connectivityManager methods synchronously from the callbacks is not safe. + * So instead we just keep the count of active networks, ie. those checking the capability request. * Debounce the result to avoid quick offline<->online changes. */ val callback = object : ConnectivityManager.NetworkCallback() { + + private val activeNetworksCount = AtomicInteger(0) + override fun onLost(network: Network) { - trySendBlocking(NetworkStatus.Offline) + if (activeNetworksCount.decrementAndGet() == 0) { + trySendBlocking(NetworkStatus.Offline) + } } - override fun onCapabilitiesChanged( - network: Network, - networkCapabilities: NetworkCapabilities - ) { - trySendBlocking(networkCapabilities.getNetworkStatus()) + override fun onAvailable(network: Network) { + if (activeNetworksCount.incrementAndGet() > 0) { + trySendBlocking(NetworkStatus.Online) + } } } trySendBlocking(connectivityManager.activeNetworkStatus()) val request = NetworkRequest.Builder() .addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) .build() + connectivityManager.registerNetworkCallback(request, callback) Timber.d("Subscribe") awaitClose { @@ -84,6 +90,9 @@ class NetworkMonitorImpl @Inject constructor( } .distinctUntilChanged() .debounce(300) + .onEach { + Timber.d("NetworkStatus changed=$it") + } .stateIn(appCoroutineScope, SharingStarted.WhileSubscribed(), connectivityManager.activeNetworkStatus()) private fun ConnectivityManager.activeNetworkStatus(): NetworkStatus { From f25314f7f7d42f0526846ed0292c701a79055a5a Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 28 Jun 2023 16:41:59 +0200 Subject: [PATCH 20/21] Coroutine: create extension method to create childScope --- .../libraries/core/coroutine/ChildScopeOf.kt | 16 ++++++++++++---- .../libraries/matrix/impl/RustMatrixClient.kt | 4 ++-- .../libraries/matrix/impl/room/RustMatrixRoom.kt | 4 ++-- 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/libraries/core/src/main/kotlin/io/element/android/libraries/core/coroutine/ChildScopeOf.kt b/libraries/core/src/main/kotlin/io/element/android/libraries/core/coroutine/ChildScopeOf.kt index 060431fdee..872ec0a5cd 100644 --- a/libraries/core/src/main/kotlin/io/element/android/libraries/core/coroutine/ChildScopeOf.kt +++ b/libraries/core/src/main/kotlin/io/element/android/libraries/core/coroutine/ChildScopeOf.kt @@ -23,11 +23,19 @@ import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.job import kotlinx.coroutines.plus -fun childScopeOf( - parentScope: CoroutineScope, +/** + * Create a child scope of the current scope. + * The child scope will be cancelled if the parent scope is cancelled. + * The child scope will be cancelled if an exception is thrown in the parent scope. + * The parent scope won't be cancelled when an exception is thrown in the child scope. + * + * @param dispatcher the dispatcher to use for this scope. + * @param name the name of the coroutine. + */ +fun CoroutineScope.childScope( dispatcher: CoroutineDispatcher, name: String, ): CoroutineScope = run { - val supervisorJob = SupervisorJob(parent = parentScope.coroutineContext.job) - parentScope + dispatcher + supervisorJob + CoroutineName(name) + val supervisorJob = SupervisorJob(parent = coroutineContext.job) + this + dispatcher + supervisorJob + CoroutineName(name) } 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 127c1c0adb..8aa2c424a7 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 @@ -19,7 +19,7 @@ package io.element.android.libraries.matrix.impl import io.element.android.libraries.androidutils.file.getSizeOfFiles import io.element.android.libraries.androidutils.file.safeDelete import io.element.android.libraries.core.coroutine.CoroutineDispatchers -import io.element.android.libraries.core.coroutine.childScopeOf +import io.element.android.libraries.core.coroutine.childScope import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.core.ProgressCallback import io.element.android.libraries.matrix.api.core.RoomId @@ -83,7 +83,7 @@ class RustMatrixClient constructor( override val sessionId: UserId = UserId(client.userId()) private val roomListService = client.roomListService() - private val sessionCoroutineScope = childScopeOf(appCoroutineScope, dispatchers.main, "Session-${sessionId}") + private val sessionCoroutineScope = appCoroutineScope.childScope(dispatchers.main, "Session-${sessionId}") private val verificationService = RustSessionVerificationService() private val syncService = RustSyncService(roomListService, sessionCoroutineScope) private val pushersService = RustPushersService( diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt index 56bd66b7c7..30d339c0fa 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt @@ -17,7 +17,7 @@ package io.element.android.libraries.matrix.impl.room import io.element.android.libraries.core.coroutine.CoroutineDispatchers -import io.element.android.libraries.core.coroutine.childScopeOf +import io.element.android.libraries.core.coroutine.childScope import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.ProgressCallback import io.element.android.libraries.matrix.api.core.RoomId @@ -70,7 +70,7 @@ class RustMatrixRoom( override val roomId = RoomId(innerRoom.id()) - private val roomCoroutineScope = childScopeOf(sessionCoroutineScope, coroutineDispatchers.main, "RoomScope-$roomId") + private val roomCoroutineScope = sessionCoroutineScope.childScope(coroutineDispatchers.main, "RoomScope-$roomId") override val membersStateFlow: StateFlow get() = _membersStateFlow From 06318300598862c1214f0e75fd6f6ac5575427b9 Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 28 Jun 2023 16:49:08 +0200 Subject: [PATCH 21/21] RoomListService: enable encryption --- .../element/android/libraries/matrix/impl/RustMatrixClient.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 8aa2c424a7..060f8fc0a9 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 @@ -81,8 +81,7 @@ class RustMatrixClient constructor( ) : MatrixClient { override val sessionId: UserId = UserId(client.userId()) - - private val roomListService = client.roomListService() + private val roomListService = client.roomListServiceWithEncryption() private val sessionCoroutineScope = appCoroutineScope.childScope(dispatchers.main, "Session-${sessionId}") private val verificationService = RustSessionVerificationService() private val syncService = RustSyncService(roomListService, sessionCoroutineScope) @@ -92,6 +91,7 @@ class RustMatrixClient constructor( ) private val notificationService = RustNotificationService(client) + private val clientDelegate = object : ClientDelegate { override fun didReceiveAuthError(isSoftLogout: Boolean) { //TODO handle this