-
Notifications
You must be signed in to change notification settings - Fork 845
added read receipts for threads #7474
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
1f8ac06
33e673c
5dd40bd
e560d7a
60e7242
0a425cf
a46d807
fbb2ca2
9cfe82c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -34,12 +34,12 @@ interface ReadService { | |
| /** | ||
| * Force the read marker to be set on the latest event. | ||
| */ | ||
| suspend fun markAsRead(params: MarkAsReadParams = MarkAsReadParams.BOTH) | ||
| suspend fun markAsRead(params: MarkAsReadParams = MarkAsReadParams.BOTH, mainTimeLineOnly: Boolean) | ||
|
|
||
| /** | ||
| * Set the read receipt on the event with provided eventId. | ||
| */ | ||
| suspend fun setReadReceipt(eventId: String) | ||
| suspend fun setReadReceipt(eventId: String, threadId: String) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe add Kdoc for parameters, especially tell about the possible value |
||
|
|
||
| /** | ||
| * Set the read marker on the event with provided eventId. | ||
|
|
@@ -62,7 +62,7 @@ interface ReadService { | |
| fun getMyReadReceiptLive(): LiveData<Optional<String>> | ||
|
|
||
| /** | ||
| * Get the eventId where the read receipt for the provided user is. | ||
| * Get the eventId from the main timeline 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 | ||
|
|
@@ -74,4 +74,8 @@ 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 |
|---|---|---|
|
|
@@ -22,6 +22,8 @@ import io.realm.RealmQuery | |
| import io.realm.Sort | ||
| import org.matrix.android.sdk.api.session.events.model.UnsignedData | ||
| import org.matrix.android.sdk.api.session.events.model.isRedacted | ||
| import org.matrix.android.sdk.api.session.room.model.ReadReceipt | ||
| import org.matrix.android.sdk.api.session.room.read.ReadService | ||
| import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent | ||
| import org.matrix.android.sdk.api.session.threads.ThreadNotificationState | ||
| import org.matrix.android.sdk.internal.database.mapper.asDomain | ||
|
|
@@ -273,8 +275,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): String? = | ||
| ReadReceiptEntity.where(realm, roomId = roomId, userId = userId) | ||
| internal fun findMyReadReceipt(realm: Realm, roomId: String, userId: String, threadId: String?): String? = | ||
| ReadReceiptEntity.where(realm, roomId = roomId, userId = userId, threadId = threadId) | ||
| .findFirst() | ||
| ?.eventId | ||
|
|
||
|
|
@@ -294,7 +296,7 @@ internal fun isUserMentioned(currentUserId: String, timelineEventEntity: Timelin | |
| * immediately so we should not display wrong notifications | ||
| */ | ||
| internal fun updateNotificationsNew(roomId: String, realm: Realm, currentUserId: String) { | ||
| val readReceipt = findMyReadReceipt(realm, roomId, currentUserId) ?: return | ||
| val readReceipt = findMyReadReceipt(realm, roomId, currentUserId, ReadService.THREAD_ID_MAIN) ?: return | ||
|
||
|
|
||
| val readReceiptChunk = ChunkEntity | ||
| .findIncludingEvent(realm, readReceipt) ?: return | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -20,12 +20,26 @@ 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> { | ||||||||||||||||
| 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)) | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| internal fun ReadReceiptEntity.Companion.where(realm: Realm, roomId: String, userId: String): RealmQuery<ReadReceiptEntity> { | ||||||||||||||||
| return realm.where<ReadReceiptEntity>() | ||||||||||||||||
| .equalTo(ReadReceiptEntityFields.PRIMARY_KEY, buildPrimaryKey(roomId, userId)) | ||||||||||||||||
| .equalTo(ReadReceiptEntityFields.USER_ID, userId) | ||||||||||||||||
|
||||||||||||||||
| .equalTo(ReadReceiptEntityFields.ROOM_ID, roomId) | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| internal fun ReadReceiptEntity.Companion.whereUserId(realm: Realm, userId: String): RealmQuery<ReadReceiptEntity> { | ||||||||||||||||
|
|
@@ -38,23 +52,25 @@ 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, originServerTs: Double): ReadReceiptEntity { | ||||||||||||||||
| internal fun ReadReceiptEntity.Companion.createUnmanaged(roomId: String, eventId: String, userId: String, threadId: String?, originServerTs: Double): ReadReceiptEntity { | ||||||||||||||||
| return ReadReceiptEntity().apply { | ||||||||||||||||
| this.primaryKey = "${roomId}_$userId" | ||||||||||||||||
| this.primaryKey = buildPrimaryKey(roomId, userId, threadId) | ||||||||||||||||
| 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): ReadReceiptEntity { | ||||||||||||||||
| return ReadReceiptEntity.where(realm, roomId, userId).findFirst() | ||||||||||||||||
| ?: realm.createObject<ReadReceiptEntity>(buildPrimaryKey(roomId, userId)) | ||||||||||||||||
| 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)) | ||||||||||||||||
| .apply { | ||||||||||||||||
| this.roomId = roomId | ||||||||||||||||
| this.userId = userId | ||||||||||||||||
| this.threadId = threadId | ||||||||||||||||
| } | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| private fun buildPrimaryKey(roomId: String, userId: String) = "${roomId}_$userId" | ||||||||||||||||
| private fun buildPrimaryKey(roomId: String, userId: String, threadId: String?) = "${roomId}_${userId}_${threadId ?: "null"}" | ||||||||||||||||
|
||||||||||||||||
| private fun buildPrimaryKey(roomId: String, userId: String, threadId: String?) = "${roomId}_${userId}_${threadId ?: "null"}" | |
| private fun buildPrimaryKey(roomId: String, userId: String, threadId: String?): String { | |
| return if (threadId == null) { | |
| "${roomId}_${userId}" | |
| } else { | |
| "${roomId}_${userId}_${threadId}" | |
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -22,6 +22,7 @@ import com.zhuinden.monarchy.Monarchy | |
| import dagger.assisted.Assisted | ||
| import dagger.assisted.AssistedFactory | ||
| import dagger.assisted.AssistedInject | ||
| import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService | ||
| import org.matrix.android.sdk.api.session.room.model.ReadReceipt | ||
| import org.matrix.android.sdk.api.session.room.read.ReadService | ||
| import org.matrix.android.sdk.api.util.Optional | ||
|
|
@@ -30,6 +31,7 @@ import org.matrix.android.sdk.internal.database.mapper.ReadReceiptsSummaryMapper | |
| 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.ReadReceiptsSummaryEntity | ||
| import org.matrix.android.sdk.internal.database.query.forMainTimelineWhere | ||
| import org.matrix.android.sdk.internal.database.query.isEventRead | ||
| import org.matrix.android.sdk.internal.database.query.where | ||
| import org.matrix.android.sdk.internal.di.SessionDatabase | ||
|
|
@@ -40,25 +42,37 @@ internal class DefaultReadService @AssistedInject constructor( | |
| @SessionDatabase private val monarchy: Monarchy, | ||
| private val setReadMarkersTask: SetReadMarkersTask, | ||
| private val readReceiptsSummaryMapper: ReadReceiptsSummaryMapper, | ||
| @UserId private val userId: String | ||
| @UserId private val userId: String, | ||
| private val homeServerCapabilitiesService: HomeServerCapabilitiesService, | ||
|
||
| ) : ReadService { | ||
|
|
||
| @AssistedFactory | ||
| interface Factory { | ||
| fun create(roomId: String): DefaultReadService | ||
| } | ||
|
|
||
| override suspend fun markAsRead(params: ReadService.MarkAsReadParams) { | ||
| override suspend fun markAsRead(params: ReadService.MarkAsReadParams, mainTimeLineOnly: Boolean) { | ||
| val readReceiptThreadId = if (homeServerCapabilitiesService.getHomeServerCapabilities().canUseThreadReadReceiptsAndNotifications) { | ||
| if (mainTimeLineOnly) ReadService.THREAD_ID_MAIN else null | ||
| } else { | ||
| null | ||
| } | ||
| val taskParams = SetReadMarkersTask.Params( | ||
| roomId = roomId, | ||
| forceReadMarker = params.forceReadMarker(), | ||
| forceReadReceipt = params.forceReadReceipt() | ||
| forceReadReceipt = params.forceReadReceipt(), | ||
| readReceiptThreadId = readReceiptThreadId | ||
| ) | ||
| setReadMarkersTask.execute(taskParams) | ||
| } | ||
|
|
||
| override suspend fun setReadReceipt(eventId: String) { | ||
| val params = SetReadMarkersTask.Params(roomId, fullyReadEventId = null, readReceiptEventId = eventId) | ||
| override suspend fun setReadReceipt(eventId: String, threadId: String) { | ||
| val readReceiptThreadId = if (homeServerCapabilitiesService.getHomeServerCapabilities().canUseThreadReadReceiptsAndNotifications) { | ||
| threadId | ||
| } else { | ||
| null | ||
| } | ||
| val params = SetReadMarkersTask.Params(roomId, fullyReadEventId = null, readReceiptEventId = eventId, readReceiptThreadId = readReceiptThreadId) | ||
| setReadMarkersTask.execute(params) | ||
| } | ||
|
|
||
|
|
@@ -68,7 +82,8 @@ internal class DefaultReadService @AssistedInject constructor( | |
| } | ||
|
|
||
| override fun isEventRead(eventId: String): Boolean { | ||
| return isEventRead(monarchy.realmConfiguration, userId, roomId, eventId) | ||
| val shouldCheckIfReadInEventsThread = homeServerCapabilitiesService.getHomeServerCapabilities().canUseThreadReadReceiptsAndNotifications | ||
| return isEventRead(monarchy.realmConfiguration, userId, roomId, eventId, shouldCheckIfReadInEventsThread) | ||
| } | ||
|
|
||
| override fun getReadMarkerLive(): LiveData<Optional<String>> { | ||
|
|
@@ -94,10 +109,11 @@ internal class DefaultReadService @AssistedInject constructor( | |
| override fun getUserReadReceipt(userId: String): String? { | ||
| var eventId: String? = null | ||
| monarchy.doWithRealm { | ||
| eventId = ReadReceiptEntity.where(it, roomId = roomId, userId = userId) | ||
| eventId = ReadReceiptEntity.forMainTimelineWhere(it, roomId = roomId, userId = userId) | ||
| .findFirst() | ||
| ?.eventId | ||
| } | ||
|
|
||
| return eventId | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| /* | ||
| * Copyright 2020 The Matrix.org Foundation C.I.C. | ||
| * | ||
| * 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 org.matrix.android.sdk.internal.session.room.read | ||
|
|
||
| import com.squareup.moshi.Json | ||
| import com.squareup.moshi.JsonClass | ||
|
|
||
| @JsonClass(generateAdapter = true) | ||
| internal data class ReadBody( | ||
| @Json(name = "thread_id") val threadId: String?, | ||
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe add default value to the new parameters to avoid API breaks.
Also can you document API change in a .sdk file for towncrier? Thanks.