Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion changelog.d/6996.sdk

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,8 @@ class FlowRoom(private val room: Room) {
return room.readService().getReadMarkerLive().asFlow()
}

fun liveReadReceipt(threadId: String?): Flow<Optional<String>> {
return room.readService().getMyReadReceiptLive(threadId).asFlow()
fun liveReadReceipt(): Flow<Optional<String>> {
return room.readService().getMyReadReceiptLive().asFlow()
}

fun liveEventReadReceipts(eventId: String): Flow<List<ReadReceipt>> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,5 @@ package org.matrix.android.sdk.api.session.room.model

data class ReadReceipt(
val roomMember: RoomMemberSummary,
val originServerTs: Long,
val threadId: String?
val originServerTs: Long
)
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,12 @@ interface ReadService {
/**
* Force the read marker to be set on the latest event.
*/
suspend fun markAsRead(params: MarkAsReadParams = MarkAsReadParams.BOTH, mainTimeLineOnly: Boolean = true)
suspend fun markAsRead(params: MarkAsReadParams = MarkAsReadParams.BOTH)

/**
* Set the read receipt on the event with provided eventId.
* @param eventId the id of the event where read receipt will be set
* @param threadId the id of the thread in which read receipt will be set. For main thread use [ReadService.THREAD_ID_MAIN] constant
*/
suspend fun setReadReceipt(eventId: String, threadId: String)
suspend fun setReadReceipt(eventId: String)

/**
* Set the read marker on the event with provided eventId.
Expand All @@ -61,10 +59,10 @@ interface ReadService {
/**
* Returns a live read receipt id for the room.
*/
fun getMyReadReceiptLive(threadId: String?): LiveData<Optional<String>>
fun getMyReadReceiptLive(): LiveData<Optional<String>>

/**
* Get the eventId from the main timeline where the read receipt for the provided user is.
* Get the eventId where the read receipt for the provided user is.
* @param userId the id of the user to look for
*
* @return the eventId where the read receipt for the provided user is attached, or null if not found
Expand All @@ -76,8 +74,4 @@ interface ReadService {
* @param eventId the event
*/
fun getEventReadReceiptsLive(eventId: String): LiveData<List<ReadReceipt>>

companion object {
const val THREAD_ID_MAIN = "main"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ private fun handleReadReceipts(realm: Realm, roomId: String, eventEntity: EventE
val originServerTs = eventEntity.originServerTs
if (originServerTs != null) {
val timestampOfEvent = originServerTs.toDouble()
val readReceiptOfSender = ReadReceiptEntity.getOrCreate(realm, roomId = roomId, userId = senderId, threadId = eventEntity.rootThreadEventId)
val readReceiptOfSender = ReadReceiptEntity.getOrCreate(realm, roomId = roomId, userId = senderId)
// If the synced RR is older, update
if (timestampOfEvent > readReceiptOfSender.originServerTs) {
val previousReceiptsSummary = ReadReceiptsSummaryEntity.where(realm, eventId = readReceiptOfSender.eventId).findFirst()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,12 +65,12 @@ internal fun Map<String, EventEntity>.updateThreadSummaryIfNeeded(
inThreadMessages = inThreadMessages,
latestMessageTimelineEventEntity = latestEventInThread
)

if (shouldUpdateNotifications) {
updateThreadNotifications(roomId, realm, currentUserId, rootThreadEventId)
}
}
}

if (shouldUpdateNotifications) {
updateNotificationsNew(roomId, realm, currentUserId)
}
}

/**
Expand Down Expand Up @@ -273,8 +273,8 @@ internal fun TimelineEventEntity.Companion.isUserMentionedInThread(realm: Realm,
/**
* Find the read receipt for the current user.
*/
internal fun findMyReadReceipt(realm: Realm, roomId: String, userId: String, threadId: String?): String? =
ReadReceiptEntity.where(realm, roomId = roomId, userId = userId, threadId = threadId)
internal fun findMyReadReceipt(realm: Realm, roomId: String, userId: String): String? =
ReadReceiptEntity.where(realm, roomId = roomId, userId = userId)
.findFirst()
?.eventId

Expand All @@ -293,29 +293,28 @@ internal fun isUserMentioned(currentUserId: String, timelineEventEntity: Timelin
* Important: It will work only with the latest chunk, while read marker will be changed
* immediately so we should not display wrong notifications
*/
internal fun updateThreadNotifications(roomId: String, realm: Realm, currentUserId: String, rootThreadEventId: String) {
val readReceipt = findMyReadReceipt(realm, roomId, currentUserId, threadId = rootThreadEventId) ?: return
internal fun updateNotificationsNew(roomId: String, realm: Realm, currentUserId: String) {
val readReceipt = findMyReadReceipt(realm, roomId, currentUserId) ?: return

val readReceiptChunk = ChunkEntity
.findIncludingEvent(realm, readReceipt) ?: return

val readReceiptChunkThreadEvents = readReceiptChunk
val readReceiptChunkTimelineEvents = readReceiptChunk
.timelineEvents
.where()
.equalTo(TimelineEventEntityFields.ROOM_ID, roomId)
.equalTo(TimelineEventEntityFields.ROOT.ROOT_THREAD_EVENT_ID, rootThreadEventId)
.sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.ASCENDING)
.findAll() ?: return

val readReceiptChunkPosition = readReceiptChunkThreadEvents.indexOfFirst { it.eventId == readReceipt }
val readReceiptChunkPosition = readReceiptChunkTimelineEvents.indexOfFirst { it.eventId == readReceipt }

if (readReceiptChunkPosition == -1) return

if (readReceiptChunkPosition < readReceiptChunkThreadEvents.lastIndex) {
if (readReceiptChunkPosition < readReceiptChunkTimelineEvents.lastIndex) {
// If the read receipt is found inside the chunk

val threadEventsAfterReadReceipt = readReceiptChunkThreadEvents
.slice(readReceiptChunkPosition..readReceiptChunkThreadEvents.lastIndex)
val threadEventsAfterReadReceipt = readReceiptChunkTimelineEvents
.slice(readReceiptChunkPosition..readReceiptChunkTimelineEvents.lastIndex)
.filter { it.root?.isThread() == true }

// In order for the below code to work for old events, we should save the previous read receipt
Expand Down Expand Up @@ -344,21 +343,26 @@ internal fun updateThreadNotifications(roomId: String, realm: Realm, currentUser
it.root?.rootThreadEventId
}

// Update root thread event only if the user have participated in
val isUserParticipating = TimelineEventEntity.isUserParticipatingInThread(
realm = realm,
roomId = roomId,
rootThreadEventId = rootThreadEventId,
senderId = currentUserId
)
val rootThreadEventEntity = EventEntity.where(realm, rootThreadEventId).findFirst()

if (isUserParticipating) {
rootThreadEventEntity?.threadNotificationState = ThreadNotificationState.NEW_MESSAGE
}
// Find the root events in the new thread events
val rootThreads = threadEventsAfterReadReceipt.distinctBy { it.root?.rootThreadEventId }.mapNotNull { it.root?.rootThreadEventId }

if (userMentionsList.contains(rootThreadEventId)) {
rootThreadEventEntity?.threadNotificationState = ThreadNotificationState.NEW_HIGHLIGHTED_MESSAGE
// Update root thread events only if the user have participated in
rootThreads.forEach { eventId ->
val isUserParticipating = TimelineEventEntity.isUserParticipatingInThread(
realm = realm,
roomId = roomId,
rootThreadEventId = eventId,
senderId = currentUserId
)
val rootThreadEventEntity = EventEntity.where(realm, eventId).findFirst()

if (isUserParticipating) {
rootThreadEventEntity?.threadNotificationState = ThreadNotificationState.NEW_MESSAGE
}

if (userMentionsList.contains(eventId)) {
rootThreadEventEntity?.threadNotificationState = ThreadNotificationState.NEW_HIGHLIGHTED_MESSAGE
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ internal class ReadReceiptsSummaryMapper @Inject constructor(
.mapNotNull {
val roomMember = RoomMemberSummaryEntity.where(realm, roomId = it.roomId, userId = it.userId).findFirst()
?: return@mapNotNull null
ReadReceipt(roomMember.asDomain(), it.originServerTs.toLong(), it.threadId)
ReadReceipt(roomMember.asDomain(), it.originServerTs.toLong())
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ internal open class ReadReceiptEntity(
var eventId: String = "",
var roomId: String = "",
var userId: String = "",
var threadId: String? = null,
var originServerTs: Double = 0.0
) : RealmObject() {
companion object
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import io.realm.RealmObject
import io.realm.RealmResults
import io.realm.annotations.Index
import io.realm.annotations.LinkingObjects
import org.matrix.android.sdk.api.session.room.read.ReadService
import org.matrix.android.sdk.internal.extensions.assertIsManaged

internal open class TimelineEventEntity(
Expand Down Expand Up @@ -53,7 +52,3 @@ internal fun TimelineEventEntity.deleteOnCascade(canDeleteRoot: Boolean) {
}
deleteFromRealm()
}

internal fun TimelineEventEntity.getThreadId(): String {
return root?.rootThreadEventId ?: ReadService.THREAD_ID_MAIN
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,17 @@ package org.matrix.android.sdk.internal.database.query
import io.realm.Realm
import io.realm.RealmConfiguration
import org.matrix.android.sdk.api.session.events.model.LocalEcho
import org.matrix.android.sdk.api.session.room.read.ReadService
import org.matrix.android.sdk.internal.database.helper.isMoreRecentThan
import org.matrix.android.sdk.internal.database.model.ChunkEntity
import org.matrix.android.sdk.internal.database.model.ReadMarkerEntity
import org.matrix.android.sdk.internal.database.model.ReadReceiptEntity
import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
import org.matrix.android.sdk.internal.database.model.getThreadId

internal fun isEventRead(
realmConfiguration: RealmConfiguration,
userId: String?,
roomId: String?,
eventId: String?,
shouldCheckIfReadInEventsThread: Boolean
eventId: String?
): Boolean {
if (userId.isNullOrBlank() || roomId.isNullOrBlank() || eventId.isNullOrBlank()) {
return false
Expand All @@ -48,8 +45,7 @@ internal fun isEventRead(
eventToCheck.root?.sender == userId -> true
// If new event exists and the latest event is from ourselves we can infer the event is read
latestEventIsFromSelf(realm, roomId, userId) -> true
eventToCheck.isBeforeLatestReadReceipt(realm, roomId, userId, null) -> true
(shouldCheckIfReadInEventsThread && eventToCheck.isBeforeLatestReadReceipt(realm, roomId, userId, eventToCheck.getThreadId())) -> true
eventToCheck.isBeforeLatestReadReceipt(realm, roomId, userId) -> true
else -> false
}
}
Expand All @@ -58,33 +54,27 @@ internal fun isEventRead(
private fun latestEventIsFromSelf(realm: Realm, roomId: String, userId: String) = TimelineEventEntity.latestEvent(realm, roomId, true)
?.root?.sender == userId

private fun TimelineEventEntity.isBeforeLatestReadReceipt(realm: Realm, roomId: String, userId: String, threadId: String?): Boolean {
val isMoreRecent = ReadReceiptEntity.where(realm, roomId, userId, threadId).findFirst()?.let { readReceipt ->
private fun TimelineEventEntity.isBeforeLatestReadReceipt(realm: Realm, roomId: String, userId: String): Boolean {
return ReadReceiptEntity.where(realm, roomId, userId).findFirst()?.let { readReceipt ->
val readReceiptEvent = TimelineEventEntity.where(realm, roomId, readReceipt.eventId).findFirst()
readReceiptEvent?.isMoreRecentThan(this)
} ?: false
return isMoreRecent
}

/**
* Missing events can be caused by the latest timeline chunk no longer contain an older event or
* by fast lane eagerly displaying events before the database has finished updating.
*/
private fun hasReadMissingEvent(realm: Realm,
latestChunkEntity: ChunkEntity,
roomId: String,
userId: String,
eventId: String,
threadId: String? = ReadService.THREAD_ID_MAIN): Boolean {
return realm.doesEventExistInChunkHistory(eventId) && realm.hasReadReceiptInLatestChunk(latestChunkEntity, roomId, userId, threadId)
private fun hasReadMissingEvent(realm: Realm, latestChunkEntity: ChunkEntity, roomId: String, userId: String, eventId: String): Boolean {
return realm.doesEventExistInChunkHistory(eventId) && realm.hasReadReceiptInLatestChunk(latestChunkEntity, roomId, userId)
}

private fun Realm.doesEventExistInChunkHistory(eventId: String): Boolean {
return ChunkEntity.findIncludingEvent(this, eventId) != null
}

private fun Realm.hasReadReceiptInLatestChunk(latestChunkEntity: ChunkEntity, roomId: String, userId: String, threadId: String?): Boolean {
return ReadReceiptEntity.where(this, roomId = roomId, userId = userId, threadId = threadId).findFirst()?.let {
private fun Realm.hasReadReceiptInLatestChunk(latestChunkEntity: ChunkEntity, roomId: String, userId: String): Boolean {
return ReadReceiptEntity.where(this, roomId = roomId, userId = userId).findFirst()?.let {
latestChunkEntity.timelineEvents.find(it.eventId)
} != null
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,12 @@ import io.realm.Realm
import io.realm.RealmQuery
import io.realm.kotlin.createObject
import io.realm.kotlin.where
import org.matrix.android.sdk.api.session.room.read.ReadService
import org.matrix.android.sdk.internal.database.model.ReadReceiptEntity
import org.matrix.android.sdk.internal.database.model.ReadReceiptEntityFields

internal fun ReadReceiptEntity.Companion.where(realm: Realm, roomId: String, userId: String, threadId: String?): RealmQuery<ReadReceiptEntity> {
internal fun ReadReceiptEntity.Companion.where(realm: Realm, roomId: String, userId: String): RealmQuery<ReadReceiptEntity> {
return realm.where<ReadReceiptEntity>()
.equalTo(ReadReceiptEntityFields.PRIMARY_KEY, buildPrimaryKey(roomId, userId, threadId))
}

internal fun ReadReceiptEntity.Companion.forMainTimelineWhere(realm: Realm, roomId: String, userId: String): RealmQuery<ReadReceiptEntity> {
return realm.where<ReadReceiptEntity>()
.equalTo(ReadReceiptEntityFields.PRIMARY_KEY, buildPrimaryKey(roomId, userId, ReadService.THREAD_ID_MAIN))
.or()
.equalTo(ReadReceiptEntityFields.PRIMARY_KEY, buildPrimaryKey(roomId, userId, null))
.equalTo(ReadReceiptEntityFields.PRIMARY_KEY, buildPrimaryKey(roomId, userId))
}

internal fun ReadReceiptEntity.Companion.whereUserId(realm: Realm, userId: String): RealmQuery<ReadReceiptEntity> {
Expand All @@ -46,37 +38,23 @@ internal fun ReadReceiptEntity.Companion.whereRoomId(realm: Realm, roomId: Strin
.equalTo(ReadReceiptEntityFields.ROOM_ID, roomId)
}

internal fun ReadReceiptEntity.Companion.createUnmanaged(
roomId: String,
eventId: String,
userId: String,
threadId: String?,
originServerTs: Double
): ReadReceiptEntity {
internal fun ReadReceiptEntity.Companion.createUnmanaged(roomId: String, eventId: String, userId: String, originServerTs: Double): ReadReceiptEntity {
return ReadReceiptEntity().apply {
this.primaryKey = buildPrimaryKey(roomId, userId, threadId)
this.primaryKey = "${roomId}_$userId"
this.eventId = eventId
this.roomId = roomId
this.userId = userId
this.threadId = threadId
this.originServerTs = originServerTs
}
}

internal fun ReadReceiptEntity.Companion.getOrCreate(realm: Realm, roomId: String, userId: String, threadId: String?): ReadReceiptEntity {
return ReadReceiptEntity.where(realm, roomId, userId, threadId).findFirst()
?: realm.createObject<ReadReceiptEntity>(buildPrimaryKey(roomId, userId, threadId))
internal fun ReadReceiptEntity.Companion.getOrCreate(realm: Realm, roomId: String, userId: String): ReadReceiptEntity {
return ReadReceiptEntity.where(realm, roomId, userId).findFirst()
?: realm.createObject<ReadReceiptEntity>(buildPrimaryKey(roomId, userId))
.apply {
this.roomId = roomId
this.userId = userId
this.threadId = threadId
}
}

private fun buildPrimaryKey(roomId: String, userId: String, threadId: String?): String {
return if (threadId == null) {
"${roomId}_${userId}"
} else {
"${roomId}_${userId}_${threadId}"
}
}
private fun buildPrimaryKey(roomId: String, userId: String) = "${roomId}_$userId"
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ import org.matrix.android.sdk.internal.session.room.membership.RoomMembersRespon
import org.matrix.android.sdk.internal.session.room.membership.admin.UserIdAndReason
import org.matrix.android.sdk.internal.session.room.membership.joining.InviteBody
import org.matrix.android.sdk.internal.session.room.membership.threepid.ThreePidInviteBody
import org.matrix.android.sdk.internal.session.room.read.ReadBody
import org.matrix.android.sdk.internal.session.room.relation.RelationsResponse
import org.matrix.android.sdk.internal.session.room.reporting.ReportContentBody
import org.matrix.android.sdk.internal.session.room.send.SendResponse
Expand Down Expand Up @@ -174,7 +173,7 @@ internal interface RoomAPI {
@Path("roomId") roomId: String,
@Path("receiptType") receiptType: String,
@Path("eventId") eventId: String,
@Body body: ReadBody
@Body body: JsonDict = emptyMap()
)

/**
Expand Down
Loading