Browse Source

Merge branch 'develop' into feature/fga/better_timeline_scroll

pull/868/head
ganfra 1 year ago
parent
commit
ca293d4f52
  1. 8
      .github/workflows/nightlyReports.yml
  2. 11
      .github/workflows/tests.yml
  3. 13
      build.gradle.kts
  4. 5
      features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineStateProvider.kt
  5. 2
      features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/TimelineItemsFactory.kt
  6. 2
      features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemEventFactory.kt
  7. 3
      features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/virtual/TimelineItemVirtualFactory.kt
  8. 4
      features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItem.kt
  9. 6
      features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/TimelinePresenterTest.kt
  10. 2
      gradle/libs.versions.toml
  11. 5
      libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/MatrixTimelineItem.kt
  12. 1
      libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventTimelineItem.kt
  13. 17
      libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt
  14. 4
      libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notification/NotificationMapper.kt
  15. 10
      libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notification/RustNotificationService.kt
  16. 2
      libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomSummaryDetailsFactory.kt
  17. 12
      libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomSummaryListProcessor.kt
  18. 36
      libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/sync/AppExtension.kt
  19. 9
      libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/sync/AppStateMapper.kt
  20. 26
      libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/sync/RustSyncService.kt
  21. 7
      libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/MatrixTimelineItemMapper.kt
  22. 5
      libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventTimelineItemMapper.kt
  23. 2
      libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/TestData.kt
  24. 2
      libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/RoomSummaryFixture.kt
  25. 1
      libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/NotificationFactory.kt

8
.github/workflows/nightlyReports.yml

@ -26,11 +26,11 @@ jobs: @@ -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()

11
.github/workflows/tests.yml

@ -37,14 +37,11 @@ jobs: @@ -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()

13
build.gradle.kts

@ -135,6 +135,15 @@ allprojects { @@ -135,6 +135,15 @@ allprojects {
allprojects {
tasks.withType<Test> {
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 { @@ -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

5
features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineStateProvider.kt

@ -36,6 +36,7 @@ import kotlinx.collections.immutable.ImmutableList @@ -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<TimelineItem> = persistentListOf()) = TimelineState(
@ -96,7 +97,7 @@ internal fun aTimelineItemList(content: TimelineItemEventContent): ImmutableList @@ -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( @@ -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"),

2
features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/TimelineItemsFactory.kt

@ -106,7 +106,7 @@ class TimelineItemsFactory @Inject constructor( @@ -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

2
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( @@ -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,

3
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( @@ -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()
)
}

4
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 @@ -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 { @@ -83,6 +83,6 @@ sealed interface TimelineItem {
val events: ImmutableList<Event>,
) : 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"
}
}

6
features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/TimelinePresenterTest.kt

@ -96,7 +96,7 @@ class TimelinePresenterTest { @@ -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 { @@ -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 { @@ -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(

2
gradle/libs.versions.toml

@ -145,7 +145,7 @@ jsoup = { module = "org.jsoup:jsoup", version.ref = "jsoup" } @@ -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" }

5
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 @@ -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
}

1
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 @@ -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,

17
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 @@ -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( @@ -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( @@ -249,7 +256,9 @@ class RustMatrixClient constructor(
sessionCoroutineScope.cancel()
client.setDelegate(null)
verificationService.destroy()
app.destroy()
roomListService.destroy()
notificationClient.destroy()
client.destroy()
}

4
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 @@ -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 },

10
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 @@ -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( @@ -36,8 +36,10 @@ class RustNotificationService(
filterByPushRules: Boolean,
): Result<NotificationData?> {
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)
}
}
}
}

2
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 @@ -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)
}

12
libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomSummaryListProcessor.kt

@ -16,6 +16,7 @@ @@ -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( @@ -43,7 +44,8 @@ class RoomSummaryListProcessor(
suspend fun postEntries(entries: List<RoomListEntry>) {
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( @@ -57,7 +59,7 @@ class RoomSummaryListProcessor(
}
}
private fun MutableList<RoomSummary>.applyUpdate(update: RoomListEntriesUpdate) {
private suspend fun MutableList<RoomSummary>.applyUpdate(update: RoomListEntriesUpdate) {
when (update) {
is RoomListEntriesUpdate.Append -> {
val roomSummaries = update.values.map {
@ -100,7 +102,7 @@ class RoomSummaryListProcessor( @@ -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( @@ -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( @@ -134,7 +136,7 @@ class RoomSummaryListProcessor(
}
}
private suspend fun updateRoomSummaries(block: MutableList<RoomSummary>.() -> Unit) =
private suspend fun updateRoomSummaries(block: suspend MutableList<RoomSummary>.() -> Unit) =
mutex.withLock {
val mutableRoomSummaries = roomSummaries.value.toMutableList()
block(mutableRoomSummaries)

36
libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/sync/AppExtension.kt

@ -0,0 +1,36 @@ @@ -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<AppState> =
mxCallbackFlow {
val listener = object : AppStateObserver {
override fun onUpdate(state: AppState) {
trySendBlocking(state)
}
}
state(listener)
}.buffer(Channel.UNLIMITED)

9
libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/sync/RoomListStateMapper.kt → libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/sync/AppStateMapper.kt

@ -17,6 +17,7 @@ @@ -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 { @@ -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
}
}

26
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 @@ -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<RoomListServiceState>,
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<SyncState> =
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)

7
libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/MatrixTimelineItemMapper.kt

@ -32,21 +32,20 @@ class MatrixTimelineItemMapper( @@ -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
}

5
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 @@ -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 @@ -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<Reaction>?.map(): List<EventReaction> { @@ -79,7 +78,7 @@ private fun List<Reaction>?.map(): List<EventReaction> {
EventReaction(
key = it.key,
count = it.count.toLong(),
senderIds = it.senders.map { sender -> UserId(sender) }
senderIds = it.senders.map { sender -> UserId(sender.senderId) }
)
} ?: emptyList()
}

2
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 @@ -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" @@ -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)

2
libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/RoomSummaryFixture.kt

@ -87,7 +87,6 @@ fun aRoomMessage( @@ -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( @@ -102,7 +101,6 @@ fun anEventTimelineItem(
content: EventContent = aProfileChangeMessageContent(),
debugInfo: TimelineItemDebugInfo = aTimelineItemDebugInfo(),
) = EventTimelineItem(
uniqueIdentifier = uniqueIdentifier,
eventId = eventId,
transactionId = transactionId,
isEditable = isEditable,

1
libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/NotificationFactory.kt

@ -232,6 +232,7 @@ class NotificationFactory @Inject constructor( @@ -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))

Loading…
Cancel
Save