Read: allow setting read marker and read receipt to latest known event independently

This commit is contained in:
ganfra 2020-01-22 14:43:39 +01:00
parent d93050240a
commit 76065ac4fc
8 changed files with 64 additions and 33 deletions

View File

@ -26,10 +26,16 @@ import im.vector.matrix.android.api.util.Optional
*/ */
interface ReadService { interface ReadService {
enum class MarkAsReadParams{
READ_RECEIPT,
READ_MARKER,
BOTH
}
/** /**
* Force the read marker to be set on the latest event. * Force the read marker to be set on the latest event.
*/ */
fun markAllAsRead(callback: MatrixCallback<Unit>) fun markAsRead(params: MarkAsReadParams = MarkAsReadParams.BOTH, callback: MatrixCallback<Unit>)
/** /**
* Set the read receipt on the event with provided eventId. * Set the read receipt on the event with provided eventId.

View File

@ -88,7 +88,7 @@ internal class DefaultCreateRoomTask @Inject constructor(
} }
private suspend fun setReadMarkers(roomId: String) { private suspend fun setReadMarkers(roomId: String) {
val setReadMarkerParams = SetReadMarkersTask.Params(roomId, markAllAsRead = true) val setReadMarkerParams = SetReadMarkersTask.Params(roomId, forceReadReceipt = true, forceReadMarker = true)
return readMarkersTask.execute(setReadMarkerParams) return readMarkersTask.execute(setReadMarkerParams)
} }
} }

View File

@ -64,7 +64,7 @@ internal class DefaultJoinRoomTask @Inject constructor(
} }
private suspend fun setReadMarkers(roomId: String) { private suspend fun setReadMarkers(roomId: String) {
val setReadMarkerParams = SetReadMarkersTask.Params(roomId, markAllAsRead = true) val setReadMarkerParams = SetReadMarkersTask.Params(roomId, forceReadMarker = true, forceReadReceipt = true)
readMarkersTask.execute(setReadMarkerParams) readMarkersTask.execute(setReadMarkerParams)
} }
} }

View File

@ -50,10 +50,14 @@ internal class DefaultReadService @AssistedInject constructor(
fun create(roomId: String): ReadService fun create(roomId: String): ReadService
} }
override fun markAllAsRead(callback: MatrixCallback<Unit>) { override fun markAsRead(params: ReadService.MarkAsReadParams, callback: MatrixCallback<Unit>) {
val params = SetReadMarkersTask.Params(roomId, markAllAsRead = true) val taskParams = SetReadMarkersTask.Params(
roomId = roomId,
forceReadMarker = params.forceReadMarker(),
forceReadReceipt = params.forceReadReceipt()
)
setReadMarkersTask setReadMarkersTask
.configureWith(params) { .configureWith(taskParams) {
this.callback = callback this.callback = callback
} }
.executeBy(taskExecutor) .executeBy(taskExecutor)
@ -110,4 +114,13 @@ internal class DefaultReadService @AssistedInject constructor(
it.firstOrNull() ?: emptyList() it.firstOrNull() ?: emptyList()
} }
} }
private fun ReadService.MarkAsReadParams.forceReadMarker(): Boolean {
return this == ReadService.MarkAsReadParams.READ_MARKER || this == ReadService.MarkAsReadParams.BOTH
}
private fun ReadService.MarkAsReadParams.forceReadReceipt(): Boolean {
return this == ReadService.MarkAsReadParams.READ_RECEIPT || this == ReadService.MarkAsReadParams.BOTH
}
} }

View File

@ -29,7 +29,7 @@ internal class DefaultMarkAllRoomsReadTask @Inject constructor(private val readM
override suspend fun execute(params: MarkAllRoomsReadTask.Params) { override suspend fun execute(params: MarkAllRoomsReadTask.Params) {
params.roomIds.forEach { roomId -> params.roomIds.forEach { roomId ->
readMarkersTask.execute(SetReadMarkersTask.Params(roomId, markAllAsRead = true)) readMarkersTask.execute(SetReadMarkersTask.Params(roomId, forceReadMarker = true, forceReadReceipt = true))
} }
} }
} }

View File

@ -25,6 +25,7 @@ import im.vector.matrix.android.internal.database.query.isReadMarkerMoreRecent
import im.vector.matrix.android.internal.database.query.latestEvent import im.vector.matrix.android.internal.database.query.latestEvent
import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.di.UserId import im.vector.matrix.android.internal.di.UserId
import im.vector.matrix.android.internal.network.NetworkConnectivityChecker
import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.room.RoomAPI import im.vector.matrix.android.internal.session.room.RoomAPI
import im.vector.matrix.android.internal.session.sync.ReadReceiptHandler import im.vector.matrix.android.internal.session.sync.ReadReceiptHandler
@ -35,16 +36,16 @@ import io.realm.Realm
import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.EventBus
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
import kotlin.collections.HashMap
import kotlin.collections.set import kotlin.collections.set
internal interface SetReadMarkersTask : Task<SetReadMarkersTask.Params, Unit> { internal interface SetReadMarkersTask : Task<SetReadMarkersTask.Params, Unit> {
data class Params( data class Params(
val roomId: String, val roomId: String,
val markAllAsRead: Boolean = false,
val fullyReadEventId: String? = null, val fullyReadEventId: String? = null,
val readReceiptEventId: String? = null val readReceiptEventId: String? = null,
val forceReadReceipt: Boolean = false,
val forceReadMarker: Boolean = false
) )
} }
@ -57,22 +58,24 @@ internal class DefaultSetReadMarkersTask @Inject constructor(
private val roomFullyReadHandler: RoomFullyReadHandler, private val roomFullyReadHandler: RoomFullyReadHandler,
private val readReceiptHandler: ReadReceiptHandler, private val readReceiptHandler: ReadReceiptHandler,
@UserId private val userId: String, @UserId private val userId: String,
private val eventBus: EventBus private val eventBus: EventBus,
private val networkConnectivityChecker: NetworkConnectivityChecker
) : SetReadMarkersTask { ) : SetReadMarkersTask {
override suspend fun execute(params: SetReadMarkersTask.Params) { override suspend fun execute(params: SetReadMarkersTask.Params) {
val markers = HashMap<String, String>() val markers = HashMap<String, String>()
Timber.v("Execute set read marker with params: $params") Timber.v("Execute set read marker with params: $params")
val (fullyReadEventId, readReceiptEventId) = if (params.markAllAsRead) { val latestSyncedEventId = latestSyncedEventId(params.roomId)
val latestSyncedEventId = Realm.getInstance(monarchy.realmConfiguration).use { realm -> val fullyReadEventId = if(params.forceReadMarker){
TimelineEventEntity.latestEvent(realm, roomId = params.roomId, includesSending = false)?.eventId latestSyncedEventId
} }else {
Pair(latestSyncedEventId, latestSyncedEventId) params.fullyReadEventId
} else { }
Pair(params.fullyReadEventId, params.readReceiptEventId) val readReceiptEventId = if(params.forceReadReceipt){
latestSyncedEventId
}else {
params.readReceiptEventId
} }
if (fullyReadEventId != null && !isReadMarkerMoreRecent(monarchy, params.roomId, fullyReadEventId)) { if (fullyReadEventId != null && !isReadMarkerMoreRecent(monarchy, params.roomId, fullyReadEventId)) {
if (LocalEcho.isLocalEchoId(fullyReadEventId)) { if (LocalEcho.isLocalEchoId(fullyReadEventId)) {
Timber.w("Can't set read marker for local event $fullyReadEventId") Timber.w("Can't set read marker for local event $fullyReadEventId")
@ -80,7 +83,6 @@ internal class DefaultSetReadMarkersTask @Inject constructor(
markers[READ_MARKER] = fullyReadEventId markers[READ_MARKER] = fullyReadEventId
} }
} }
if (readReceiptEventId != null if (readReceiptEventId != null
&& !isEventRead(monarchy, userId, params.roomId, readReceiptEventId)) { && !isEventRead(monarchy, userId, params.roomId, readReceiptEventId)) {
if (LocalEcho.isLocalEchoId(readReceiptEventId)) { if (LocalEcho.isLocalEchoId(readReceiptEventId)) {
@ -89,16 +91,24 @@ internal class DefaultSetReadMarkersTask @Inject constructor(
markers[READ_RECEIPT] = readReceiptEventId markers[READ_RECEIPT] = readReceiptEventId
} }
} }
val shouldUpdateRoomSummary = readReceiptEventId != null && readReceiptEventId == latestSyncedEventId
updateDatabase(params.roomId, markers, shouldUpdateRoomSummary)
if (markers.isEmpty()) { if (markers.isEmpty()) {
return return
} }
updateDatabase(params.roomId, markers) networkConnectivityChecker.waitUntilConnected()
executeRequest<Unit>(eventBus) { executeRequest<Unit>(eventBus) {
apiCall = roomAPI.sendReadMarker(params.roomId, markers) apiCall = roomAPI.sendReadMarker(params.roomId, markers)
} }
} }
private suspend fun updateDatabase(roomId: String, markers: HashMap<String, String>) { private fun latestSyncedEventId(roomId: String): String? =
Realm.getInstance(monarchy.realmConfiguration).use { realm ->
TimelineEventEntity.latestEvent(realm, roomId = roomId, includesSending = false)?.eventId
}
private suspend fun updateDatabase(roomId: String, markers: HashMap<String, String>, shouldUpdateRoomSummary: Boolean) {
monarchy.awaitTransaction { realm -> monarchy.awaitTransaction { realm ->
val readMarkerId = markers[READ_MARKER] val readMarkerId = markers[READ_MARKER]
val readReceiptId = markers[READ_RECEIPT] val readReceiptId = markers[READ_RECEIPT]
@ -108,14 +118,13 @@ internal class DefaultSetReadMarkersTask @Inject constructor(
if (readReceiptId != null) { if (readReceiptId != null) {
val readReceiptContent = ReadReceiptHandler.createContent(userId, readReceiptId) val readReceiptContent = ReadReceiptHandler.createContent(userId, readReceiptId)
readReceiptHandler.handle(realm, roomId, readReceiptContent, false) readReceiptHandler.handle(realm, roomId, readReceiptContent, false)
val isLatestReceived = TimelineEventEntity.latestEvent(realm, roomId = roomId, includesSending = false)?.eventId == readReceiptId }
if (isLatestReceived) { if(shouldUpdateRoomSummary){
val roomSummary = RoomSummaryEntity.where(realm, roomId).findFirst() val roomSummary = RoomSummaryEntity.where(realm, roomId).findFirst()
?: return@awaitTransaction ?: return@awaitTransaction
roomSummary.notificationCount = 0 roomSummary.notificationCount = 0
roomSummary.highlightCount = 0 roomSummary.highlightCount = 0
roomSummary.hasUnreadMessages = false roomSummary.hasUnreadMessages = false
}
} }
} }
} }

View File

@ -41,6 +41,7 @@ import im.vector.matrix.android.api.session.room.model.message.MessageContent
import im.vector.matrix.android.api.session.room.model.message.MessageType import im.vector.matrix.android.api.session.room.model.message.MessageType
import im.vector.matrix.android.api.session.room.model.message.getFileUrl import im.vector.matrix.android.api.session.room.model.message.getFileUrl
import im.vector.matrix.android.api.session.room.model.tombstone.RoomTombstoneContent import im.vector.matrix.android.api.session.room.model.tombstone.RoomTombstoneContent
import im.vector.matrix.android.api.session.room.read.ReadService
import im.vector.matrix.android.api.session.room.send.UserDraft import im.vector.matrix.android.api.session.room.send.UserDraft
import im.vector.matrix.android.api.session.room.timeline.Timeline import im.vector.matrix.android.api.session.room.timeline.Timeline
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
@ -149,6 +150,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
observeUnreadState() observeUnreadState()
room.getRoomSummaryLive() room.getRoomSummaryLive()
room.rx().loadRoomMembersIfNeeded().subscribeLogError().disposeOnClear() room.rx().loadRoomMembersIfNeeded().subscribeLogError().disposeOnClear()
room.markAsRead(ReadService.MarkAsReadParams.READ_RECEIPT, object : MatrixCallback<Any> {})
// Inform the SDK that the room is displayed // Inform the SDK that the room is displayed
session.onRoomDisplayed(initialState.roomId) session.onRoomDisplayed(initialState.roomId)
} }
@ -753,7 +755,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
} }
private fun handleMarkAllAsRead() { private fun handleMarkAllAsRead() {
room.markAllAsRead(object : MatrixCallback<Any> {}) room.markAsRead(ReadService.MarkAsReadParams.BOTH, object : MatrixCallback<Any> {})
} }
private fun handleReportContent(action: RoomDetailAction.ReportContent) { private fun handleReportContent(action: RoomDetailAction.ReportContent) {

View File

@ -23,6 +23,7 @@ import androidx.core.app.RemoteInput
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.room.Room import im.vector.matrix.android.api.session.room.Room
import im.vector.matrix.android.api.session.room.read.ReadService
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.di.ActiveSessionHolder import im.vector.riotx.core.di.ActiveSessionHolder
import im.vector.riotx.core.extensions.vectorComponent import im.vector.riotx.core.extensions.vectorComponent
@ -88,7 +89,7 @@ class NotificationBroadcastReceiver : BroadcastReceiver() {
private fun handleMarkAsRead(roomId: String) { private fun handleMarkAsRead(roomId: String) {
activeSessionHolder.getActiveSession().let { session -> activeSessionHolder.getActiveSession().let { session ->
session.getRoom(roomId) session.getRoom(roomId)
?.markAllAsRead(object : MatrixCallback<Unit> {}) ?.markAsRead(ReadService.MarkAsReadParams.READ_RECEIPT, object : MatrixCallback<Unit> {})
} }
} }