Fix marking a room as read re-instantiates its timeline (#5628)

* Add `Timeline.markAsRead` to avoid reinstantiating the timeline using `Room.markAsRead`

* Mark as read when exiting the room screen, destroy the timeline when fully closed

* Ensure `MarkAsFullyReadAndExit` event can only be processed once

* Fix `DelayedVisibility` not being displayed in previews
This commit is contained in:
Jorge Martin Espinosa 2025-10-30 08:39:06 +01:00 committed by GitHub
parent bb61126c96
commit 6c3b280ecd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 281 additions and 89 deletions

View file

@ -9,6 +9,7 @@ package io.element.android.libraries.matrix.api
import io.element.android.libraries.core.data.tryOrNull
import io.element.android.libraries.matrix.api.core.DeviceId
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.MatrixPatterns
import io.element.android.libraries.matrix.api.core.RoomAlias
import io.element.android.libraries.matrix.api.core.RoomId
@ -34,6 +35,7 @@ import io.element.android.libraries.matrix.api.roomlist.RoomListService
import io.element.android.libraries.matrix.api.spaces.SpaceService
import io.element.android.libraries.matrix.api.sync.SlidingSyncVersion
import io.element.android.libraries.matrix.api.sync.SyncService
import io.element.android.libraries.matrix.api.timeline.Timeline
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
@ -183,6 +185,14 @@ interface MatrixClient {
* Adds an emoji to the list of recent emoji reactions for this account.
*/
suspend fun addRecentEmoji(emoji: String): Result<Unit>
/**
* Marks the room with the provided [roomId] as read, sending a fully read receipt for [eventId].
*
* This method should be used with caution as providing the [eventId] ourselves can result in incorrect read receipts.
* Use [Timeline.markAsRead] instead when possible.
*/
suspend fun markRoomAsFullyRead(roomId: RoomId, eventId: EventId): Result<Unit>
}
/**

View file

@ -16,7 +16,7 @@ sealed class QrLoginException : Exception() {
data object OidcMetadataInvalid : QrLoginException()
data object SlidingSyncNotAvailable : QrLoginException()
data object OtherDeviceNotSignedIn : QrLoginException()
data object Unknown : QrLoginException()
data object CheckCodeAlreadySent : QrLoginException()
data object CheckCodeCannotBeSent : QrLoginException()
data object Unknown : QrLoginException()
}

View file

@ -17,6 +17,7 @@ import io.element.android.libraries.matrix.api.room.powerlevels.RoomPowerLevelsV
import io.element.android.libraries.matrix.api.room.tombstone.PredecessorRoom
import io.element.android.libraries.matrix.api.roomdirectory.RoomVisibility
import io.element.android.libraries.matrix.api.timeline.ReceiptType
import io.element.android.libraries.matrix.api.timeline.Timeline
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
@ -180,6 +181,10 @@ interface BaseRoom : Closeable {
/**
* Mark the room as read by trying to attach an unthreaded read receipt to the latest room event.
*
* Note this will instantiate a new timeline, which is an expensive operation.
* Prefer using [Timeline.markAsRead] instead when possible.
*
* @param receiptType The type of receipt to send.
*/
suspend fun markAsRead(receiptType: ReceiptType): Result<Unit>

View file

@ -55,6 +55,7 @@ interface Timeline : AutoCloseable {
val mode: Mode
val membershipChangeEventReceived: Flow<Unit>
suspend fun sendReadReceipt(eventId: EventId, receiptType: ReceiptType): Result<Unit>
suspend fun markAsRead(receiptType: ReceiptType): Result<Unit>
suspend fun paginate(direction: PaginationDirection): Result<Boolean>
val backwardPaginationStatus: StateFlow<PaginationStatus>
@ -227,4 +228,9 @@ interface Timeline : AutoCloseable {
* pinned
*/
suspend fun unpinEvent(eventId: EventId): Result<Boolean>
/**
* Get the latest event id of the timeline.
*/
suspend fun getLatestEventId(): Result<EventId?>
}