Timeline/Read: update read receipt locally to
This commit is contained in:
parent
88fb9667a3
commit
9668487b6b
|
@ -32,7 +32,6 @@ import im.vector.matrix.android.internal.database.model.ChunkEntity
|
|||
import im.vector.matrix.android.internal.database.model.ReadMarkerEntity
|
||||
import im.vector.matrix.android.internal.database.model.ReadReceiptEntity
|
||||
import im.vector.matrix.android.internal.database.model.ReadReceiptsSummaryEntity
|
||||
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
|
||||
import im.vector.matrix.android.internal.database.query.find
|
||||
import im.vector.matrix.android.internal.database.query.findLastLiveChunkFromRoom
|
||||
import im.vector.matrix.android.internal.database.query.where
|
||||
|
|
|
@ -30,6 +30,7 @@ import im.vector.matrix.android.internal.database.query.where
|
|||
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.send.LocalEchoEventFactory
|
||||
import im.vector.matrix.android.internal.session.sync.ReadReceiptHandler
|
||||
import im.vector.matrix.android.internal.session.sync.RoomFullyReadHandler
|
||||
import im.vector.matrix.android.internal.task.Task
|
||||
import im.vector.matrix.android.internal.util.awaitTransaction
|
||||
|
@ -53,7 +54,8 @@ private const val READ_RECEIPT = "m.read"
|
|||
internal class DefaultSetReadMarkersTask @Inject constructor(private val roomAPI: RoomAPI,
|
||||
private val credentials: Credentials,
|
||||
private val monarchy: Monarchy,
|
||||
private val roomFullyReadHandler: RoomFullyReadHandler
|
||||
private val roomFullyReadHandler: RoomFullyReadHandler,
|
||||
private val readReceiptHandler: ReadReceiptHandler
|
||||
) : SetReadMarkersTask {
|
||||
|
||||
override suspend fun execute(params: SetReadMarkersTask.Params) {
|
||||
|
@ -82,7 +84,7 @@ internal class DefaultSetReadMarkersTask @Inject constructor(private val roomAPI
|
|||
}
|
||||
|
||||
if (readReceiptEventId != null
|
||||
&& !isEventRead(params.roomId, readReceiptEventId)) {
|
||||
&& !isEventRead(params.roomId, readReceiptEventId)) {
|
||||
if (LocalEchoEventFactory.isLocalEchoId(readReceiptEventId)) {
|
||||
Timber.w("Can't set read receipt for local event $readReceiptEventId")
|
||||
} else {
|
||||
|
@ -102,15 +104,16 @@ internal class DefaultSetReadMarkersTask @Inject constructor(private val roomAPI
|
|||
monarchy.awaitTransaction { realm ->
|
||||
val readMarkerId = markers[READ_MARKER]
|
||||
val readReceiptId = markers[READ_RECEIPT]
|
||||
|
||||
if (readMarkerId != null) {
|
||||
roomFullyReadHandler.handle(realm, roomId, FullyReadContent(readMarkerId))
|
||||
}
|
||||
if (readReceiptId != null) {
|
||||
val readReceiptContent = ReadReceiptHandler.createContent(credentials.userId, readReceiptId)
|
||||
readReceiptHandler.handle(realm, roomId, readReceiptContent, false)
|
||||
val isLatestReceived = TimelineEventEntity.latestEvent(realm, roomId = roomId, includesSending = false)?.eventId == readReceiptId
|
||||
if (isLatestReceived) {
|
||||
val roomSummary = RoomSummaryEntity.where(realm, roomId).findFirst()
|
||||
?: return@awaitTransaction
|
||||
?: return@awaitTransaction
|
||||
roomSummary.notificationCount = 0
|
||||
roomSummary.highlightCount = 0
|
||||
}
|
||||
|
@ -118,7 +121,6 @@ internal class DefaultSetReadMarkersTask @Inject constructor(private val roomAPI
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
private fun isReadMarkerMoreRecent(roomId: String, fullyReadEventId: String): Boolean {
|
||||
return Realm.getInstance(monarchy.realmConfiguration).use { realm ->
|
||||
val readMarkerEntity = ReadMarkerEntity.where(realm, roomId = roomId).findFirst()
|
||||
|
@ -130,36 +132,19 @@ internal class DefaultSetReadMarkersTask @Inject constructor(private val roomAPI
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
private fun isEventRead(roomId: String, eventId: String): Boolean {
|
||||
return Realm.getInstance(monarchy.realmConfiguration).use { realm ->
|
||||
val readReceipt = ReadReceiptEntity.where(realm, roomId, credentials.userId).findFirst()
|
||||
?: return false
|
||||
?: return false
|
||||
val liveChunk = ChunkEntity.findLastLiveChunkFromRoom(realm, roomId)
|
||||
?: return false
|
||||
?: return false
|
||||
val readReceiptIndex = liveChunk.timelineEvents.find(readReceipt.eventId)?.root?.displayIndex
|
||||
?: Int.MIN_VALUE
|
||||
?: Int.MIN_VALUE
|
||||
val eventToCheckIndex = liveChunk.timelineEvents.find(eventId)?.root?.displayIndex
|
||||
?: Int.MAX_VALUE
|
||||
?: Int.MAX_VALUE
|
||||
eventToCheckIndex <= readReceiptIndex
|
||||
}
|
||||
}
|
||||
|
||||
private fun SetReadMarkersTask.Params.fullyReadEventId(): String? {
|
||||
if (fullyReadEventId != null) {
|
||||
return this.fullyReadEventId
|
||||
} else {
|
||||
Realm.getInstance(monarchy.realmConfiguration).use { realm ->
|
||||
val readReceipt = ReadReceiptEntity.where(realm, roomId, credentials.userId).findFirst()
|
||||
val readMarker = ReadMarkerEntity.where(realm, roomId).findFirst()
|
||||
return if (readMarker?.eventId == readReceipt?.eventId) {
|
||||
readReceiptEventId
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -38,6 +38,22 @@ private const val TIMESTAMP_KEY = "ts"
|
|||
|
||||
internal class ReadReceiptHandler @Inject constructor() {
|
||||
|
||||
companion object {
|
||||
|
||||
fun createContent(userId: String, eventId: String): ReadReceiptContent {
|
||||
return mapOf(
|
||||
eventId to mapOf(
|
||||
READ_KEY to mapOf(
|
||||
userId to mapOf(
|
||||
TIMESTAMP_KEY to System.currentTimeMillis().toDouble()
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun handle(realm: Realm, roomId: String, content: ReadReceiptContent?, isInitialSync: Boolean) {
|
||||
if (content == null) {
|
||||
return
|
||||
|
|
|
@ -26,6 +26,7 @@ import android.view.animation.AnimationUtils
|
|||
import androidx.core.view.isInvisible
|
||||
import im.vector.riotx.R
|
||||
import kotlinx.coroutines.*
|
||||
import timber.log.Timber
|
||||
|
||||
private const val DELAY_IN_MS = 1_500L
|
||||
|
||||
|
@ -44,6 +45,7 @@ class ReadMarkerView @JvmOverloads constructor(
|
|||
private var callbackDispatcherJob: Job? = null
|
||||
|
||||
fun bindView(eventId: String?, hasReadMarker: Boolean, displayReadMarker: Boolean, readMarkerCallback: Callback) {
|
||||
Timber.v("Bind event $eventId - hasReadMarker: $hasReadMarker - displayReadMarker: $displayReadMarker")
|
||||
this.eventId = eventId
|
||||
this.callback = readMarkerCallback
|
||||
if (displayReadMarker) {
|
||||
|
|
|
@ -687,7 +687,7 @@ class RoomDetailFragment :
|
|||
val summary = state.asyncRoomSummary()
|
||||
val inviter = state.asyncInviter()
|
||||
if (summary?.membership == Membership.JOIN) {
|
||||
timelineEventController.update(state.timeline, state.highlightedEventId, state.hideReadMarker)
|
||||
timelineEventController.update(state)
|
||||
inviteView.visibility = View.GONE
|
||||
val uid = session.myUserId
|
||||
val meMember = session.getRoom(state.roomId)?.getRoomMember(uid)
|
||||
|
|
|
@ -158,7 +158,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
|||
|
||||
private fun handleTombstoneEvent(action: RoomDetailActions.HandleTombstoneEvent) {
|
||||
val tombstoneContent = action.event.getClearContent().toModel<RoomTombstoneContent>()
|
||||
?: return
|
||||
?: return
|
||||
|
||||
val roomId = tombstoneContent.replacementRoom ?: ""
|
||||
val isRoomJoined = session.getRoom(roomId)?.roomSummary()?.membership == Membership.JOIN
|
||||
|
@ -305,7 +305,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
|||
|
||||
//is original event a reply?
|
||||
val inReplyTo = state.sendMode.timelineEvent.root.getClearContent().toModel<MessageContent>()?.relatesTo?.inReplyTo?.eventId
|
||||
?: state.sendMode.timelineEvent.root.content.toModel<EncryptedEventContent>()?.relatesTo?.inReplyTo?.eventId
|
||||
?: state.sendMode.timelineEvent.root.content.toModel<EncryptedEventContent>()?.relatesTo?.inReplyTo?.eventId
|
||||
if (inReplyTo != null) {
|
||||
//TODO check if same content?
|
||||
room.getTimeLineEvent(inReplyTo)?.let {
|
||||
|
@ -314,12 +314,12 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
|||
} else {
|
||||
val messageContent: MessageContent? =
|
||||
state.sendMode.timelineEvent.annotations?.editSummary?.aggregatedContent.toModel()
|
||||
?: state.sendMode.timelineEvent.root.getClearContent().toModel()
|
||||
?: state.sendMode.timelineEvent.root.getClearContent().toModel()
|
||||
val existingBody = messageContent?.body ?: ""
|
||||
if (existingBody != action.text) {
|
||||
room.editTextMessage(state.sendMode.timelineEvent.root.eventId
|
||||
?: "", messageContent?.type
|
||||
?: MessageType.MSGTYPE_TEXT, action.text, action.autoMarkdown)
|
||||
?: "", messageContent?.type
|
||||
?: MessageType.MSGTYPE_TEXT, action.text, action.autoMarkdown)
|
||||
} else {
|
||||
Timber.w("Same message content, do not send edition")
|
||||
}
|
||||
|
@ -334,7 +334,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
|||
is SendMode.QUOTE -> {
|
||||
val messageContent: MessageContent? =
|
||||
state.sendMode.timelineEvent.annotations?.editSummary?.aggregatedContent.toModel()
|
||||
?: state.sendMode.timelineEvent.root.getClearContent().toModel()
|
||||
?: state.sendMode.timelineEvent.root.getClearContent().toModel()
|
||||
val textMsg = messageContent?.body
|
||||
|
||||
val finalText = legacyRiotQuoteText(textMsg, action.text)
|
||||
|
@ -645,7 +645,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
|||
Option.empty()
|
||||
} else {
|
||||
val readMarkerIndex = room.getTimeLineEvent(readMarkerId)?.displayIndex
|
||||
?: Int.MIN_VALUE
|
||||
?: Int.MIN_VALUE
|
||||
Option.just(readMarkerIndex)
|
||||
}
|
||||
}
|
||||
|
@ -694,8 +694,6 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
|||
readMarker.getOrNull() == readReceipt.getOrNull()
|
||||
}
|
||||
)
|
||||
.throttleLast(250, TimeUnit.MILLISECONDS)
|
||||
.distinctUntilChanged()
|
||||
.startWith(false)
|
||||
.subscribe {
|
||||
setState { copy(hideReadMarker = it) }
|
||||
|
|
|
@ -31,6 +31,7 @@ import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
|||
import im.vector.riotx.core.date.VectorDateFormatter
|
||||
import im.vector.riotx.core.epoxy.LoadingItem_
|
||||
import im.vector.riotx.core.extensions.localDateTime
|
||||
import im.vector.riotx.features.home.room.detail.RoomDetailViewState
|
||||
import im.vector.riotx.features.home.room.detail.timeline.factory.MergedHeaderItemFactory
|
||||
import im.vector.riotx.features.home.room.detail.timeline.factory.TimelineItemFactory
|
||||
import im.vector.riotx.features.home.room.detail.timeline.helper.*
|
||||
|
@ -140,11 +141,10 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
|
|||
requestModelBuild()
|
||||
}
|
||||
|
||||
fun update(timeline: Timeline?, eventIdToHighlight: String?, hideReadMarker: Boolean) {
|
||||
if (this.timeline != timeline) {
|
||||
this.timeline = timeline
|
||||
this.timeline?.listener = this
|
||||
|
||||
fun update(viewState: RoomDetailViewState) {
|
||||
if (timeline != viewState.timeline) {
|
||||
timeline = viewState.timeline
|
||||
timeline?.listener = this
|
||||
// Clear cache
|
||||
synchronized(modelCache) {
|
||||
for (i in 0 until modelCache.size) {
|
||||
|
@ -152,23 +152,22 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
var requestModelBuild = false
|
||||
if (this.eventIdToHighlight != eventIdToHighlight) {
|
||||
if (eventIdToHighlight != viewState.highlightedEventId) {
|
||||
// Clear cache to force a refresh
|
||||
synchronized(modelCache) {
|
||||
for (i in 0 until modelCache.size) {
|
||||
if (modelCache[i]?.eventId == eventIdToHighlight
|
||||
|| modelCache[i]?.eventId == this.eventIdToHighlight) {
|
||||
if (modelCache[i]?.eventId == viewState.highlightedEventId
|
||||
|| modelCache[i]?.eventId == eventIdToHighlight) {
|
||||
modelCache[i] = null
|
||||
}
|
||||
}
|
||||
}
|
||||
this.eventIdToHighlight = eventIdToHighlight
|
||||
eventIdToHighlight = viewState.highlightedEventId
|
||||
requestModelBuild = true
|
||||
}
|
||||
if (this.hideReadMarker != hideReadMarker) {
|
||||
this.hideReadMarker = hideReadMarker
|
||||
if (hideReadMarker != viewState.hideReadMarker) {
|
||||
hideReadMarker = viewState.hideReadMarker
|
||||
requestModelBuild = true
|
||||
}
|
||||
if (requestModelBuild) {
|
||||
|
@ -230,8 +229,8 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
|
|||
// Should be build if not cached or if cached but contains mergedHeader or formattedDay
|
||||
// We then are sure we always have items up to date.
|
||||
if (modelCache[position] == null
|
||||
|| modelCache[position]?.mergedHeaderModel != null
|
||||
|| modelCache[position]?.formattedDayModel != null) {
|
||||
|| modelCache[position]?.mergedHeaderModel != null
|
||||
|| modelCache[position]?.formattedDayModel != null) {
|
||||
modelCache[position] = buildItemModels(position, currentSnapshot)
|
||||
}
|
||||
}
|
||||
|
@ -256,7 +255,6 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
|
|||
val date = event.root.localDateTime()
|
||||
val nextDate = nextEvent?.root?.localDateTime()
|
||||
val addDaySeparator = date.toLocalDate() != nextDate?.toLocalDate()
|
||||
|
||||
val eventModel = timelineItemFactory.create(event, nextEvent, eventIdToHighlight, hideReadMarker, callback).also {
|
||||
it.id(event.localId)
|
||||
it.setOnVisibilityStateChanged(TimelineEventVisibilityStateChangedListener(callback, event))
|
||||
|
@ -298,7 +296,6 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
fun searchPositionOfEvent(eventId: String): Int? = synchronized(modelCache) {
|
||||
// Search in the cache
|
||||
var realPosition = 0
|
||||
|
|
Loading…
Reference in New Issue