Merge branch 'develop' into feature/fga/better_timeline_scroll

This commit is contained in:
ganfra
2023-07-13 11:24:00 +02:00
25 changed files with 124 additions and 71 deletions

View File

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

View File

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

View File

@@ -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 {
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

View File

@@ -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
}
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"),

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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<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(
}
}
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(
}
}
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<RoomSummary>.() -> Unit) =
private suspend fun updateRoomSummaries(block: suspend MutableList<RoomSummary>.() -> Unit) =
mutex.withLock {
val mutableRoomSummaries = roomSummaries.value.toMutableList()
block(mutableRoomSummaries)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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