From 4a80df082ceb85b14964509b5d4f8173ffe14570 Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 25 Sep 2019 19:14:12 +0200 Subject: [PATCH] Timeline: refact [WIP] --- .../EventAnnotationsSummaryEntityQuery.kt | 8 +- .../query/ReadReceiptsSummaryEntityQueries.kt | 9 +- .../query/TimelineEventEntityQueries.kt | 8 +- .../room/EventRelationsAggregationTask.kt | 10 +-- .../session/room/read/SetReadMarkersTask.kt | 21 +++-- .../session/room/send/DefaultSendService.kt | 2 +- .../session/room/timeline/DefaultTimeline.kt | 9 +- .../room/timeline/DefaultTimelineService.kt | 6 +- .../room/timeline/TimelineHiddenReadMarker.kt | 89 ++++++++++++------- .../session/sync/RoomFullyReadHandler.kt | 15 ++-- .../core/ui/views/JumpToReadMarkerView.kt | 31 +++++-- .../riotx/core/ui/views/ReadMarkerView.kt | 1 - .../home/room/detail/ReadMarkerHelper.kt | 25 +++--- .../home/room/detail/RoomDetailFragment.kt | 73 ++++++++------- .../home/room/detail/RoomDetailViewModel.kt | 78 +++++++--------- .../home/room/detail/RoomDetailViewState.kt | 3 +- .../timeline/TimelineEventController.kt | 57 +++++------- .../timeline/TimelineLayoutManagerHolder.kt | 29 ------ .../factory/MergedHeaderItemFactory.kt | 3 +- .../detail/timeline/item/AbsMessageItem.kt | 7 +- .../detail/timeline/item/BaseEventItem.kt | 3 + .../room/detail/timeline/item/DefaultItem.kt | 4 + .../detail/timeline/item/MergedHeaderItem.kt | 10 ++- .../room/detail/timeline/item/NoticeItem.kt | 7 +- .../main/res/layout/fragment_room_detail.xml | 4 +- 25 files changed, 266 insertions(+), 246 deletions(-) delete mode 100644 vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/TimelineLayoutManagerHolder.kt diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/EventAnnotationsSummaryEntityQuery.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/EventAnnotationsSummaryEntityQuery.kt index f1179ebeb4..0f454b0af7 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/EventAnnotationsSummaryEntityQuery.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/EventAnnotationsSummaryEntityQuery.kt @@ -38,10 +38,12 @@ internal fun EventAnnotationsSummaryEntity.Companion.whereInRoom(realm: Realm, r } -internal fun EventAnnotationsSummaryEntity.Companion.create(realm: Realm, eventId: String): EventAnnotationsSummaryEntity { - val obj = realm.createObject(EventAnnotationsSummaryEntity::class.java, eventId) +internal fun EventAnnotationsSummaryEntity.Companion.create(realm: Realm, roomId: String, eventId: String): EventAnnotationsSummaryEntity { + val obj = realm.createObject(EventAnnotationsSummaryEntity::class.java, eventId).apply { + this.roomId = roomId + } //Denormalization - TimelineEventEntity.where(realm, eventId = eventId).findFirst()?.let { + TimelineEventEntity.where(realm, roomId = roomId, eventId = eventId).findFirst()?.let { it.annotations = obj } return obj diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/ReadReceiptsSummaryEntityQueries.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/ReadReceiptsSummaryEntityQueries.kt index 0c3d7d8eb1..1773297727 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/ReadReceiptsSummaryEntityQueries.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/ReadReceiptsSummaryEntityQueries.kt @@ -27,10 +27,7 @@ internal fun ReadReceiptsSummaryEntity.Companion.where(realm: Realm, eventId: St .equalTo(ReadReceiptsSummaryEntityFields.EVENT_ID, eventId) } -internal fun ReadReceiptsSummaryEntity.Companion.whereInRoom(realm: Realm, roomId: String?): RealmQuery { - val query = realm.where() - if (roomId != null) { - query.equalTo(ReadReceiptsSummaryEntityFields.ROOM_ID, roomId) - } - return query +internal fun ReadReceiptsSummaryEntity.Companion.whereInRoom(realm: Realm, roomId: String): RealmQuery { + return realm.where() + .equalTo(ReadReceiptsSummaryEntityFields.ROOM_ID, roomId) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/TimelineEventEntityQueries.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/TimelineEventEntityQueries.kt index 182e58a3b5..8b9beca11e 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/TimelineEventEntityQueries.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/TimelineEventEntityQueries.kt @@ -22,13 +22,15 @@ import im.vector.matrix.android.internal.database.model.EventEntity.LinkFilterMo import io.realm.* import io.realm.kotlin.where -internal fun TimelineEventEntity.Companion.where(realm: Realm, eventId: String): RealmQuery { +internal fun TimelineEventEntity.Companion.where(realm: Realm, roomId: String, eventId: String): RealmQuery { return realm.where() + .equalTo(TimelineEventEntityFields.ROOM_ID, roomId) .equalTo(TimelineEventEntityFields.EVENT_ID, eventId) } -internal fun TimelineEventEntity.Companion.where(realm: Realm, eventIds: List): RealmQuery { +internal fun TimelineEventEntity.Companion.where(realm: Realm, roomId: String, eventIds: List): RealmQuery { return realm.where() + .equalTo(TimelineEventEntityFields.ROOM_ID, roomId) .`in`(TimelineEventEntityFields.EVENT_ID, eventIds.toTypedArray()) } @@ -121,6 +123,6 @@ internal fun TimelineEventEntity.Companion.findAllInRoomWithSendStates(realm: Re val sendStatesStr = sendStates.map { it.name }.toTypedArray() return realm.where() .equalTo(TimelineEventEntityFields.ROOM_ID, roomId) - .`in`(TimelineEventEntityFields.ROOT.SEND_STATE_STR,sendStatesStr) + .`in`(TimelineEventEntityFields.ROOT.SEND_STATE_STR, sendStatesStr) .findAll() } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationTask.kt index 786ba168ac..3743eef211 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationTask.kt @@ -85,7 +85,7 @@ internal class DefaultEventRelationsAggregationTask @Inject constructor( EventAnnotationsSummaryEntity.where(realm, event.eventId ?: "").findFirst()?.let { - TimelineEventEntity.where(realm, eventId = event.eventId + TimelineEventEntity.where(realm, roomId = roomId, eventId = event.eventId ?: "").findFirst()?.let { tet -> tet.annotations = it } @@ -167,8 +167,7 @@ internal class DefaultEventRelationsAggregationTask @Inject constructor( var existing = EventAnnotationsSummaryEntity.where(realm, targetEventId).findFirst() if (existing == null) { Timber.v("###REPLACE creating new relation summary for $targetEventId") - existing = EventAnnotationsSummaryEntity.create(realm, targetEventId) - existing.roomId = roomId + existing = EventAnnotationsSummaryEntity.create(realm, roomId, targetEventId) } //we have it @@ -233,8 +232,7 @@ internal class DefaultEventRelationsAggregationTask @Inject constructor( val eventId = event.eventId ?: "" val existing = EventAnnotationsSummaryEntity.where(realm, eventId).findFirst() if (existing == null) { - val eventSummary = EventAnnotationsSummaryEntity.create(realm, eventId) - eventSummary.roomId = roomId + val eventSummary = EventAnnotationsSummaryEntity.create(realm, roomId, eventId) val sum = realm.createObject(ReactionAggregatedSummaryEntity::class.java) sum.key = it.key sum.firstTimestamp = event.originServerTs ?: 0 //TODO how to maintain order? @@ -261,7 +259,7 @@ internal class DefaultEventRelationsAggregationTask @Inject constructor( val reactionEventId = event.eventId Timber.v("Reaction $reactionEventId relates to $relatedEventID") val eventSummary = EventAnnotationsSummaryEntity.where(realm, relatedEventID).findFirst() - ?: EventAnnotationsSummaryEntity.create(realm, relatedEventID).apply { this.roomId = roomId } + ?: EventAnnotationsSummaryEntity.create(realm, roomId, relatedEventID).apply { this.roomId = roomId } var sum = eventSummary.reactionsSummary.find { it.key == reaction } val txId = event.unsignedData?.transactionId 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 6a875b0563..2748a49930 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 @@ -17,9 +17,7 @@ package im.vector.matrix.android.internal.session.room.read import com.zhuinden.monarchy.Monarchy -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.RoomSummaryEntity import im.vector.matrix.android.internal.database.model.TimelineEventEntity import im.vector.matrix.android.internal.database.query.* @@ -83,7 +81,7 @@ internal class DefaultSetReadMarkersTask @Inject constructor(private val roomAPI } if (readReceiptEventId != null - && !isEventRead(monarchy, userId, params.roomId, readReceiptEventId)) { + && !isEventRead(monarchy, userId, params.roomId, readReceiptEventId)) { if (LocalEchoEventFactory.isLocalEchoId(readReceiptEventId)) { Timber.w("Can't set read receipt for local event $readReceiptEventId") } else { @@ -112,7 +110,7 @@ internal class DefaultSetReadMarkersTask @Inject constructor(private val roomAPI 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 roomSummary.hasUnreadMessages = false @@ -121,14 +119,15 @@ internal class DefaultSetReadMarkersTask @Inject constructor(private val roomAPI } } - private fun isReadMarkerMoreRecent(roomId: String, fullyReadEventId: String): Boolean { + private fun isReadMarkerMoreRecent(roomId: String, newReadMarkerId: String): Boolean { return Realm.getInstance(monarchy.realmConfiguration).use { realm -> - val readMarkerEntity = ReadMarkerEntity.where(realm, roomId = roomId).findFirst() - val readMarkerEvent = readMarkerEntity?.timelineEvent?.firstOrNull() - val eventToCheck = TimelineEventEntity.where(realm, eventId = fullyReadEventId).findFirst() - val readReceiptIndex = readMarkerEvent?.root?.displayIndex ?: Int.MAX_VALUE - val eventToCheckIndex = eventToCheck?.root?.displayIndex ?: Int.MIN_VALUE - eventToCheckIndex > readReceiptIndex + val currentReadMarkerId = ReadMarkerEntity.where(realm, roomId = roomId).findFirst()?.eventId + ?: return true + val readMarkerEvent = TimelineEventEntity.where(realm, roomId = roomId, eventId = currentReadMarkerId).findFirst() + val newReadMarkerEvent = TimelineEventEntity.where(realm, roomId = roomId, eventId = newReadMarkerId).findFirst() + val currentReadMarkerIndex = readMarkerEvent?.root?.displayIndex ?: Int.MAX_VALUE + val newReadMarkerIndex = newReadMarkerEvent?.root?.displayIndex ?: Int.MIN_VALUE + newReadMarkerIndex > currentReadMarkerIndex } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/DefaultSendService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/DefaultSendService.kt index a342d3fe72..8b5b84c297 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/DefaultSendService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/DefaultSendService.kt @@ -158,7 +158,7 @@ internal class DefaultSendService @AssistedInject constructor(@Assisted private override fun deleteFailedEcho(localEcho: TimelineEvent) { monarchy.writeAsync { realm -> - TimelineEventEntity.where(realm, eventId = localEcho.root.eventId ?: "").findFirst()?.let { + TimelineEventEntity.where(realm, roomId = roomId, eventId = localEcho.root.eventId ?: "").findFirst()?.let { it.deleteFromRealm() } EventEntity.where(realm, eventId = localEcho.root.eventId ?: "").findFirst()?.let { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt index 9147b922cb..d95408bfb1 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt @@ -24,6 +24,7 @@ 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.TimelineSettings import im.vector.matrix.android.api.util.CancelableBag +import im.vector.matrix.android.internal.database.helper.deleteOnCascade import im.vector.matrix.android.internal.database.mapper.TimelineEventMapper import im.vector.matrix.android.internal.database.mapper.asDomain import im.vector.matrix.android.internal.database.model.ChunkEntity @@ -625,12 +626,14 @@ internal class DefaultTimeline( } private fun clearUnlinkedEvents(realm: Realm) { - realm.executeTransaction { + realm.executeTransaction { localRealm -> val unlinkedChunks = ChunkEntity - .where(it, roomId = roomId) + .where(localRealm, roomId = roomId) .equalTo("${ChunkEntityFields.TIMELINE_EVENTS.ROOT}.${EventEntityFields.IS_UNLINKED}", true) .findAll() - unlinkedChunks.deleteAllFromRealm() + unlinkedChunks.forEach { + it.deleteOnCascade() + } } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimelineService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimelineService.kt index 0ded458a20..4c4b08ce4f 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimelineService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimelineService.kt @@ -60,14 +60,14 @@ internal class DefaultTimelineService @AssistedInject constructor(@Assisted priv timelineEventMapper, settings, TimelineHiddenReadReceipts(readReceiptsSummaryMapper, roomId, settings), - TimelineHiddenReadMarker(roomId) + TimelineHiddenReadMarker(roomId, settings) ) } override fun getTimeLineEvent(eventId: String): TimelineEvent? { return monarchy .fetchCopyMap({ - TimelineEventEntity.where(it, eventId = eventId).findFirst() + TimelineEventEntity.where(it, roomId = roomId, eventId = eventId).findFirst() }, { entity, realm -> timelineEventMapper.map(entity) }) @@ -75,7 +75,7 @@ internal class DefaultTimelineService @AssistedInject constructor(@Assisted priv override fun getTimeLineEventLive(eventId: String): LiveData { val liveData = RealmLiveData(monarchy.realmConfiguration) { - TimelineEventEntity.where(it, eventId = eventId) + TimelineEventEntity.where(it, roomId = roomId, eventId = eventId) } return Transformations.map(liveData) { events -> events.firstOrNull()?.let { timelineEventMapper.map(it) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineHiddenReadMarker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineHiddenReadMarker.kt index 7ae6cbcfe1..eebb98ca19 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineHiddenReadMarker.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineHiddenReadMarker.kt @@ -18,12 +18,16 @@ package im.vector.matrix.android.internal.session.room.timeline +import im.vector.matrix.android.api.session.room.timeline.TimelineSettings import im.vector.matrix.android.internal.database.model.ReadMarkerEntity +import im.vector.matrix.android.internal.database.model.ReadMarkerEntityFields import im.vector.matrix.android.internal.database.model.TimelineEventEntity import im.vector.matrix.android.internal.database.model.TimelineEventEntityFields +import im.vector.matrix.android.internal.database.query.FilterContent import im.vector.matrix.android.internal.database.query.where +import io.realm.OrderedRealmCollectionChangeListener import io.realm.Realm -import io.realm.RealmObjectChangeListener +import io.realm.RealmQuery import io.realm.RealmResults /** @@ -31,7 +35,8 @@ import io.realm.RealmResults * When an hidden event has read marker, we want to transfer it on the first older displayed event. * It has to be used in [DefaultTimeline] and we should call the [start] and [dispose] methods to properly handle realm subscription. */ -internal class TimelineHiddenReadMarker constructor(private val roomId: String) { +internal class TimelineHiddenReadMarker constructor(private val roomId: String, + private val settings: TimelineSettings) { interface Delegate { fun rebuildEvent(eventId: String, hasReadMarker: Boolean): Boolean @@ -39,39 +44,42 @@ internal class TimelineHiddenReadMarker constructor(private val roomId: String) } private var previousDisplayedEventId: String? = null - private var readMarkerEntity: ReadMarkerEntity? = null + private var hiddenReadMarker: RealmResults? = null private lateinit var liveEvents: RealmResults private lateinit var delegate: Delegate - private val readMarkerListener = RealmObjectChangeListener { readMarker, _ -> - if (!readMarker.isLoaded || !readMarker.isValid) { - return@RealmObjectChangeListener + private val readMarkerListener = OrderedRealmCollectionChangeListener> { readMarkers, changeSet -> + if (!readMarkers.isLoaded || !readMarkers.isValid) { + return@OrderedRealmCollectionChangeListener } var hasChange = false - previousDisplayedEventId?.also { - hasChange = delegate.rebuildEvent(it, false) - previousDisplayedEventId = null - } - val isEventHidden = liveEvents.where().equalTo(TimelineEventEntityFields.EVENT_ID, readMarker.eventId).findFirst() == null - if (isEventHidden) { - val hiddenEvent = readMarker.timelineEvent?.firstOrNull() - ?: return@RealmObjectChangeListener - val displayIndex = hiddenEvent.root?.displayIndex - if (displayIndex != null) { - // Then we are looking for the first displayable event after the hidden one - val firstDisplayedEvent = liveEvents.where() - .lessThanOrEqualTo(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, displayIndex) - .findFirst() - - // If we find one, we should rebuild this one with marker - if (firstDisplayedEvent != null) { - previousDisplayedEventId = firstDisplayedEvent.eventId - hasChange = delegate.rebuildEvent(firstDisplayedEvent.eventId, true) - } + if (changeSet.deletions.isNotEmpty()) { + previousDisplayedEventId?.also { + hasChange = delegate.rebuildEvent(it, false) + previousDisplayedEventId = null } } - if (hasChange) delegate.onReadMarkerUpdated() + val readMarker = readMarkers.firstOrNull() ?: return@OrderedRealmCollectionChangeListener + val hiddenEvent = readMarker.timelineEvent?.firstOrNull() + ?: return@OrderedRealmCollectionChangeListener + + val displayIndex = hiddenEvent.root?.displayIndex + if (displayIndex != null) { + // Then we are looking for the first displayable event after the hidden one + val firstDisplayedEvent = liveEvents.where() + .lessThanOrEqualTo(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, displayIndex) + .findFirst() + + // If we find one, we should rebuild this one with marker + if (firstDisplayedEvent != null) { + previousDisplayedEventId = firstDisplayedEvent.eventId + hasChange = delegate.rebuildEvent(firstDisplayedEvent.eventId, true) + } + } + if (hasChange) { + delegate.onReadMarkerUpdated() + } } @@ -83,8 +91,10 @@ internal class TimelineHiddenReadMarker constructor(private val roomId: String) this.delegate = delegate // We are looking for read receipts set on hidden events. // We only accept those with a timelineEvent (so coming from pagination/sync). - readMarkerEntity = ReadMarkerEntity.where(realm, roomId = roomId) - .findFirstAsync() + hiddenReadMarker = ReadMarkerEntity.where(realm, roomId = roomId) + .isNotEmpty(ReadMarkerEntityFields.TIMELINE_EVENT) + .filterReceiptsWithSettings() + .findAllAsync() .also { it.addChangeListener(readMarkerListener) } } @@ -93,7 +103,26 @@ internal class TimelineHiddenReadMarker constructor(private val roomId: String) * Dispose the realm query subscription. Has to be called on an HandlerThread */ fun dispose() { - this.readMarkerEntity?.removeAllChangeListeners() + this.hiddenReadMarker?.removeAllChangeListeners() } + /** + * We are looking for readMarker related to filtered events. So, it's the opposite of [DefaultTimeline.filterEventsWithSettings] method. + */ + private fun RealmQuery.filterReceiptsWithSettings(): RealmQuery { + beginGroup() + if (settings.filterTypes) { + not().`in`("${ReadMarkerEntityFields.TIMELINE_EVENT}.${TimelineEventEntityFields.ROOT.TYPE}", settings.allowedTypes.toTypedArray()) + } + if (settings.filterTypes && settings.filterEdits) { + or() + } + if (settings.filterEdits) { + like("${ReadMarkerEntityFields.TIMELINE_EVENT}.${TimelineEventEntityFields.ROOT.CONTENT}", FilterContent.EDIT_TYPE) + } + endGroup() + return this + } + + } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/RoomFullyReadHandler.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/RoomFullyReadHandler.kt index 99fbc5750d..fdbaa2ab1b 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/RoomFullyReadHandler.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/RoomFullyReadHandler.kt @@ -16,10 +16,12 @@ package im.vector.matrix.android.internal.session.sync +import im.vector.matrix.android.internal.database.model.EventEntity import im.vector.matrix.android.internal.session.room.read.FullyReadContent import im.vector.matrix.android.internal.database.model.ReadMarkerEntity import im.vector.matrix.android.internal.database.model.RoomSummaryEntity import im.vector.matrix.android.internal.database.model.TimelineEventEntity +import im.vector.matrix.android.internal.database.model.TimelineEventEntityFields import im.vector.matrix.android.internal.database.query.getOrCreate import im.vector.matrix.android.internal.database.query.where import io.realm.Realm @@ -37,15 +39,14 @@ internal class RoomFullyReadHandler @Inject constructor() { RoomSummaryEntity.getOrCreate(realm, roomId).apply { readMarkerId = content.eventId } - val readMarkerEntity = ReadMarkerEntity.getOrCreate(realm, roomId) - // Remove the old marker if any - if (readMarkerEntity.eventId.isNotEmpty()) { - val oldReadMarkerEvent = TimelineEventEntity.where(realm, eventId = readMarkerEntity.eventId).findFirst() - oldReadMarkerEvent?.readMarker = null + // Remove the old markers if any + val oldReadMarkerEvents = TimelineEventEntity.where(realm, roomId = roomId, linkFilterMode = EventEntity.LinkFilterMode.BOTH).isNotNull(TimelineEventEntityFields.READ_MARKER.`$`).findAll() + oldReadMarkerEvents.forEach { it.readMarker = null } + val readMarkerEntity = ReadMarkerEntity.getOrCreate(realm, roomId).apply { + this.eventId = content.eventId } - readMarkerEntity.eventId = content.eventId // Attach to timelineEvent if known - val timelineEventEntity = TimelineEventEntity.where(realm, eventId = content.eventId).findFirst() + val timelineEventEntity = TimelineEventEntity.where(realm, roomId = roomId, eventId = content.eventId).findFirst() timelineEventEntity?.readMarker = readMarkerEntity } diff --git a/vector/src/main/java/im/vector/riotx/core/ui/views/JumpToReadMarkerView.kt b/vector/src/main/java/im/vector/riotx/core/ui/views/JumpToReadMarkerView.kt index 398d525217..ed81f82e72 100644 --- a/vector/src/main/java/im/vector/riotx/core/ui/views/JumpToReadMarkerView.kt +++ b/vector/src/main/java/im/vector/riotx/core/ui/views/JumpToReadMarkerView.kt @@ -21,16 +21,21 @@ package im.vector.riotx.core.ui.views import android.content.Context import android.util.AttributeSet import android.view.View +import android.view.ViewGroup import android.widget.LinearLayout import android.widget.RelativeLayout import androidx.core.content.ContextCompat import androidx.core.view.isVisible +import androidx.core.view.updateLayoutParams import butterknife.ButterKnife +import com.airbnb.epoxy.VisibilityState +import com.google.android.material.internal.ViewUtils.dpToPx import im.vector.riotx.R import im.vector.riotx.features.themes.ThemeUtils import kotlinx.android.synthetic.main.view_jump_to_read_marker.view.* import me.gujun.android.span.span import me.saket.bettermovementmethod.BetterLinkMovementMethod +import timber.log.Timber class JumpToReadMarkerView @JvmOverloads constructor( context: Context, @@ -49,26 +54,34 @@ class JumpToReadMarkerView @JvmOverloads constructor( setupView() } + private var readMarkerId: String? = null + private fun setupView() { - LinearLayout.inflate(context, R.layout.view_jump_to_read_marker, this) + inflate(context, R.layout.view_jump_to_read_marker, this) setBackgroundColor(ContextCompat.getColor(context, R.color.notification_accent_color)) jumpToReadMarkerLabelView.movementMethod = BetterLinkMovementMethod.getInstance() isClickable = true + jumpToReadMarkerLabelView.text = span(resources.getString(R.string.room_jump_to_first_unread)) { + textDecorationLine = "underline" + onClick = { + readMarkerId?.also { + callback?.onJumpToReadMarkerClicked(it) + } + } + } closeJumpToReadMarkerView.setOnClickListener { - visibility = View.GONE + visibility = View.INVISIBLE callback?.onClearReadMarkerClicked() } } fun render(show: Boolean, readMarkerId: String?) { - isVisible = show - if (readMarkerId != null) { - jumpToReadMarkerLabelView.text = span(resources.getString(R.string.room_jump_to_first_unread)) { - textDecorationLine = "underline" - onClick = { callback?.onJumpToReadMarkerClicked(readMarkerId) } - } + this.readMarkerId = readMarkerId + visibility = if(show){ + View.VISIBLE + }else { + View.INVISIBLE } - } 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 19dad458a5..9e9a147719 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 @@ -45,7 +45,6 @@ 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/ReadMarkerHelper.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/ReadMarkerHelper.kt index 85ad6201d3..7364c254a8 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/ReadMarkerHelper.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/ReadMarkerHelper.kt @@ -19,6 +19,7 @@ package im.vector.riotx.features.home.room.detail import androidx.recyclerview.widget.LinearLayoutManager import im.vector.riotx.core.di.ScreenScope +import im.vector.riotx.core.utils.createBackgroundHandler import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController import timber.log.Timber import javax.inject.Inject @@ -31,6 +32,7 @@ class ReadMarkerHelper @Inject constructor() { var callback: Callback? = null private var onReadMarkerLongDisplayed = false + private var jumpToReadMarkerVisible = false private var readMarkerVisible: Boolean = true private var state: RoomDetailViewState? = null @@ -75,23 +77,20 @@ class ReadMarkerHelper @Inject constructor() { val nonNullState = this.state ?: return val lastVisibleItem = layoutManager.findLastVisibleItemPosition() val readMarkerId = nonNullState.asyncRoomSummary()?.readMarkerId - if (readMarkerId == null) { - callback?.onJumpToReadMarkerVisibilityUpdate(false, null) - } - val positionOfReadMarker = timelineEventController.searchPositionOfEvent(readMarkerId) - if (positionOfReadMarker == null) { - if (nonNullState.timeline?.isLive == true && lastVisibleItem > 0) { - callback?.onJumpToReadMarkerVisibilityUpdate(true, readMarkerId) - } else { - callback?.onJumpToReadMarkerVisibilityUpdate(false, readMarkerId) - } + val newJumpToReadMarkerVisible = if (readMarkerId == null) { + false } else { - if (positionOfReadMarker > lastVisibleItem) { - callback?.onJumpToReadMarkerVisibilityUpdate(true, readMarkerId) + val positionOfReadMarker = timelineEventController.searchPositionOfEvent(readMarkerId) + if (positionOfReadMarker == null) { + nonNullState.timeline?.isLive == true && lastVisibleItem > 0 } else { - callback?.onJumpToReadMarkerVisibilityUpdate(false, readMarkerId) + positionOfReadMarker > lastVisibleItem } } + if (newJumpToReadMarkerVisible != jumpToReadMarkerVisible) { + jumpToReadMarkerVisible = newJumpToReadMarkerVisible + callback?.onJumpToReadMarkerVisibilityUpdate(jumpToReadMarkerVisible, readMarkerId) + } } 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 aadfbb9fcb..f490bf66ab 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 @@ -157,7 +157,7 @@ class RoomDetailFragment : } } - /** + /**x * Sanitize the display name. * * @param displayName the display name to sanitize @@ -373,22 +373,22 @@ class RoomDetailFragment : if (messageContent is MessageTextContent && messageContent.format == MessageType.FORMAT_MATRIX_HTML) { val parser = Parser.builder().build() val document = parser.parse(messageContent.formattedBody - ?: messageContent.body) + ?: messageContent.body) formattedBody = eventHtmlRenderer.render(document) } composerLayout.composerRelatedMessageContent.text = formattedBody - ?: nonFormattedBody + ?: nonFormattedBody updateComposerText(defaultContent) composerLayout.composerRelatedMessageActionIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), iconRes)) avatarRenderer.render(event.senderAvatar, event.root.senderId - ?: "", event.senderName, composerLayout.composerRelatedMessageAvatar) + ?: "", event.senderName, composerLayout.composerRelatedMessageAvatar) avatarRenderer.render(event.senderAvatar, - event.root.senderId ?: "", - event.senderName, - composerLayout.composerRelatedMessageAvatar) + event.root.senderId ?: "", + event.senderName, + composerLayout.composerRelatedMessageAvatar) composerLayout.expand { //need to do it here also when not using quick reply focusComposerAndShowKeyboard() @@ -426,9 +426,9 @@ class RoomDetailFragment : REQUEST_FILES_REQUEST_CODE, TAKE_IMAGE_REQUEST_CODE -> handleMediaIntent(data) REACTION_SELECT_REQUEST_CODE -> { val eventId = data.getStringExtra(EmojiReactionPickerActivity.EXTRA_EVENT_ID) - ?: return + ?: return val reaction = data.getStringExtra(EmojiReactionPickerActivity.EXTRA_REACTION_RESULT) - ?: return + ?: return //TODO check if already reacted with that? roomDetailViewModel.process(RoomDetailActions.SendReaction(reaction, eventId)) } @@ -487,26 +487,26 @@ class RoomDetailFragment : if (vectorPreferences.swipeToReplyIsEnabled()) { val swipeCallback = RoomMessageTouchHelperCallback(requireContext(), - R.drawable.ic_reply, - object : RoomMessageTouchHelperCallback.QuickReplayHandler { - override fun performQuickReplyOnHolder(model: EpoxyModel<*>) { - (model as? AbsMessageItem)?.attributes?.informationData?.let { - val eventId = it.eventId - roomDetailViewModel.process(RoomDetailActions.EnterReplyMode(eventId, composerLayout.composerEditText.text.toString())) - } - } + R.drawable.ic_reply, + object : RoomMessageTouchHelperCallback.QuickReplayHandler { + override fun performQuickReplyOnHolder(model: EpoxyModel<*>) { + (model as? AbsMessageItem)?.attributes?.informationData?.let { + val eventId = it.eventId + roomDetailViewModel.process(RoomDetailActions.EnterReplyMode(eventId, composerLayout.composerEditText.text.toString())) + } + } - override fun canSwipeModel(model: EpoxyModel<*>): Boolean { - return when (model) { - is MessageFileItem, - is MessageImageVideoItem, - is MessageTextItem -> { - return (model as AbsMessageItem).attributes.informationData.sendState == SendState.SYNCED - } - else -> false - } - } - }) + override fun canSwipeModel(model: EpoxyModel<*>): Boolean { + return when (model) { + is MessageFileItem, + is MessageImageVideoItem, + is MessageTextItem -> { + return (model as AbsMessageItem).attributes.informationData.sendState == SendState.SYNCED + } + else -> false + } + } + }) val touchHelper = ItemTouchHelper(swipeCallback) touchHelper.attachToRecyclerView(recyclerView) } @@ -948,12 +948,19 @@ class RoomDetailFragment : .show(requireActivity().supportFragmentManager, "DISPLAY_READ_RECEIPTS") } - override fun onReadMarkerLongBound(isDisplayed: Boolean) { - if (isDisplayed) { - readMarkerHelper.onReadMarkerLongDisplayed() + override fun onReadMarkerLongBound(readMarkerId: String, isDisplayed: Boolean) { + readMarkerHelper.onReadMarkerLongDisplayed() + val readMarkerIndex = timelineEventController.searchPositionOfEvent(readMarkerId) ?: return + val lastVisibleItemPosition = layoutManager.findLastVisibleItemPosition() + if (readMarkerIndex > lastVisibleItemPosition) { + return + } + val firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition() + val firstVisibleItem = timelineEventController.adapter.getModelAtPosition(firstVisibleItemPosition) + val nextReadMarkerId = when (firstVisibleItem) { + is BaseEventItem -> firstVisibleItem.getEventId() + else -> null } - val firstVisibleItem = layoutManager.findFirstVisibleItemPosition() - val nextReadMarkerId = timelineEventController.searchEventIdAtPosition(firstVisibleItem) if (nextReadMarkerId != null) { roomDetailViewModel.process(RoomDetailActions.SetReadMarkerAction(nextReadMarkerId)) } 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 72c6d67a7d..a863337cdd 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 @@ -58,7 +58,6 @@ import im.vector.riotx.core.utils.LiveEvent import im.vector.riotx.core.utils.subscribeLogError import im.vector.riotx.features.command.CommandParser import im.vector.riotx.features.command.ParsedCommand -import im.vector.riotx.features.home.room.detail.timeline.TimelineLayoutManagerHolder import im.vector.riotx.features.home.room.detail.timeline.helper.TimelineDisplayableEvents import im.vector.riotx.features.settings.VectorPreferences import io.reactivex.Observable @@ -72,7 +71,6 @@ import java.util.concurrent.TimeUnit class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: RoomDetailViewState, - private val timelineLayoutManagerHolder: TimelineLayoutManagerHolder, private val userPreferencesProvider: UserPreferencesProvider, private val vectorPreferences: VectorPreferences, private val session: Session @@ -117,8 +115,6 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro observeEventDisplayedActions() observeSummaryState() observeDrafts() - observeReadMarkerVisibility() - observeOwnState() room.rx().loadRoomMembersIfNeeded().subscribeLogError().disposeOnClear() timeline.start() setState { copy(timeline = this@RoomDetailViewModel.timeline) } @@ -182,23 +178,23 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro copy( // Create a sendMode from a draft and retrieve the TimelineEvent sendMode = when (draft) { - is UserDraft.REGULAR -> SendMode.REGULAR(draft.text) - is UserDraft.QUOTE -> { - room.getTimeLineEvent(draft.linkedEventId)?.let { timelineEvent -> - SendMode.QUOTE(timelineEvent, draft.text) - } - } - is UserDraft.REPLY -> { - room.getTimeLineEvent(draft.linkedEventId)?.let { timelineEvent -> - SendMode.REPLY(timelineEvent, draft.text) - } - } - is UserDraft.EDIT -> { - room.getTimeLineEvent(draft.linkedEventId)?.let { timelineEvent -> - SendMode.EDIT(timelineEvent, draft.text) - } - } - } ?: SendMode.REGULAR("") + is UserDraft.REGULAR -> SendMode.REGULAR(draft.text) + is UserDraft.QUOTE -> { + room.getTimeLineEvent(draft.linkedEventId)?.let { timelineEvent -> + SendMode.QUOTE(timelineEvent, draft.text) + } + } + is UserDraft.REPLY -> { + room.getTimeLineEvent(draft.linkedEventId)?.let { timelineEvent -> + SendMode.REPLY(timelineEvent, draft.text) + } + } + is UserDraft.EDIT -> { + room.getTimeLineEvent(draft.linkedEventId)?.let { timelineEvent -> + SendMode.EDIT(timelineEvent, draft.text) + } + } + } ?: SendMode.REGULAR("") ) } } @@ -207,7 +203,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 @@ -342,7 +338,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro is SendMode.EDIT -> { //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 { @@ -351,13 +347,13 @@ 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") } @@ -368,7 +364,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) @@ -681,7 +677,15 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro } private fun handleSetReadMarkerAction(action: RoomDetailActions.SetReadMarkerAction) = withState { state -> - room.setReadMarker(action.eventId, callback = object : MatrixCallback {}) + var readMarkerId = action.eventId + val indexOfEvent = timeline.getIndexOfEvent(readMarkerId) + // force to set the read marker on the next event + if (indexOfEvent != null) { + timeline.getTimelineEventAtIndex(indexOfEvent - 1)?.root?.eventId?.also { eventIdOfNext -> + readMarkerId = eventIdOfNext + } + } + room.setReadMarker(readMarkerId, callback = object : MatrixCallback {}) } private fun handleMarkAllAsRead() { @@ -724,22 +728,6 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro } } - private fun observeReadMarkerVisibility() { - Observable - .combineLatest( - room.rx().liveReadMarker(), - room.rx().liveReadReceipt(), - BiFunction, Optional, Boolean> { readMarker, readReceipt -> - readMarker.getOrNull() != readReceipt.getOrNull() - } - ) - .subscribe { - setState { copy(readMarkerVisible = it) } - } - .disposeOnClear() - } - - override fun onCleared() { timeline.dispose() super.onCleared() diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewState.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewState.kt index 2609aed2e3..7549ffbb23 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewState.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewState.kt @@ -52,8 +52,7 @@ data class RoomDetailViewState( val tombstoneEvent: Event? = null, val tombstoneEventHandling: Async = Uninitialized, val syncState: SyncState = SyncState.IDLE, - val highlightedEventId: String? = null, - val readMarkerVisible: Boolean = false + val highlightedEventId: String? = null ) : MvRxState { constructor(args: RoomDetailArgs) : this(roomId = args.roomId, eventId = args.eventId) 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 525fb6cd6a..3dda4b333c 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 @@ -82,7 +82,7 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec interface ReadReceiptsCallback { fun onReadReceiptsClicked(readReceipts: List) - fun onReadMarkerLongBound(isDisplayed: Boolean) + fun onReadMarkerLongBound(readMarkerId: String, isDisplayed: Boolean) } interface UrlClickCallback { @@ -161,7 +161,7 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec synchronized(modelCache) { for (i in 0 until modelCache.size) { if (modelCache[i]?.eventId == viewState.highlightedEventId - || modelCache[i]?.eventId == eventIdToHighlight) { + || modelCache[i]?.eventId == eventIdToHighlight) { modelCache[i] = null } } @@ -232,8 +232,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) } } @@ -258,18 +258,24 @@ 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, readMarkerVisible, callback).also { + // Don't show read marker if it's on first item + val showReadMarker = if (currentPosition == 0 && event.hasReadMarker) { + false + } else { + readMarkerVisible + } + val eventModel = timelineItemFactory.create(event, nextEvent, eventIdToHighlight, showReadMarker, callback).also { it.id(event.localId) it.setOnVisibilityStateChanged(TimelineEventVisibilityStateChangedListener(callback, event)) } val mergedHeaderModel = mergedHeaderItemFactory.create(event, - nextEvent = nextEvent, - items = items, - addDaySeparator = addDaySeparator, - readMarkerVisible = readMarkerVisible, - currentPosition = currentPosition, - eventIdToHighlight = eventIdToHighlight, - callback = callback + nextEvent = nextEvent, + items = items, + addDaySeparator = addDaySeparator, + readMarkerVisible = readMarkerVisible, + currentPosition = currentPosition, + eventIdToHighlight = eventIdToHighlight, + callback = callback ) { requestModelBuild() } @@ -317,40 +323,23 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec realPosition++ } for (i in 0 until modelCache.size) { - val itemCache = modelCache[i] - if (itemCache?.eventId == eventId) { + val itemCache = modelCache[i] ?: continue + if (itemCache.eventId == eventId) { return realPosition } - if (itemCache?.eventModel != null) { + if (itemCache.eventModel != null && !mergedHeaderItemFactory.isCollapsed(itemCache.localId)) { realPosition++ } - if (itemCache?.mergedHeaderModel != null) { + if (itemCache.mergedHeaderModel != null) { realPosition++ } - if (itemCache?.formattedDayModel != null) { + if (itemCache.formattedDayModel != null) { realPosition++ } } return null } - fun searchEventIdAtPosition(position: Int): String? = synchronized(modelCache) { - var offsetValue = 0 - for (i in 0 until position) { - val itemCache = modelCache[i] - if (itemCache?.eventModel == null) { - offsetValue-- - } - if (itemCache?.mergedHeaderModel != null) { - offsetValue++ - } - if (itemCache?.formattedDayModel != null) { - offsetValue++ - } - } - return modelCache.getOrNull(position - offsetValue)?.eventId - } - fun isLoadingForward() = showingForwardLoader private data class CacheItemData( diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/TimelineLayoutManagerHolder.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/TimelineLayoutManagerHolder.kt deleted file mode 100644 index 429515798a..0000000000 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/TimelineLayoutManagerHolder.kt +++ /dev/null @@ -1,29 +0,0 @@ -/* - - * Copyright 2019 New Vector Ltd - * - * 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 im.vector.riotx.features.home.room.detail.timeline - -import androidx.recyclerview.widget.LinearLayoutManager -import im.vector.riotx.core.di.ScreenScope -import javax.inject.Inject - -@ScreenScope -class TimelineLayoutManagerHolder @Inject constructor() { - - lateinit var layoutManager: LinearLayoutManager - -} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt index ddf93410a1..b5e5f50e03 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt @@ -71,7 +71,8 @@ class MergedHeaderItemFactory @Inject constructor(private val sessionHolder: Act userId = mergedEvent.root.senderId ?: "", avatarUrl = senderAvatar, memberName = senderName ?: "", - eventId = mergedEvent.localId + localId = mergedEvent.localId, + eventId = mergedEvent.root.eventId ?: "" ) mergedData.add(data) } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/AbsMessageItem.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/AbsMessageItem.kt index 5bf8ba6e06..c7d75998a2 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/AbsMessageItem.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/AbsMessageItem.kt @@ -29,6 +29,7 @@ import androidx.core.view.children import androidx.core.view.isGone import androidx.core.view.isVisible import com.airbnb.epoxy.EpoxyAttribute +import com.airbnb.epoxy.VisibilityState import im.vector.matrix.android.api.session.room.send.SendState import im.vector.riotx.R import im.vector.riotx.core.resources.ColorProvider @@ -58,7 +59,7 @@ abstract class AbsMessageItem : BaseEventItem() { private val _readMarkerCallback = object : ReadMarkerView.Callback { override fun onReadMarkerLongBound(isDisplayed: Boolean) { - attributes.readReceiptsCallback?.onReadMarkerLongBound(isDisplayed) + attributes.readReceiptsCallback?.onReadMarkerLongBound(attributes.informationData.eventId, isDisplayed) } } @@ -157,6 +158,10 @@ abstract class AbsMessageItem : BaseEventItem() { return true } + override fun getEventId(): String? { + return attributes.informationData.eventId + } + protected open fun renderSendState(root: View, textView: TextView?, failureIndicator: ImageView? = null) { root.isClickable = attributes.informationData.sendState.isSent() val state = if (attributes.informationData.hasPendingEdits) SendState.UNSENT else attributes.informationData.sendState diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/BaseEventItem.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/BaseEventItem.kt index 5b0b64fea7..7727b07cd8 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/BaseEventItem.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/BaseEventItem.kt @@ -44,10 +44,13 @@ abstract class BaseEventItem : VectorEpoxyModel override fun bind(holder: H) { super.bind(holder) + holder holder.leftGuideline.setGuidelineBegin(leftGuideline) holder.checkableBackground.isChecked = highlighted } + abstract fun getEventId(): String? + abstract class BaseHolder(@IdRes val stubId: Int) : VectorEpoxyHolder() { val leftGuideline by bind(R.id.messageStartGuideline) val checkableBackground by bind(R.id.messageSelectedBackground) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/DefaultItem.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/DefaultItem.kt index a5ffb9a2ae..0e2d87512b 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/DefaultItem.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/DefaultItem.kt @@ -55,6 +55,10 @@ abstract class DefaultItem : BaseEventItem() { holder.readReceiptsView.render(informationData.readReceipts, avatarRenderer, _readReceiptsClickListener) } + override fun getEventId(): String? { + return informationData.eventId + } + override fun getViewType() = STUB_ID class Holder : BaseHolder(STUB_ID) { diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MergedHeaderItem.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MergedHeaderItem.kt index da19a88133..727a585d71 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MergedHeaderItem.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MergedHeaderItem.kt @@ -42,7 +42,7 @@ abstract class MergedHeaderItem : BaseEventItem() { private val _readMarkerCallback = object : ReadMarkerView.Callback { override fun onReadMarkerLongBound(isDisplayed: Boolean) { - attributes.readReceiptsCallback?.onReadMarkerLongBound(isDisplayed) + attributes.readReceiptsCallback?.onReadMarkerLongBound(attributes.readMarkerId ?: "", isDisplayed) } } @@ -89,8 +89,14 @@ abstract class MergedHeaderItem : BaseEventItem() { super.unbind(holder) } + + override fun getEventId(): String? { + return attributes.mergeData.firstOrNull()?.eventId + } + data class Data( - val eventId: Long, + val localId: Long, + val eventId: String, val userId: String, val memberName: String, val avatarUrl: String? diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/NoticeItem.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/NoticeItem.kt index 559b02aa61..c398970e8e 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/NoticeItem.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/NoticeItem.kt @@ -40,7 +40,7 @@ abstract class NoticeItem : BaseEventItem() { private val _readMarkerCallback = object : ReadMarkerView.Callback { override fun onReadMarkerLongBound(isDisplayed: Boolean) { - attributes.readReceiptsCallback?.onReadMarkerLongBound(isDisplayed) + attributes.readReceiptsCallback?.onReadMarkerLongBound(attributes.informationData.eventId, isDisplayed) } } @@ -69,6 +69,11 @@ abstract class NoticeItem : BaseEventItem() { super.unbind(holder) } + + override fun getEventId(): String? { + return attributes.informationData.eventId + } + override fun getViewType() = STUB_ID class Holder : BaseHolder(STUB_ID) { diff --git a/vector/src/main/res/layout/fragment_room_detail.xml b/vector/src/main/res/layout/fragment_room_detail.xml index f95afbd647..a9385f4eeb 100644 --- a/vector/src/main/res/layout/fragment_room_detail.xml +++ b/vector/src/main/res/layout/fragment_room_detail.xml @@ -124,10 +124,10 @@ android:id="@+id/jumpToReadMarkerView" android:layout_width="0dp" android:layout_height="wrap_content" - android:visibility="gone" + android:visibility="invisible" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/syncProgressBarWrap" /> + app:layout_constraintTop_toBottomOf="@id/syncStateView" />