diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/read/DefaultReadService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/read/DefaultReadService.kt index 1f0bae6fea..24263c1047 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/read/DefaultReadService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/read/DefaultReadService.kt @@ -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 diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/read/SetReadMarkersTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/read/SetReadMarkersTask.kt index beaf4eb0af..16be19a867 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/read/SetReadMarkersTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/read/SetReadMarkersTask.kt @@ -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 - } - } - } - } - } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/ReadReceiptHandler.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/ReadReceiptHandler.kt index e61e81dd16..192a11fa68 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/ReadReceiptHandler.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/ReadReceiptHandler.kt @@ -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 diff --git a/vector/src/main/java/im/vector/riotx/core/ui/views/ReadMarkerView.kt b/vector/src/main/java/im/vector/riotx/core/ui/views/ReadMarkerView.kt index 986acef616..55665ca27f 100644 --- a/vector/src/main/java/im/vector/riotx/core/ui/views/ReadMarkerView.kt +++ b/vector/src/main/java/im/vector/riotx/core/ui/views/ReadMarkerView.kt @@ -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) { diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt index 0bb45ccfb8..d689f019e7 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt @@ -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) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt index afe4ea6681..978e63f7f1 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt @@ -158,7 +158,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro private fun handleTombstoneEvent(action: RoomDetailActions.HandleTombstoneEvent) { val tombstoneContent = action.event.getClearContent().toModel() - ?: 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()?.relatesTo?.inReplyTo?.eventId - ?: state.sendMode.timelineEvent.root.content.toModel()?.relatesTo?.inReplyTo?.eventId + ?: state.sendMode.timelineEvent.root.content.toModel()?.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) } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/TimelineEventController.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/TimelineEventController.kt index b5a5fe8ca8..3aeac06f12 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/TimelineEventController.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/TimelineEventController.kt @@ -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