diff --git a/.github/workflows/nightlyReports.yml b/.github/workflows/nightlyReports.yml index cbe9935fc7..ef2c294f2e 100644 --- a/.github/workflows/nightlyReports.yml +++ b/.github/workflows/nightlyReports.yml @@ -26,11 +26,11 @@ jobs: distribution: 'temurin' # See 'Supported distributions' for available options java-version: '17' - - name: ⚙️ Run unit & screenshot tests, debug and release - run: ./gradlew test $CI_GRADLE_ARG_PROPERTIES -Pci-build=true + - name: ⚙️ Run unit tests, debug and release + run: ./gradlew test $CI_GRADLE_ARG_PROPERTIES - - name: ⚙️ Run unit & screenshot tests, generate kover report - run: ./gradlew koverMergedReport $CI_GRADLE_ARG_PROPERTIES -Pci-build=true + - name: 📈 Run screenshot tests, generate kover report and verify coverage + run: ./gradlew verifyPaparazziDebug koverMergedReport koverMergedVerify $CI_GRADLE_ARG_PROPERTIES -Pci-build=true - name: ✅ Upload kover report if: always() diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index f5df6b1362..ffdd6f5ad7 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -37,14 +37,11 @@ jobs: with: cache-read-only: ${{ github.ref != 'refs/heads/develop' }} - - name: ⚙️ Run unit & screenshot tests, debug and release - run: ./gradlew test $CI_GRADLE_ARG_PROPERTIES -Pci-build=true + - name: ⚙️ Run unit tests, debug and release + run: ./gradlew test $CI_GRADLE_ARG_PROPERTIES - - name: ⚙️ Run unit & screenshot tests, generate kover report - run: ./gradlew koverMergedReport $CI_GRADLE_ARG_PROPERTIES -Pci-build=true - - - name: 📈 Verify coverage - run: ./gradlew koverMergedVerify $CI_GRADLE_ARG_PROPERTIES -Pci-build=true + - name: 📈 Run screenshot tests, generate kover report and verify coverage + run: ./gradlew verifyPaparazziDebug koverMergedReport koverMergedVerify $CI_GRADLE_ARG_PROPERTIES -Pci-build=true - name: 🚫 Upload kover failed coverage reports if: failure() diff --git a/build.gradle.kts b/build.gradle.kts index b5d082b8ce..f8eda34bd8 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -135,6 +135,15 @@ allprojects { allprojects { tasks.withType { maxParallelForks = (Runtime.getRuntime().availableProcessors() / 2).coerceAtLeast(1) + + val isScreenshotTest = project.gradle.startParameter.taskNames.any { it.contains("paparazzi", ignoreCase = true) } + if (isScreenshotTest) { + // Increase heap size for screenshot tests + maxHeapSize = "1g" + } else { + // Disable screenshot tests by default + exclude("**/ScreenshotTest*") + } } } @@ -245,9 +254,11 @@ koverMerged { excludes += "io.element.android.libraries.push.impl.notifications.NotificationState*" excludes += "io.element.android.features.messages.impl.media.local.pdf.PdfViewerState" excludes += "io.element.android.features.messages.impl.media.local.LocalMediaViewState" - excludes += "io.element.android.features.location.impl.map.MapState" + excludes += "io.element.android.features.location.impl.map.MapState*" excludes += "io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState*" excludes += "io.element.android.libraries.designsystem.swipe.SwipeableActionsState*" + excludes += "io.element.android.features.messages.impl.timeline.components.ExpandableState*" + excludes += "io.element.android.features.messages.impl.timeline.model.bubble.BubbleState*" } bound { minValue = 90 diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineStateProvider.kt index e96d7ac0f3..7539043b5d 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineStateProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineStateProvider.kt @@ -36,6 +36,7 @@ import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toPersistentList +import java.util.UUID import kotlin.random.Random fun aTimelineState(timelineItems: ImmutableList = persistentListOf()) = TimelineState( @@ -96,7 +97,7 @@ internal fun aTimelineItemList(content: TimelineItemEventContent): ImmutableList } fun aTimelineItemDaySeparator(): TimelineItem.Virtual { - return TimelineItem.Virtual("virtual_day", aTimelineItemDaySeparatorModel("Today")) + return TimelineItem.Virtual(UUID.randomUUID().toString(), aTimelineItemDaySeparatorModel("Today")) } internal fun aTimelineItemEvent( @@ -111,7 +112,7 @@ internal fun aTimelineItemEvent( timelineItemReactions: TimelineItemReactions = aTimelineItemReactions(), ): TimelineItem.Event { return TimelineItem.Event( - id = eventId.value, + id = UUID.randomUUID().toString(), eventId = eventId, transactionId = transactionId, senderId = UserId("@senderId:domain"), diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/TimelineItemsFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/TimelineItemsFactory.kt index 87a91f7ec2..f607a0e034 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/TimelineItemsFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/TimelineItemsFactory.kt @@ -106,7 +106,7 @@ class TimelineItemsFactory @Inject constructor( val timelineItemState = when (val currentTimelineItem = timelineItems[index]) { is MatrixTimelineItem.Event -> eventItemFactory.create(currentTimelineItem, index, timelineItems) - is MatrixTimelineItem.Virtual -> virtualItemFactory.create(currentTimelineItem, index) + is MatrixTimelineItem.Virtual -> virtualItemFactory.create(currentTimelineItem) MatrixTimelineItem.Other -> null } timelineItemsCache[index] = timelineItemState diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemEventFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemEventFactory.kt index 6ccbf7143a..9a09c77a34 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemEventFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemEventFactory.kt @@ -71,7 +71,7 @@ class TimelineItemEventFactory @Inject constructor( size = AvatarSize.TimelineSender ) return TimelineItem.Event( - id = currentTimelineItem.uniqueId, + id = currentTimelineItem.uniqueId.toString(), eventId = currentTimelineItem.eventId, transactionId = currentTimelineItem.transactionId, senderId = currentSender, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/virtual/TimelineItemVirtualFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/virtual/TimelineItemVirtualFactory.kt index 8aae552880..cca1786bf8 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/virtual/TimelineItemVirtualFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/virtual/TimelineItemVirtualFactory.kt @@ -29,10 +29,9 @@ class TimelineItemVirtualFactory @Inject constructor( fun create( virtualTimelineItem: MatrixTimelineItem.Virtual, - index: Int, ): TimelineItem.Virtual { return TimelineItem.Virtual( - id = "virtual_item_$index", + id = virtualTimelineItem.uniqueId.toString(), model = virtualTimelineItem.computeModel() ) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItem.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItem.kt index 6743651e76..21b7e8607f 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItem.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItem.kt @@ -24,8 +24,8 @@ import io.element.android.libraries.designsystem.components.avatar.AvatarData import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo -import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState import io.element.android.libraries.matrix.api.timeline.item.event.InReplyTo +import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState import kotlinx.collections.immutable.ImmutableList @Immutable @@ -83,6 +83,6 @@ sealed interface TimelineItem { val events: ImmutableList, ) : TimelineItem { // use last id with a suffix. Last will not change in cas of new event from backpagination. - val id = events.last().id + "_group" + val id = "${events.last().id}_group" } } diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/TimelinePresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/TimelinePresenterTest.kt index 9151376d4d..08b8702d27 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/TimelinePresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/TimelinePresenterTest.kt @@ -96,7 +96,7 @@ class TimelinePresenterTest { fun `present - on scroll finished send read receipt if an event is before the index`() = runTest { val timeline = FakeMatrixTimeline() val timelineItemsFactory = aTimelineItemsFactory().apply { - replaceWith(listOf(MatrixTimelineItem.Event(anEventTimelineItem()))) + replaceWith(listOf(MatrixTimelineItem.Event(0, anEventTimelineItem()))) } val room = FakeMatrixRoom(matrixTimeline = timeline) val presenter = TimelinePresenter( @@ -119,7 +119,7 @@ class TimelinePresenterTest { fun `present - on scroll finished will not send read receipt no event is before the index`() = runTest { val timeline = FakeMatrixTimeline() val timelineItemsFactory = aTimelineItemsFactory().apply { - replaceWith(listOf(MatrixTimelineItem.Event(anEventTimelineItem()))) + replaceWith(listOf(MatrixTimelineItem.Event(0, anEventTimelineItem()))) } val room = FakeMatrixRoom(matrixTimeline = timeline) val presenter = TimelinePresenter( @@ -142,7 +142,7 @@ class TimelinePresenterTest { fun `present - on scroll finished will not send read receipt only virtual events exist before the index`() = runTest { val timeline = FakeMatrixTimeline() val timelineItemsFactory = aTimelineItemsFactory().apply { - replaceWith(listOf(MatrixTimelineItem.Virtual(VirtualTimelineItem.ReadMarker))) + replaceWith(listOf(MatrixTimelineItem.Virtual(0, VirtualTimelineItem.ReadMarker))) } val room = FakeMatrixRoom(matrixTimeline = timeline) val presenter = TimelinePresenter( diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 0c5ea537a5..eb848571d9 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -145,7 +145,7 @@ jsoup = { module = "org.jsoup:jsoup", version.ref = "jsoup" } appyx_core = { module = "com.bumble.appyx:core", version.ref = "appyx" } molecule-runtime = { module = "app.cash.molecule:molecule-runtime", version.ref = "molecule" } timber = "com.jakewharton.timber:timber:5.0.1" -matrix_sdk = "org.matrix.rustcomponents:sdk-android:0.1.29" +matrix_sdk = "org.matrix.rustcomponents:sdk-android:0.1.31" sqldelight-driver-android = { module = "com.squareup.sqldelight:android-driver", version.ref = "sqldelight" } sqldelight-driver-jvm = { module = "com.squareup.sqldelight:sqlite-driver", version.ref = "sqldelight" } sqldelight-coroutines = { module = "com.squareup.sqldelight:coroutines-extensions", version.ref = "sqldelight" } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/MatrixTimelineItem.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/MatrixTimelineItem.kt index f84f1875e4..e64d803aba 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/MatrixTimelineItem.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/MatrixTimelineItem.kt @@ -21,13 +21,12 @@ import io.element.android.libraries.matrix.api.timeline.item.event.EventTimeline import io.element.android.libraries.matrix.api.timeline.item.virtual.VirtualTimelineItem sealed interface MatrixTimelineItem { - data class Event(val event: EventTimelineItem) : MatrixTimelineItem { - val uniqueId: String = event.uniqueIdentifier + data class Event(val uniqueId: Long, val event: EventTimelineItem) : MatrixTimelineItem { val eventId: EventId? = event.eventId val transactionId: String? = event.transactionId } - data class Virtual(val virtual: VirtualTimelineItem) : MatrixTimelineItem + data class Virtual(val uniqueId: Long, val virtual: VirtualTimelineItem) : MatrixTimelineItem object Other : MatrixTimelineItem } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventTimelineItem.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventTimelineItem.kt index e2a86fbb3c..854d38b7dd 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventTimelineItem.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventTimelineItem.kt @@ -21,7 +21,6 @@ import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo data class EventTimelineItem( - val uniqueIdentifier: String, val eventId: EventId?, val transactionId: String?, val isEditable: Boolean, 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 75beba0901..8798e01f98 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 @@ -47,6 +47,7 @@ import io.element.android.libraries.matrix.impl.room.RoomContentForwarder 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 @@ -85,17 +86,23 @@ class RustMatrixClient constructor( ) : MatrixClient { override val sessionId: UserId = UserId(client.userId()) - private val roomListService = client.roomListServiceWithEncryption() + private val app = client.app().use { builder -> + builder.finish() + } + private val roomListService = app.roomListService() private val sessionDispatcher = dispatchers.io.limitedParallelism(64) private val sessionCoroutineScope = appCoroutineScope.childScope(dispatchers.main, "Session-${sessionId}") private val verificationService = RustSessionVerificationService() - private val syncService = RustSyncService(roomListService, sessionCoroutineScope) + private val syncService = RustSyncService(app, roomListService.stateFlow(), sessionCoroutineScope) private val pushersService = RustPushersService( client = client, dispatchers = dispatchers, ) - - private val notificationService = RustNotificationService(client) + private val notificationClient = client.notificationClient().use { builder -> + builder.finish() + } + + private val notificationService = RustNotificationService(notificationClient) private val clientDelegate = object : ClientDelegate { override fun didReceiveAuthError(isSoftLogout: Boolean) { @@ -249,7 +256,9 @@ class RustMatrixClient constructor( sessionCoroutineScope.cancel() client.setDelegate(null) verificationService.destroy() + app.destroy() roomListService.destroy() + notificationClient.destroy() client.destroy() } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notification/NotificationMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notification/NotificationMapper.kt index 957f2ff7b9..07acb7fec5 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notification/NotificationMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notification/NotificationMapper.kt @@ -27,12 +27,12 @@ import org.matrix.rustcomponents.sdk.use class NotificationMapper { private val timelineEventMapper = TimelineEventMapper() - fun map(notificationItem: NotificationItem): NotificationData { + fun map(roomId: RoomId, notificationItem: NotificationItem): NotificationData { return notificationItem.use { item -> NotificationData( senderId = UserId(item.event.senderId()), eventId = EventId(item.event.eventId()), - roomId = RoomId(item.roomInfo.id), + roomId = roomId, senderAvatarUrl = item.senderInfo.avatarUrl, senderDisplayName = item.senderInfo.displayName, roomAvatarUrl = item.roomInfo.avatarUrl ?: item.senderInfo.avatarUrl.takeIf { item.roomInfo.isDirect }, diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notification/RustNotificationService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notification/RustNotificationService.kt index 7d523abdc7..92c996049e 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notification/RustNotificationService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notification/RustNotificationService.kt @@ -21,11 +21,11 @@ import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.api.notification.NotificationData import io.element.android.libraries.matrix.api.notification.NotificationService -import org.matrix.rustcomponents.sdk.Client +import org.matrix.rustcomponents.sdk.NotificationClient import org.matrix.rustcomponents.sdk.use class RustNotificationService( - private val client: Client, + private val notificationClient: NotificationClient, ) : NotificationService { private val notificationMapper: NotificationMapper = NotificationMapper() @@ -36,8 +36,10 @@ class RustNotificationService( filterByPushRules: Boolean, ): Result { return runCatching { - val item = client.getNotificationItem(roomId.value, eventId.value, filterByPushRules) - item?.use(notificationMapper::map) + val item = notificationClient.getNotification(roomId.value, eventId.value) + item?.use { + notificationMapper.map(roomId, it) + } } } } 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 43d96f8e8a..7dd7bf4581 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 @@ -24,7 +24,7 @@ import org.matrix.rustcomponents.sdk.RoomListItem class RoomSummaryDetailsFactory(private val roomMessageFactory: RoomMessageFactory = RoomMessageFactory()) { - fun create(roomListItem: RoomListItem, room: Room?): RoomSummaryDetails { + suspend fun create(roomListItem: RoomListItem, room: Room?): RoomSummaryDetails { val latestRoomMessage = roomListItem.latestEvent()?.use { roomMessageFactory.create(it) } 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 f489bbc5e0..a8ab4cb807 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 @@ -16,6 +16,7 @@ package io.element.android.libraries.matrix.impl.room +import io.element.android.libraries.core.coroutine.parallelMap import io.element.android.libraries.matrix.api.room.RoomSummary import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.flow.MutableStateFlow @@ -43,7 +44,8 @@ class RoomSummaryListProcessor( suspend fun postEntries(entries: List) { updateRoomSummaries { Timber.v("Update rooms from postEntries (with ${entries.size} items) on ${Thread.currentThread()}") - addAll(entries.map(::buildSummaryForRoomListEntry)) + val roomSummaries = entries.parallelMap(::buildSummaryForRoomListEntry) + addAll(roomSummaries) } initLatch.complete(Unit) } @@ -57,7 +59,7 @@ class RoomSummaryListProcessor( } } - private fun MutableList.applyUpdate(update: RoomListEntriesUpdate) { + private suspend fun MutableList.applyUpdate(update: RoomListEntriesUpdate) { when (update) { is RoomListEntriesUpdate.Append -> { val roomSummaries = update.values.map { @@ -100,7 +102,7 @@ class RoomSummaryListProcessor( } } - private fun buildSummaryForRoomListEntry(entry: RoomListEntry): RoomSummary { + private suspend fun buildSummaryForRoomListEntry(entry: RoomListEntry): RoomSummary { return when (entry) { RoomListEntry.Empty -> buildEmptyRoomSummary() is RoomListEntry.Filled -> buildAndCacheRoomSummaryForIdentifier(entry.roomId) @@ -114,7 +116,7 @@ class RoomSummaryListProcessor( return RoomSummary.Empty(UUID.randomUUID().toString()) } - private fun buildAndCacheRoomSummaryForIdentifier(identifier: String): RoomSummary { + private suspend fun buildAndCacheRoomSummaryForIdentifier(identifier: String): RoomSummary { val builtRoomSummary = roomListService.roomOrNull(identifier)?.use { roomListItem -> roomListItem.fullRoomOrNull().use { fullRoom -> RoomSummary.Filled( @@ -134,7 +136,7 @@ class RoomSummaryListProcessor( } } - private suspend fun updateRoomSummaries(block: MutableList.() -> Unit) = + private suspend fun updateRoomSummaries(block: suspend MutableList.() -> Unit) = mutex.withLock { val mutableRoomSummaries = roomSummaries.value.toMutableList() block(mutableRoomSummaries) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/sync/AppExtension.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/sync/AppExtension.kt new file mode 100644 index 0000000000..a987081f96 --- /dev/null +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/sync/AppExtension.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.impl.sync + +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.App +import org.matrix.rustcomponents.sdk.AppState +import org.matrix.rustcomponents.sdk.AppStateObserver + +fun App.stateFlow(): Flow = + mxCallbackFlow { + val listener = object : AppStateObserver { + override fun onUpdate(state: AppState) { + trySendBlocking(state) + } + } + state(listener) + }.buffer(Channel.UNLIMITED) 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/AppStateMapper.kt similarity index 80% rename from libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/sync/RoomListStateMapper.kt rename to libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/sync/AppStateMapper.kt index 939abf2816..7db05f1efd 100644 --- 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/AppStateMapper.kt @@ -17,6 +17,7 @@ package io.element.android.libraries.matrix.impl.sync import io.element.android.libraries.matrix.api.sync.SyncState +import org.matrix.rustcomponents.sdk.AppState import org.matrix.rustcomponents.sdk.RoomListServiceState internal fun RoomListServiceState.toSyncState(): SyncState { @@ -28,3 +29,11 @@ internal fun RoomListServiceState.toSyncState(): SyncState { RoomListServiceState.TERMINATED -> SyncState.Terminated } } + +internal fun AppState.toSyncState(): SyncState { + return when (this) { + AppState.RUNNING -> SyncState.Syncing + AppState.TERMINATED -> SyncState.Terminated + AppState.ERROR -> SyncState.InError + } +} 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 c89c50ab9b..08accdbe06 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 @@ -18,47 +18,39 @@ 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.stateFlow import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.Flow 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 -import org.matrix.rustcomponents.sdk.RoomListService +import org.matrix.rustcomponents.sdk.App import org.matrix.rustcomponents.sdk.RoomListServiceState import timber.log.Timber -import java.util.concurrent.atomic.AtomicBoolean class RustSyncService( - private val roomListService: RoomListService, + private val app: App, + roomListStateFlow: Flow, sessionCoroutineScope: CoroutineScope ) : SyncService { - private val isSyncing = AtomicBoolean(false) - override fun startSync() = runCatching { - if (isSyncing.compareAndSet(false, true)) { - Timber.v("Start sync") - roomListService.sync() - } + Timber.v("Start sync") + app.start() } override fun stopSync() = runCatching { - if (isSyncing.compareAndSet(true, false)) { - Timber.v("Stop sync") - roomListService.stopSync() - } + Timber.v("Stop sync") + app.pause() } override val syncState: StateFlow = - roomListService - .stateFlow() + roomListStateFlow .map(RoomListServiceState::toSyncState) .onEach { state -> Timber.v("Sync state=$state") - isSyncing.set(state == SyncState.Syncing) } .distinctUntilChanged() .stateIn(sessionCoroutineScope, SharingStarted.Eagerly, SyncState.Idle) 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 f448873ab7..2e86aa0706 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 @@ -32,21 +32,20 @@ class MatrixTimelineItemMapper( ) { fun map(timelineItem: TimelineItem): MatrixTimelineItem = timelineItem.use { + val uniqueId = timelineItem.uniqueId().toLong() val asEvent = it.asEvent() if (asEvent != null) { val eventTimelineItem = eventTimelineItemMapper.map(asEvent) - - if (eventTimelineItem.hasNotLoadedInReplyTo() && eventTimelineItem.eventId != null) { fetchEventDetails(eventTimelineItem.eventId!!) } - return MatrixTimelineItem.Event(eventTimelineItem) + return MatrixTimelineItem.Event(uniqueId, eventTimelineItem) } val asVirtual = it.asVirtual() if (asVirtual != null) { val virtualTimelineItem = virtualTimelineItemMapper.map(asVirtual) - return MatrixTimelineItem.Virtual(virtualTimelineItem) + return MatrixTimelineItem.Virtual(uniqueId, virtualTimelineItem) } return MatrixTimelineItem.Other } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventTimelineItemMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventTimelineItemMapper.kt index d250072267..b4887faf68 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventTimelineItemMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventTimelineItemMapper.kt @@ -20,8 +20,8 @@ import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo import io.element.android.libraries.matrix.api.timeline.item.event.EventReaction -import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem +import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState import io.element.android.libraries.matrix.api.timeline.item.event.ProfileTimelineDetails import org.matrix.rustcomponents.sdk.Reaction import org.matrix.rustcomponents.sdk.EventSendState as RustEventSendState @@ -33,7 +33,6 @@ class EventTimelineItemMapper(private val contentMapper: TimelineEventContentMap fun map(eventTimelineItem: RustEventTimelineItem): EventTimelineItem = eventTimelineItem.use { EventTimelineItem( - uniqueIdentifier = it.uniqueIdentifier(), eventId = it.eventId()?.let(::EventId), transactionId = it.transactionId(), isEditable = it.isEditable(), @@ -79,7 +78,7 @@ private fun List?.map(): List { EventReaction( key = it.key, count = it.count.toLong(), - senderIds = it.senders.map { sender -> UserId(sender) } + senderIds = it.senders.map { sender -> UserId(sender.senderId) } ) } ?: emptyList() } diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/TestData.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/TestData.kt index 4cc9422eb7..0227763479 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/TestData.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/TestData.kt @@ -23,6 +23,7 @@ import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.api.core.SpaceId import io.element.android.libraries.matrix.api.core.ThreadId import io.element.android.libraries.matrix.api.core.UserId +import java.util.UUID const val A_USER_NAME = "alice" const val A_PASSWORD = "password" @@ -57,4 +58,3 @@ const val A_FAILURE_REASON = "There has been a failure" val A_THROWABLE = Throwable(A_FAILURE_REASON) val AN_EXCEPTION = Exception(A_FAILURE_REASON) - diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/RoomSummaryFixture.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/RoomSummaryFixture.kt index 7580a32e18..0a956577ca 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/RoomSummaryFixture.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/RoomSummaryFixture.kt @@ -87,7 +87,6 @@ fun aRoomMessage( ) fun anEventTimelineItem( - uniqueIdentifier: String = A_UNIQUE_ID, eventId: EventId = AN_EVENT_ID, transactionId: String? = null, isEditable: Boolean = false, @@ -102,7 +101,6 @@ fun anEventTimelineItem( content: EventContent = aProfileChangeMessageContent(), debugInfo: TimelineItemDebugInfo = aTimelineItemDebugInfo(), ) = EventTimelineItem( - uniqueIdentifier = uniqueIdentifier, eventId = eventId, transactionId = transactionId, isEditable = isEditable, diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/NotificationFactory.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/NotificationFactory.kt index 7b7b395862..b359f540f8 100755 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/NotificationFactory.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/NotificationFactory.kt @@ -232,6 +232,7 @@ class NotificationFactory @Inject constructor( .setSmallIcon(smallIcon) .setColor(accentColor) .setAutoCancel(true) + .setWhen(fallbackNotifiableEvent.timestamp) // Ideally we'd use `createOpenRoomPendingIntent` here, but the broken notification might apply to an invite // and the user won't have access to the room yet, resulting in an error screen. .setContentIntent(pendingIntentFactory.createOpenSessionPendingIntent(fallbackNotifiableEvent.sessionId))