From 5e07e96bdb8857de41b2bbfc99cc8dde4b39e2ed Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 19 Nov 2019 19:49:48 +0100 Subject: [PATCH] Read marker: start reworking how we manage it [WIP] --- .../session/room/timeline/TimelineEvent.kt | 3 +- .../database/helper/ChunkEntityHelper.kt | 16 +-- .../database/mapper/TimelineEventMapper.kt | 5 +- .../database/model/ReadMarkerEntity.kt | 5 - .../database/model/TimelineEventEntity.kt | 3 +- .../session/room/timeline/DefaultTimeline.kt | 23 +-- .../room/timeline/DefaultTimelineService.kt | 21 ++- .../room/timeline/TimelineHiddenReadMarker.kt | 133 ------------------ .../session/sync/RoomFullyReadHandler.kt | 18 +-- .../riotx/core/extensions/TimelineEvent.kt | 4 - .../riotx/core/ui/views/ReadMarkerView.kt | 89 ------------ .../home/room/detail/ReadMarkerHelper.kt | 35 ----- .../home/room/detail/RoomDetailAction.kt | 4 +- .../home/room/detail/RoomDetailFragment.kt | 36 +---- .../home/room/detail/RoomDetailViewModel.kt | 111 +++++++++------ .../home/room/detail/RoomDetailViewState.kt | 3 +- .../timeline/TimelineEventController.kt | 87 ++++++------ .../timeline/factory/DefaultItemFactory.kt | 3 +- .../timeline/factory/EncryptedItemFactory.kt | 3 +- .../factory/MergedHeaderItemFactory.kt | 11 -- .../timeline/factory/MessageItemFactory.kt | 5 +- .../timeline/factory/NoticeItemFactory.kt | 3 +- .../timeline/factory/TimelineItemFactory.kt | 13 +- .../helper/MessageInformationDataFactory.kt | 8 +- ...lineEventVisibilityStateChangedListener.kt | 13 ++ .../detail/timeline/item/AbsMessageItem.kt | 15 -- .../detail/timeline/item/BaseEventItem.kt | 2 - .../detail/timeline/item/MergedHeaderItem.kt | 20 --- .../timeline/item/MessageInformationData.kt | 4 +- .../room/detail/timeline/item/NoticeItem.kt | 18 --- .../timeline/item/TimelineReadMarkerItem.kt | 31 ++++ .../res/layout/item_timeline_event_base.xml | 13 +- .../item_timeline_event_base_noinfo.xml | 30 +--- .../res/layout/item_timeline_read_marker.xml | 18 +++ 34 files changed, 236 insertions(+), 570 deletions(-) delete mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineHiddenReadMarker.kt delete mode 100644 vector/src/main/java/im/vector/riotx/core/ui/views/ReadMarkerView.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/TimelineReadMarkerItem.kt create mode 100644 vector/src/main/res/layout/item_timeline_read_marker.xml diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineEvent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineEvent.kt index ad747efee9..ed7f49aa46 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineEvent.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineEvent.kt @@ -41,8 +41,7 @@ data class TimelineEvent( val isUniqueDisplayName: Boolean, val senderAvatar: String?, val annotations: EventAnnotationsSummary? = null, - val readReceipts: List = emptyList(), - val hasReadMarker: Boolean = false + val readReceipts: List = emptyList() ) { val metadata = HashMap() diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/ChunkEntityHelper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/ChunkEntityHelper.kt index e9ffa140c9..826b35254e 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/ChunkEntityHelper.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/ChunkEntityHelper.kt @@ -23,7 +23,6 @@ import im.vector.matrix.android.internal.database.mapper.asDomain import im.vector.matrix.android.internal.database.mapper.toEntity import im.vector.matrix.android.internal.database.model.ChunkEntity import im.vector.matrix.android.internal.database.model.EventAnnotationsSummaryEntity -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.TimelineEventEntity @@ -140,7 +139,7 @@ internal fun ChunkEntity.add(roomId: String, val senderId = event.senderId ?: "" val readReceiptsSummaryEntity = ReadReceiptsSummaryEntity.where(realm, eventId).findFirst() - ?: ReadReceiptsSummaryEntity(eventId, roomId) + ?: ReadReceiptsSummaryEntity(eventId, roomId) // Update RR for the sender of a new message with a dummy one @@ -168,7 +167,6 @@ internal fun ChunkEntity.add(roomId: String, it.roomId = roomId it.annotations = EventAnnotationsSummaryEntity.where(realm, eventId).findFirst() it.readReceipts = readReceiptsSummaryEntity - it.readMarker = ReadMarkerEntity.where(realm, roomId = roomId, eventId = eventId).findFirst() } val position = if (direction == PaginationDirection.FORWARDS) 0 else this.timelineEvents.size timelineEvents.add(position, eventEntity) @@ -176,14 +174,14 @@ internal fun ChunkEntity.add(roomId: String, internal fun ChunkEntity.lastDisplayIndex(direction: PaginationDirection, defaultValue: Int = 0): Int { return when (direction) { - PaginationDirection.FORWARDS -> forwardsDisplayIndex - PaginationDirection.BACKWARDS -> backwardsDisplayIndex - } ?: defaultValue + PaginationDirection.FORWARDS -> forwardsDisplayIndex + PaginationDirection.BACKWARDS -> backwardsDisplayIndex + } ?: defaultValue } internal fun ChunkEntity.lastStateIndex(direction: PaginationDirection, defaultValue: Int = 0): Int { return when (direction) { - PaginationDirection.FORWARDS -> forwardsStateIndex - PaginationDirection.BACKWARDS -> backwardsStateIndex - } ?: defaultValue + PaginationDirection.FORWARDS -> forwardsStateIndex + PaginationDirection.BACKWARDS -> backwardsStateIndex + } ?: defaultValue } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/TimelineEventMapper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/TimelineEventMapper.kt index 8046ecbff0..9959f940b6 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/TimelineEventMapper.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/TimelineEventMapper.kt @@ -36,7 +36,7 @@ internal class TimelineEventMapper @Inject constructor(private val readReceiptsS } return TimelineEvent( root = timelineEventEntity.root?.asDomain() - ?: Event("", timelineEventEntity.eventId), + ?: Event("", timelineEventEntity.eventId), annotations = timelineEventEntity.annotations?.asDomain(), localId = timelineEventEntity.localId, displayIndex = timelineEventEntity.root?.displayIndex ?: 0, @@ -45,8 +45,7 @@ internal class TimelineEventMapper @Inject constructor(private val readReceiptsS senderAvatar = timelineEventEntity.senderAvatar, readReceipts = readReceipts?.sortedByDescending { it.originServerTs - } ?: emptyList(), - hasReadMarker = timelineEventEntity.readMarker?.eventId?.isNotEmpty() == true + } ?: emptyList() ) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/ReadMarkerEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/ReadMarkerEntity.kt index 9e78c94f88..4d16d120d8 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/ReadMarkerEntity.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/ReadMarkerEntity.kt @@ -17,8 +17,6 @@ package im.vector.matrix.android.internal.database.model import io.realm.RealmObject -import io.realm.RealmResults -import io.realm.annotations.LinkingObjects import io.realm.annotations.PrimaryKey internal open class ReadMarkerEntity( @@ -27,8 +25,5 @@ internal open class ReadMarkerEntity( var eventId: String = "" ) : RealmObject() { - @LinkingObjects("readMarker") - val timelineEvent: RealmResults? = null - companion object } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/TimelineEventEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/TimelineEventEntity.kt index fd3a427781..235910b1ea 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/TimelineEventEntity.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/TimelineEventEntity.kt @@ -30,8 +30,7 @@ internal open class TimelineEventEntity(var localId: Long = 0, var isUniqueDisplayName: Boolean = false, var senderAvatar: String? = null, var senderMembershipEvent: EventEntity? = null, - var readReceipts: ReadReceiptsSummaryEntity? = null, - var readMarker: ReadMarkerEntity? = null + var readReceipts: ReadReceiptsSummaryEntity? = null ) : RealmObject() { @LinkingObjects("timelineEvents") 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 4127e43540..aa4bd42bf7 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 @@ -74,9 +74,8 @@ internal class DefaultTimeline( private val cryptoService: CryptoService, private val timelineEventMapper: TimelineEventMapper, private val settings: TimelineSettings, - private val hiddenReadReceipts: TimelineHiddenReadReceipts, - private val hiddenReadMarker: TimelineHiddenReadMarker -) : Timeline, TimelineHiddenReadReceipts.Delegate, TimelineHiddenReadMarker.Delegate { + private val hiddenReadReceipts: TimelineHiddenReadReceipts +) : Timeline, TimelineHiddenReadReceipts.Delegate { private companion object { val BACKGROUND_HANDLER = createBackgroundHandler("TIMELINE_DB_THREAD") @@ -197,7 +196,6 @@ internal class DefaultTimeline( if (settings.buildReadReceipts) { hiddenReadReceipts.start(realm, filteredEvents, nonFilteredEvents, this) } - hiddenReadMarker.start(realm, filteredEvents, nonFilteredEvents, this) isReady.set(true) } } @@ -217,7 +215,6 @@ internal class DefaultTimeline( if (this::filteredEvents.isInitialized) { filteredEvents.removeAllChangeListeners() } - hiddenReadMarker.dispose() if (settings.buildReadReceipts) { hiddenReadReceipts.dispose() } @@ -298,7 +295,7 @@ internal class DefaultTimeline( return hasMoreInCache(direction) || !hasReachedEnd(direction) } -// TimelineHiddenReadReceipts.Delegate + // TimelineHiddenReadReceipts.Delegate override fun rebuildEvent(eventId: String, readReceipts: List): Boolean { return rebuildEvent(eventId) { te -> @@ -310,19 +307,7 @@ internal class DefaultTimeline( postSnapshot() } -// TimelineHiddenReadMarker.Delegate - - override fun rebuildEvent(eventId: String, hasReadMarker: Boolean): Boolean { - return rebuildEvent(eventId) { te -> - te.copy(hasReadMarker = hasReadMarker) - } - } - - override fun onReadMarkerUpdated() { - postSnapshot() - } - -// Private methods ***************************************************************************** + // Private methods ***************************************************************************** private fun rebuildEvent(eventId: String, builder: (TimelineEvent) -> TimelineEvent): Boolean { return builtEventsIdMap[eventId]?.let { builtIndex -> 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 3bd67d38c3..d92dbd66be 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 @@ -53,17 +53,16 @@ internal class DefaultTimelineService @AssistedInject constructor(@Assisted priv override fun createTimeline(eventId: String?, settings: TimelineSettings): Timeline { return DefaultTimeline(roomId, - eventId, - monarchy.realmConfiguration, - taskExecutor, - contextOfEventTask, - clearUnlinkedEventsTask, - paginationTask, - cryptoService, - timelineEventMapper, - settings, - TimelineHiddenReadReceipts(readReceiptsSummaryMapper, roomId, settings), - TimelineHiddenReadMarker(roomId, settings) + eventId, + monarchy.realmConfiguration, + taskExecutor, + contextOfEventTask, + clearUnlinkedEventsTask, + paginationTask, + cryptoService, + timelineEventMapper, + settings, + TimelineHiddenReadReceipts(readReceiptsSummaryMapper, roomId, settings) ) } 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 deleted file mode 100644 index 4f80883bf9..0000000000 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineHiddenReadMarker.kt +++ /dev/null @@ -1,133 +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.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.RealmQuery -import io.realm.RealmResults - -/** - * This class is responsible for handling the read marker for hidden events. - * 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, - private val settings: TimelineSettings) { - - interface Delegate { - fun rebuildEvent(eventId: String, hasReadMarker: Boolean): Boolean - fun onReadMarkerUpdated() - } - - private var previousDisplayedEventId: String? = null - private var hiddenReadMarker: RealmResults? = null - - private lateinit var filteredEvents: RealmResults - private lateinit var nonFilteredEvents: RealmResults - private lateinit var delegate: Delegate - - private val readMarkerListener = OrderedRealmCollectionChangeListener> { readMarkers, changeSet -> - if (!readMarkers.isLoaded || !readMarkers.isValid) { - return@OrderedRealmCollectionChangeListener - } - var hasChange = false - if (changeSet.deletions.isNotEmpty()) { - previousDisplayedEventId?.also { - hasChange = delegate.rebuildEvent(it, false) - previousDisplayedEventId = null - } - } - val readMarker = readMarkers.firstOrNull() ?: return@OrderedRealmCollectionChangeListener - val hiddenEvent = readMarker.timelineEvent?.firstOrNull() - ?: return@OrderedRealmCollectionChangeListener - - val isLoaded = nonFilteredEvents.where() - .equalTo(TimelineEventEntityFields.EVENT_ID, hiddenEvent.eventId) - .findFirst() != null - - val displayIndex = hiddenEvent.root?.displayIndex - if (isLoaded && displayIndex != null) { - // Then we are looking for the first displayable event after the hidden one - val firstDisplayedEvent = filteredEvents.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() - } - } - - /** - * Start the realm query subscription. Has to be called on an HandlerThread - */ - fun start(realm: Realm, - filteredEvents: RealmResults, - nonFilteredEvents: RealmResults, - delegate: Delegate) { - this.filteredEvents = filteredEvents - this.nonFilteredEvents = nonFilteredEvents - 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). - hiddenReadMarker = ReadMarkerEntity.where(realm, roomId = roomId) - .isNotEmpty(ReadMarkerEntityFields.TIMELINE_EVENT) - .filterReceiptsWithSettings() - .findAllAsync() - .also { it.addChangeListener(readMarkerListener) } - } - - /** - * Dispose the realm query subscription. Has to be called on an HandlerThread - */ - fun dispose() { - 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 - } -} 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 853774460f..61ae8b9925 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,14 +16,10 @@ 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 im.vector.matrix.android.internal.session.room.read.FullyReadContent import io.realm.Realm import timber.log.Timber import javax.inject.Inject @@ -39,18 +35,8 @@ internal class RoomFullyReadHandler @Inject constructor() { RoomSummaryEntity.getOrCreate(realm, roomId).apply { readMarkerId = content.eventId } - // 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 { + ReadMarkerEntity.getOrCreate(realm, roomId).apply { this.eventId = content.eventId } - // Attach to timelineEvent if known - val timelineEventEntities = TimelineEventEntity.where(realm, roomId = roomId, eventId = content.eventId).findAll() - timelineEventEntities.forEach { it.readMarker = readMarkerEntity } } } diff --git a/vector/src/main/java/im/vector/riotx/core/extensions/TimelineEvent.kt b/vector/src/main/java/im/vector/riotx/core/extensions/TimelineEvent.kt index 387105c480..388ec9bebe 100644 --- a/vector/src/main/java/im/vector/riotx/core/extensions/TimelineEvent.kt +++ b/vector/src/main/java/im/vector/riotx/core/extensions/TimelineEvent.kt @@ -24,7 +24,3 @@ fun TimelineEvent.canReact(): Boolean { // Only event of type Event.EVENT_TYPE_MESSAGE are supported for the moment return root.getClearType() == EventType.MESSAGE && root.sendState == SendState.SYNCED && !root.isRedacted() } - -fun TimelineEvent.displayReadMarker(myUserId: String): Boolean { - return hasReadMarker && readReceipts.find { it.user.userId == myUserId } == null -} 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 deleted file mode 100644 index 0fb8b55250..0000000000 --- a/vector/src/main/java/im/vector/riotx/core/ui/views/ReadMarkerView.kt +++ /dev/null @@ -1,89 +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.core.ui.views - -import android.content.Context -import android.util.AttributeSet -import android.view.View -import android.view.animation.Animation -import android.view.animation.AnimationUtils -import im.vector.riotx.R -import kotlinx.coroutines.* - -private const val DELAY_IN_MS = 1_000L - -class ReadMarkerView @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyleAttr: Int = 0 -) : View(context, attrs, defStyleAttr) { - - interface Callback { - fun onReadMarkerLongBound(isDisplayed: Boolean) - } - - private var eventId: String? = null - private var callback: Callback? = null - private var callbackDispatcherJob: Job? = null - - fun bindView(eventId: String?, hasReadMarker: Boolean, displayReadMarker: Boolean, readMarkerCallback: Callback) { - this.eventId = eventId - this.callback = readMarkerCallback - if (displayReadMarker) { - startAnimation() - } else { - this.animation?.cancel() - this.visibility = INVISIBLE - } - if (hasReadMarker) { - callbackDispatcherJob = GlobalScope.launch(Dispatchers.Main) { - delay(DELAY_IN_MS) - callback?.onReadMarkerLongBound(displayReadMarker) - } - } - } - - fun unbind() { - this.callbackDispatcherJob?.cancel() - this.callback = null - this.eventId = null - this.animation?.cancel() - this.visibility = INVISIBLE - } - - private fun startAnimation() { - if (animation == null) { - animation = AnimationUtils.loadAnimation(context, R.anim.unread_marker_anim) - animation.startOffset = DELAY_IN_MS / 2 - animation.duration = DELAY_IN_MS / 2 - animation.setAnimationListener(object : Animation.AnimationListener { - override fun onAnimationStart(animation: Animation) { - } - - override fun onAnimationEnd(animation: Animation) { - visibility = INVISIBLE - } - - override fun onAnimationRepeat(animation: Animation) {} - }) - } - visibility = VISIBLE - animation.start() - } -} 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 7b3ebeb71c..98556cc7fa 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 @@ -28,49 +28,14 @@ class ReadMarkerHelper @Inject constructor() { lateinit var timelineEventController: TimelineEventController lateinit var layoutManager: LinearLayoutManager var callback: Callback? = null - - private var onReadMarkerLongDisplayed = false private var jumpToReadMarkerVisible = false - private var readMarkerVisible: Boolean = true private var state: RoomDetailViewState? = null - fun readMarkerVisible(): Boolean { - return readMarkerVisible - } - - fun onResume() { - onReadMarkerLongDisplayed = false - } - - fun onReadMarkerLongDisplayed() { - onReadMarkerLongDisplayed = true - } - fun updateWith(newState: RoomDetailViewState) { state = newState - checkReadMarkerVisibility() checkJumpToReadMarkerVisibility() } - fun onTimelineScrolled() { - checkJumpToReadMarkerVisibility() - } - - private fun checkReadMarkerVisibility() { - val nonNullState = this.state ?: return - val firstVisibleItem = layoutManager.findFirstVisibleItemPosition() - val lastVisibleItem = layoutManager.findLastVisibleItemPosition() - readMarkerVisible = if (!onReadMarkerLongDisplayed) { - true - } else { - if (nonNullState.timeline?.isLive == false) { - true - } else { - !(firstVisibleItem == 0 && lastVisibleItem > 0) - } - } - } - private fun checkJumpToReadMarkerVisibility() { val nonNullState = this.state ?: return val lastVisibleItem = layoutManager.findLastVisibleItemPosition() diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailAction.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailAction.kt index 0a6321dd57..c1743ae3fc 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailAction.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailAction.kt @@ -35,13 +35,15 @@ sealed class RoomDetailAction : VectorViewModelAction { data class RedactAction(val targetEventId: String, val reason: String? = "") : RoomDetailAction() data class UpdateQuickReactAction(val targetEventId: String, val selectedReaction: String, val add: Boolean) : RoomDetailAction() data class NavigateToEvent(val eventId: String, val highlight: Boolean) : RoomDetailAction() - data class SetReadMarkerAction(val eventId: String) : RoomDetailAction() object MarkAllAsRead : RoomDetailAction() data class DownloadFile(val eventId: String, val messageFileContent: MessageFileContent) : RoomDetailAction() data class HandleTombstoneEvent(val event: Event) : RoomDetailAction() object AcceptInvite : RoomDetailAction() object RejectInvite : RoomDetailAction() + object EnterTrackingUnreadMessagesState : RoomDetailAction() + object ExitTrackingUnreadMessagesState : RoomDetailAction() + data class EnterEditMode(val eventId: String, val text: String) : RoomDetailAction() data class EnterQuoteMode(val eventId: String, val text: String) : RoomDetailAction() data class EnterReplyMode(val eventId: String, val text: String) : RoomDetailAction() 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 31278a1fff..e0c43b9e74 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 @@ -292,6 +292,7 @@ class RoomDetailFragment @Inject constructor( } override fun onDestroy() { + roomDetailViewModel.handle(RoomDetailAction.ExitTrackingUnreadMessagesState) debouncer.cancelAll() super.onDestroy() } @@ -299,6 +300,7 @@ class RoomDetailFragment @Inject constructor( private fun setupJumpToBottomView() { jumpToBottomView.visibility = View.INVISIBLE jumpToBottomView.setOnClickListener { + roomDetailViewModel.handle(RoomDetailAction.ExitTrackingUnreadMessagesState) jumpToBottomView.visibility = View.INVISIBLE withState(roomDetailViewModel) { state -> if (state.timeline?.isLive == false) { @@ -428,7 +430,6 @@ class RoomDetailFragment @Inject constructor( } override fun onResume() { - readMarkerHelper.onResume() super.onResume() notificationDrawerManager.setCurrentRoom(roomDetailArgs.roomId) } @@ -484,13 +485,6 @@ class RoomDetailFragment @Inject constructor( recyclerView.adapter = timelineEventController.adapter recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() { - override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { - if (recyclerView.scrollState == RecyclerView.SCROLL_STATE_IDLE) { - updateJumpToBottomViewVisibility() - } - readMarkerHelper.onTimelineScrolled() - } - override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) { when (newState) { RecyclerView.SCROLL_STATE_IDLE -> { @@ -668,7 +662,7 @@ class RoomDetailFragment @Inject constructor( val inviter = state.asyncInviter() if (summary?.membership == Membership.JOIN) { scrollOnHighlightedEventCallback.timeline = state.timeline - timelineEventController.update(state, readMarkerHelper.readMarkerVisible()) + timelineEventController.update(state) inviteView.visibility = View.GONE val uid = session.myUserId val meMember = session.getRoom(state.roomId)?.getRoomMember(uid) @@ -1024,28 +1018,8 @@ class RoomDetailFragment @Inject constructor( .show(requireActivity().supportFragmentManager, "DISPLAY_READ_RECEIPTS") } - 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() - var nextReadMarkerId: String? = null - for (itemPosition in firstVisibleItemPosition until lastVisibleItemPosition) { - val timelineItem = timelineEventController.adapter.getModelAtPosition(itemPosition) - if (timelineItem is BaseEventItem) { - val eventId = timelineItem.getEventIds().firstOrNull() ?: continue - if (!LocalEcho.isLocalEchoId(eventId)) { - nextReadMarkerId = eventId - break - } - } - } - if (nextReadMarkerId != null) { - roomDetailViewModel.handle(RoomDetailAction.SetReadMarkerAction(nextReadMarkerId)) - } + override fun onReadMarkerDisplayed() { + roomDetailViewModel.handle(RoomDetailAction.EnterTrackingUnreadMessagesState) } // AutocompleteUserPresenter.Callback 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 a264e0d06c..e1ff991797 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 @@ -40,6 +40,7 @@ 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.tombstone.RoomTombstoneContent import im.vector.matrix.android.api.session.room.send.UserDraft +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.session.room.timeline.getTextEditableContent import im.vector.matrix.android.internal.crypto.attachments.toElementToDecrypt @@ -64,6 +65,7 @@ import org.commonmark.renderer.html.HtmlRenderer import timber.log.Timber import java.io.File import java.util.concurrent.TimeUnit +import java.util.concurrent.atomic.AtomicBoolean class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: RoomDetailViewState, userPreferencesProvider: UserPreferencesProvider, @@ -102,6 +104,9 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro // Slot to keep a pending uri during permission request var pendingUri: Uri? = null + private var trackUnreadMessages = AtomicBoolean(false) + private var mostRecentDisplayedEvent: TimelineEvent? = null + @AssistedInject.Factory interface Factory { fun create(initialState: RoomDetailViewState): RoomDetailViewModel @@ -120,6 +125,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro } init { + getSnapshotOfReadMarkerId() observeSyncState() observeRoomSummary() observeEventDisplayedActions() @@ -132,33 +138,47 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro override fun handle(action: RoomDetailAction) { when (action) { - is RoomDetailAction.SaveDraft -> handleSaveDraft(action) - is RoomDetailAction.SendMessage -> handleSendMessage(action) - is RoomDetailAction.SendMedia -> handleSendMedia(action) - is RoomDetailAction.TimelineEventTurnsVisible -> handleEventVisible(action) - is RoomDetailAction.TimelineEventTurnsInvisible -> handleEventInvisible(action) - is RoomDetailAction.LoadMoreTimelineEvents -> handleLoadMore(action) - is RoomDetailAction.SendReaction -> handleSendReaction(action) - is RoomDetailAction.AcceptInvite -> handleAcceptInvite() - is RoomDetailAction.RejectInvite -> handleRejectInvite() - is RoomDetailAction.RedactAction -> handleRedactEvent(action) - is RoomDetailAction.UndoReaction -> handleUndoReact(action) - is RoomDetailAction.UpdateQuickReactAction -> handleUpdateQuickReaction(action) - is RoomDetailAction.ExitSpecialMode -> handleExitSpecialMode(action) - is RoomDetailAction.EnterEditMode -> handleEditAction(action) - is RoomDetailAction.EnterQuoteMode -> handleQuoteAction(action) - is RoomDetailAction.EnterReplyMode -> handleReplyAction(action) - is RoomDetailAction.DownloadFile -> handleDownloadFile(action) - is RoomDetailAction.NavigateToEvent -> handleNavigateToEvent(action) - is RoomDetailAction.HandleTombstoneEvent -> handleTombstoneEvent(action) - is RoomDetailAction.ResendMessage -> handleResendEvent(action) - is RoomDetailAction.RemoveFailedEcho -> handleRemove(action) - is RoomDetailAction.ClearSendQueue -> handleClearSendQueue() - is RoomDetailAction.ResendAll -> handleResendAll() - is RoomDetailAction.SetReadMarkerAction -> handleSetReadMarkerAction(action) - is RoomDetailAction.MarkAllAsRead -> handleMarkAllAsRead() - is RoomDetailAction.ReportContent -> handleReportContent(action) - is RoomDetailAction.IgnoreUser -> handleIgnoreUser(action) + is RoomDetailAction.SaveDraft -> handleSaveDraft(action) + is RoomDetailAction.SendMessage -> handleSendMessage(action) + is RoomDetailAction.SendMedia -> handleSendMedia(action) + is RoomDetailAction.TimelineEventTurnsVisible -> handleEventVisible(action) + is RoomDetailAction.TimelineEventTurnsInvisible -> handleEventInvisible(action) + is RoomDetailAction.LoadMoreTimelineEvents -> handleLoadMore(action) + is RoomDetailAction.SendReaction -> handleSendReaction(action) + is RoomDetailAction.AcceptInvite -> handleAcceptInvite() + is RoomDetailAction.RejectInvite -> handleRejectInvite() + is RoomDetailAction.RedactAction -> handleRedactEvent(action) + is RoomDetailAction.UndoReaction -> handleUndoReact(action) + is RoomDetailAction.UpdateQuickReactAction -> handleUpdateQuickReaction(action) + is RoomDetailAction.ExitSpecialMode -> handleExitSpecialMode(action) + is RoomDetailAction.EnterEditMode -> handleEditAction(action) + is RoomDetailAction.EnterQuoteMode -> handleQuoteAction(action) + is RoomDetailAction.EnterReplyMode -> handleReplyAction(action) + is RoomDetailAction.DownloadFile -> handleDownloadFile(action) + is RoomDetailAction.NavigateToEvent -> handleNavigateToEvent(action) + is RoomDetailAction.HandleTombstoneEvent -> handleTombstoneEvent(action) + is RoomDetailAction.ResendMessage -> handleResendEvent(action) + is RoomDetailAction.RemoveFailedEcho -> handleRemove(action) + is RoomDetailAction.ClearSendQueue -> handleClearSendQueue() + is RoomDetailAction.ResendAll -> handleResendAll() + is RoomDetailAction.MarkAllAsRead -> handleMarkAllAsRead() + is RoomDetailAction.ReportContent -> handleReportContent(action) + is RoomDetailAction.IgnoreUser -> handleIgnoreUser(action) + is RoomDetailAction.EnterTrackingUnreadMessagesState -> handleEnterTrackingUnreadMessages() + is RoomDetailAction.ExitTrackingUnreadMessagesState -> handleExitTrackingUnreadMessages() + } + } + + private fun handleEnterTrackingUnreadMessages() { + trackUnreadMessages.set(true) + } + + private fun handleExitTrackingUnreadMessages() { + if (trackUnreadMessages.getAndSet(false)) { + mostRecentDisplayedEvent?.root?.eventId?.also { + room.setReadMarker(it, callback = object : MatrixCallback {}) + } + mostRecentDisplayedEvent = null } } @@ -685,26 +705,22 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro .buffer(1, TimeUnit.SECONDS) .filter { it.isNotEmpty() } .subscribeBy(onNext = { actions -> - val mostRecentEvent = actions.maxBy { it.event.displayIndex } - mostRecentEvent?.event?.root?.eventId?.let { eventId -> + val bufferedMostRecentDisplayedEvent = actions.maxBy { it.event.displayIndex }?.event ?: return@subscribeBy + val globalMostRecentDisplayedEvent = mostRecentDisplayedEvent + if (trackUnreadMessages.get()) { + if (globalMostRecentDisplayedEvent == null) { + mostRecentDisplayedEvent = bufferedMostRecentDisplayedEvent + } else if (bufferedMostRecentDisplayedEvent.displayIndex > globalMostRecentDisplayedEvent.displayIndex) { + mostRecentDisplayedEvent = bufferedMostRecentDisplayedEvent + } + } + bufferedMostRecentDisplayedEvent.root.eventId?.let { eventId -> room.setReadReceipt(eventId, callback = object : MatrixCallback {}) } }) .disposeOnClear() } - private fun handleSetReadMarkerAction(action: RoomDetailAction.SetReadMarkerAction) = withState { - 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() { room.markAllAsRead(object : MatrixCallback {}) } @@ -759,6 +775,19 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro } } + private fun getSnapshotOfReadMarkerId() { + room.rx().liveRoomSummary() + .unwrap() + .filter { it.readMarkerId != null } + .take(1) + .subscribe { roomSummary -> + setState { + copy(readMarkerIdSnapshot = roomSummary.readMarkerId) + } + } + .disposeOnClear() + } + private fun observeSummaryState() { asyncSubscribe(RoomDetailViewState::asyncRoomSummary) { summary -> if (summary.membership == Membership.INVITE) { 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 03110858a1..e476545aa8 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,7 +52,8 @@ data class RoomDetailViewState( val tombstoneEvent: Event? = null, val tombstoneEventHandling: Async = Uninitialized, val syncState: SyncState = SyncState.IDLE, - val highlightedEventId: String? = null + val highlightedEventId: String? = null, + val readMarkerIdSnapshot: 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 be2f1dd7e4..9614d2aba7 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,15 +31,10 @@ 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.core.utils.DimensionConverter -import im.vector.riotx.features.home.AvatarRenderer 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.TimelineEventDiffUtilCallback -import im.vector.riotx.features.home.room.detail.timeline.helper.TimelineEventVisibilityStateChangedListener -import im.vector.riotx.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider -import im.vector.riotx.features.home.room.detail.timeline.helper.nextOrNull +import im.vector.riotx.features.home.room.detail.timeline.helper.* import im.vector.riotx.features.home.room.detail.timeline.item.* import im.vector.riotx.features.media.ImageContentRenderer import im.vector.riotx.features.media.VideoContentRenderer @@ -50,8 +45,6 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec private val timelineItemFactory: TimelineItemFactory, private val timelineMediaSizeProvider: TimelineMediaSizeProvider, private val mergedHeaderItemFactory: MergedHeaderItemFactory, - private val avatarRenderer: AvatarRenderer, - private val dimensionConverter: DimensionConverter, @TimelineEventControllerHandler private val backgroundHandler: Handler ) : EpoxyController(backgroundHandler, backgroundHandler), Timeline.Listener, EpoxyController.Interceptor { @@ -86,7 +79,7 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec interface ReadReceiptsCallback { fun onReadReceiptsClicked(readReceipts: List) - fun onReadMarkerLongBound(readMarkerId: String, isDisplayed: Boolean) + fun onReadMarkerDisplayed() } interface UrlClickCallback { @@ -101,6 +94,7 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec private var currentSnapshot: List = emptyList() private var inSubmitList: Boolean = false private var timeline: Timeline? = null + private var readMarkerIdSnapshot: String? = null var callback: Callback? = null @@ -163,7 +157,7 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec } } - fun update(viewState: RoomDetailViewState, readMarkerVisible: Boolean) { + fun update(viewState: RoomDetailViewState) { if (timeline != viewState.timeline) { timeline = viewState.timeline timeline?.listener = this @@ -188,8 +182,8 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec eventIdToHighlight = viewState.highlightedEventId requestModelBuild = true } - if (this.readMarkerVisible != readMarkerVisible) { - this.readMarkerVisible = readMarkerVisible + if (this.readMarkerIdSnapshot != viewState.readMarkerIdSnapshot) { + this.readMarkerIdSnapshot = viewState.readMarkerIdSnapshot requestModelBuild = true } if (requestModelBuild) { @@ -197,7 +191,6 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec } } - private var readMarkerVisible: Boolean = false private var eventIdToHighlight: String? = null override fun onAttachedToRecyclerView(recyclerView: RecyclerView) { @@ -247,42 +240,40 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec private fun getModels(): List> { synchronized(modelCache) { + val readMarkerIdSnapshot = this.readMarkerIdSnapshot + val displayableReadMarkerId = if (readMarkerIdSnapshot != null) { + timeline?.getFirstDisplayableEventId(readMarkerIdSnapshot) + } else { + null + } (0 until modelCache.size).forEach { position -> - // Should be build if not cached or if cached but contains mergedHeader or formattedDay + // Should be build if not cached or if cached but contains additional models // 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] = buildItemModels(position, currentSnapshot) + if (modelCache[position] == null || modelCache[position]?.hasAdditionalModel() == true) { + modelCache[position] = buildItemModels(position, currentSnapshot, displayableReadMarkerId) } } - return modelCache - .map { - val eventModel = if (it == null || mergedHeaderItemFactory.isCollapsed(it.localId)) { - null - } else { - it.eventModel - } - listOf(eventModel, it?.mergedHeaderModel, it?.formattedDayModel) - } - .flatten() - .filterNotNull() } + return modelCache + .map { + val eventModel = if (it == null || mergedHeaderItemFactory.isCollapsed(it.localId)) { + null + } else { + it.eventModel + } + listOf(it?.readMarkerModel, eventModel, it?.mergedHeaderModel, it?.formattedDayModel) + } + .flatten() + .filterNotNull() } - private fun buildItemModels(currentPosition: Int, items: List): CacheItemData { + private fun buildItemModels(currentPosition: Int, items: List, displayableReadMarkerId: String?): CacheItemData { val event = items[currentPosition] val nextEvent = items.nextOrNull(currentPosition) val date = event.root.localDateTime() val nextDate = nextEvent?.root?.localDateTime() val addDaySeparator = date.toLocalDate() != nextDate?.toLocalDate() - // 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 { + val eventModel = timelineItemFactory.create(event, nextEvent, eventIdToHighlight, callback).also { it.id(event.localId) it.setOnVisibilityStateChanged(TimelineEventVisibilityStateChangedListener(callback, event)) } @@ -290,7 +281,6 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec nextEvent = nextEvent, items = items, addDaySeparator = addDaySeparator, - readMarkerVisible = readMarkerVisible, currentPosition = currentPosition, eventIdToHighlight = eventIdToHighlight, callback = callback @@ -298,8 +288,20 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec requestModelBuild() } val daySeparatorItem = buildDaySeparatorItem(addDaySeparator, date) + val readMarkerItem = buildReadMarkerItem(currentPosition, event, displayableReadMarkerId) + return CacheItemData(event.localId, event.root.eventId, eventModel, mergedHeaderModel, daySeparatorItem, readMarkerItem) + } - return CacheItemData(event.localId, event.root.eventId, eventModel, mergedHeaderModel, daySeparatorItem) + private fun buildReadMarkerItem(currentPosition: Int, event: TimelineEvent, displayableReadMarkerId: String?): TimelineReadMarkerItem? { + return if (currentPosition != 0 && event.root.eventId == displayableReadMarkerId) { + TimelineReadMarkerItem_() + .also { + it.id(event.localId) + it.setOnVisibilityStateChanged(ReadMarkerVisibilityStateChangedListener(callback)) + } + } else { + null + } } private fun buildDaySeparatorItem(addDaySeparator: Boolean, date: LocalDateTime): DaySeparatorItem? { @@ -342,6 +344,9 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec val eventId: String?, val eventModel: EpoxyModel<*>? = null, val mergedHeaderModel: MergedHeaderItem? = null, - val formattedDayModel: DaySeparatorItem? = null - ) + val formattedDayModel: DaySeparatorItem? = null, + val readMarkerModel: TimelineReadMarkerItem? = null + ) { + fun hasAdditionalModel() = mergedHeaderModel != null || formattedDayModel != null || readMarkerModel != null + } } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/DefaultItemFactory.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/DefaultItemFactory.kt index 1ae47f9c22..94d7812512 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/DefaultItemFactory.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/DefaultItemFactory.kt @@ -46,7 +46,6 @@ class DefaultItemFactory @Inject constructor(private val avatarSizeProvider: Ava fun create(event: TimelineEvent, highlight: Boolean, - readMarkerVisible: Boolean, callback: TimelineEventController.Callback?, exception: Exception? = null): DefaultItem { val text = if (exception == null) { @@ -54,7 +53,7 @@ class DefaultItemFactory @Inject constructor(private val avatarSizeProvider: Ava } else { "an exception occurred when rendering the event ${event.root.eventId}" } - val informationData = informationDataFactory.create(event, null, readMarkerVisible) + val informationData = informationDataFactory.create(event, null) return create(text, informationData, highlight, callback) } } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/EncryptedItemFactory.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/EncryptedItemFactory.kt index c7aca768dc..512fffa29e 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/EncryptedItemFactory.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/EncryptedItemFactory.kt @@ -42,7 +42,6 @@ class EncryptedItemFactory @Inject constructor(private val messageInformationDat fun create(event: TimelineEvent, nextEvent: TimelineEvent?, highlight: Boolean, - readMarkerVisible: Boolean, callback: TimelineEventController.Callback?): VectorEpoxyModel<*>? { event.root.eventId ?: return null @@ -66,7 +65,7 @@ class EncryptedItemFactory @Inject constructor(private val messageInformationDat // TODO This is not correct format for error, change it - val informationData = messageInformationDataFactory.create(event, nextEvent, readMarkerVisible) + val informationData = messageInformationDataFactory.create(event, nextEvent) val attributes = attributesFactory.create(null, informationData, callback) return MessageTextItem_() .leftGuideline(avatarSizeProvider.leftGuideline) 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 51364e24c9..a2e979a08d 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 @@ -36,7 +36,6 @@ class MergedHeaderItemFactory @Inject constructor(private val sessionHolder: Act nextEvent: TimelineEvent?, items: List, addDaySeparator: Boolean, - readMarkerVisible: Boolean, currentPosition: Int, eventIdToHighlight: String?, callback: TimelineEventController.Callback?, @@ -50,20 +49,12 @@ class MergedHeaderItemFactory @Inject constructor(private val sessionHolder: Act null } else { var highlighted = false - var readMarkerId: String? = null - var showReadMarker = false val mergedEvents = (prevSameTypeEvents + listOf(event)).asReversed() val mergedData = ArrayList(mergedEvents.size) mergedEvents.forEach { mergedEvent -> if (!highlighted && mergedEvent.root.eventId == eventIdToHighlight) { highlighted = true } - if (readMarkerId == null && mergedEvent.hasReadMarker) { - readMarkerId = mergedEvent.root.eventId - } - if (!showReadMarker && mergedEvent.hasReadMarker && readMarkerVisible) { - showReadMarker = true - } val senderAvatar = mergedEvent.senderAvatar val senderName = mergedEvent.getDisambiguatedDisplayName() val data = MergedHeaderItem.Data( @@ -96,8 +87,6 @@ class MergedHeaderItemFactory @Inject constructor(private val sessionHolder: Act mergeItemCollapseStates[event.localId] = it requestModelBuild() }, - readMarkerId = readMarkerId, - showReadMarker = isCollapsed && showReadMarker, readReceiptsCallback = callback ) MergedHeaderItem_() diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MessageItemFactory.kt index de2686de04..9c96f17022 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MessageItemFactory.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MessageItemFactory.kt @@ -69,12 +69,11 @@ class MessageItemFactory @Inject constructor( fun create(event: TimelineEvent, nextEvent: TimelineEvent?, highlight: Boolean, - readMarkerVisible: Boolean, callback: TimelineEventController.Callback? ): VectorEpoxyModel<*>? { event.root.eventId ?: return null - val informationData = messageInformationDataFactory.create(event, nextEvent, readMarkerVisible) + val informationData = messageInformationDataFactory.create(event, nextEvent) if (event.root.isRedacted()) { // message is redacted @@ -91,7 +90,7 @@ class MessageItemFactory @Inject constructor( || event.isEncrypted() && event.root.content.toModel()?.relatesTo?.type == RelationType.REPLACE ) { // This is an edit event, we should it when debugging as a notice event - return noticeItemFactory.create(event, highlight, readMarkerVisible, callback) + return noticeItemFactory.create(event, highlight, callback) } val attributes = messageItemAttributesFactory.create(messageContent, informationData, callback) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/NoticeItemFactory.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/NoticeItemFactory.kt index 8768da26cf..4ee90f82a9 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/NoticeItemFactory.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/NoticeItemFactory.kt @@ -34,10 +34,9 @@ class NoticeItemFactory @Inject constructor(private val eventFormatter: NoticeEv fun create(event: TimelineEvent, highlight: Boolean, - readMarkerVisible: Boolean, callback: TimelineEventController.Callback?): NoticeItem? { val formattedText = eventFormatter.format(event) ?: return null - val informationData = informationDataFactory.create(event, null, readMarkerVisible) + val informationData = informationDataFactory.create(event, null) val attributes = NoticeItem.Attributes( avatarRenderer = avatarRenderer, informationData = informationData, diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/TimelineItemFactory.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/TimelineItemFactory.kt index 618ca121c2..5b6dec9900 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/TimelineItemFactory.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/TimelineItemFactory.kt @@ -33,14 +33,13 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me fun create(event: TimelineEvent, nextEvent: TimelineEvent?, eventIdToHighlight: String?, - readMarkerVisible: Boolean, callback: TimelineEventController.Callback?): VectorEpoxyModel<*> { val highlight = event.root.eventId == eventIdToHighlight val computedModel = try { when (event.root.getClearType()) { EventType.STICKER, - EventType.MESSAGE -> messageItemFactory.create(event, nextEvent, highlight, readMarkerVisible, callback) + EventType.MESSAGE -> messageItemFactory.create(event, nextEvent, highlight, callback) // State and call EventType.STATE_ROOM_TOMBSTONE, EventType.STATE_ROOM_NAME, @@ -53,21 +52,21 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me EventType.CALL_ANSWER, EventType.REACTION, EventType.REDACTION, - EventType.ENCRYPTION -> noticeItemFactory.create(event, highlight, readMarkerVisible, callback) + EventType.ENCRYPTION -> noticeItemFactory.create(event, highlight, callback) // State room create EventType.STATE_ROOM_CREATE -> roomCreateItemFactory.create(event, callback) // Crypto EventType.ENCRYPTED -> { if (event.root.isRedacted()) { // Redacted event, let the MessageItemFactory handle it - messageItemFactory.create(event, nextEvent, highlight, readMarkerVisible, callback) + messageItemFactory.create(event, nextEvent, highlight, callback) } else { - encryptedItemFactory.create(event, nextEvent, highlight, readMarkerVisible, callback) + encryptedItemFactory.create(event, nextEvent, highlight, callback) } } // Unhandled event types (yet) - EventType.STATE_ROOM_THIRD_PARTY_INVITE -> defaultItemFactory.create(event, highlight, readMarkerVisible, callback) + EventType.STATE_ROOM_THIRD_PARTY_INVITE -> defaultItemFactory.create(event, highlight, callback) else -> { Timber.v("Type ${event.root.getClearType()} not handled") null @@ -75,7 +74,7 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me } } catch (e: Exception) { Timber.e(e, "failed to create message item") - defaultItemFactory.create(event, highlight, readMarkerVisible, callback, e) + defaultItemFactory.create(event, highlight, callback, e) } return (computedModel ?: EmptyItem_()) } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt index e44e657733..34e34fc7de 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt @@ -39,7 +39,7 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses private val dateFormatter: VectorDateFormatter, private val colorProvider: ColorProvider) { - fun create(event: TimelineEvent, nextEvent: TimelineEvent?, readMarkerVisible: Boolean): MessageInformationData { + fun create(event: TimelineEvent, nextEvent: TimelineEvent?): MessageInformationData { // Non nullability has been tested before val eventId = event.root.eventId!! @@ -63,8 +63,6 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses textColor = colorProvider.getColor(getColorFromUserId(event.root.senderId ?: "")) } - val displayReadMarker = readMarkerVisible && event.hasReadMarker - return MessageInformationData( eventId = eventId, senderId = event.root.senderId ?: "", @@ -88,9 +86,7 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses .map { ReadReceiptData(it.user.userId, it.user.avatarUrl, it.user.displayName, it.originServerTs) } - .toList(), - hasReadMarker = event.hasReadMarker, - displayReadMarker = displayReadMarker + .toList() ) } } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/TimelineEventVisibilityStateChangedListener.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/TimelineEventVisibilityStateChangedListener.kt index c2aaf482ae..7efbce0073 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/TimelineEventVisibilityStateChangedListener.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/TimelineEventVisibilityStateChangedListener.kt @@ -21,6 +21,19 @@ import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.riotx.core.epoxy.VectorEpoxyModel import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController +class ReadMarkerVisibilityStateChangedListener(private val callback: TimelineEventController.Callback?) + : VectorEpoxyModel.OnVisibilityStateChangedListener { + + private var dispatched: Boolean = false + + override fun onVisibilityStateChanged(visibilityState: Int) { + if (visibilityState == VisibilityState.VISIBLE && !dispatched) { + dispatched = true + callback?.onReadMarkerDisplayed() + } + } +} + class TimelineEventVisibilityStateChangedListener(private val callback: TimelineEventController.Callback?, private val event: TimelineEvent) : VectorEpoxyModel.OnVisibilityStateChangedListener { 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 2ca6bbfd37..713b60d4d8 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 @@ -27,7 +27,6 @@ import com.airbnb.epoxy.EpoxyAttribute import im.vector.matrix.android.api.session.room.send.SendState import im.vector.riotx.R import im.vector.riotx.core.resources.ColorProvider -import im.vector.riotx.core.ui.views.ReadMarkerView import im.vector.riotx.core.utils.DebouncedClickListener import im.vector.riotx.features.home.AvatarRenderer import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController @@ -50,13 +49,6 @@ abstract class AbsMessageItem : BaseEventItem() { attributes.readReceiptsCallback?.onReadReceiptsClicked(attributes.informationData.readReceipts) }) - private val _readMarkerCallback = object : ReadMarkerView.Callback { - - override fun onReadMarkerLongBound(isDisplayed: Boolean) { - attributes.readReceiptsCallback?.onReadMarkerLongBound(attributes.informationData.eventId, isDisplayed) - } - } - var reactionClickListener: ReactionButton.ReactedListener = object : ReactionButton.ReactedListener { override fun onReacted(reactionButton: ReactionButton) { attributes.reactionPillCallback?.onClickOnReactionPill(attributes.informationData, reactionButton.reactionString, true) @@ -110,12 +102,6 @@ abstract class AbsMessageItem : BaseEventItem() { attributes.avatarRenderer, _readReceiptsClickListener ) - holder.readMarkerView.bindView( - attributes.informationData.eventId, - attributes.informationData.hasReadMarker, - attributes.informationData.displayReadMarker, - _readMarkerCallback - ) val reactions = attributes.informationData.orderedReactionList if (!shouldShowReactionAtBottom() || reactions.isNullOrEmpty()) { @@ -138,7 +124,6 @@ abstract class AbsMessageItem : BaseEventItem() { } override fun unbind(holder: H) { - holder.readMarkerView.unbind() holder.readReceiptsView.unbind() super.unbind(holder) } 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 8543484b00..576e596f90 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 @@ -26,7 +26,6 @@ import im.vector.riotx.R import im.vector.riotx.core.epoxy.VectorEpoxyHolder import im.vector.riotx.core.epoxy.VectorEpoxyModel import im.vector.riotx.core.platform.CheckableView -import im.vector.riotx.core.ui.views.ReadMarkerView import im.vector.riotx.core.ui.views.ReadReceiptsView import im.vector.riotx.core.utils.DimensionConverter @@ -62,7 +61,6 @@ abstract class BaseEventItem : VectorEpoxyModel val leftGuideline by bind(R.id.messageStartGuideline) val checkableBackground by bind(R.id.messageSelectedBackground) val readReceiptsView by bind(R.id.readReceiptsView) - val readMarkerView by bind(R.id.readMarkerView) override fun bindView(itemView: View) { super.bindView(itemView) 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 01e82ddf6b..bbccb71ffd 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 @@ -25,7 +25,6 @@ import androidx.core.view.isVisible import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyModelClass import im.vector.riotx.R -import im.vector.riotx.core.ui.views.ReadMarkerView import im.vector.riotx.features.home.AvatarRenderer import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController @@ -39,13 +38,6 @@ abstract class MergedHeaderItem : BaseEventItem() { attributes.mergeData.distinctBy { it.userId } } - private val _readMarkerCallback = object : ReadMarkerView.Callback { - - override fun onReadMarkerLongBound(isDisplayed: Boolean) { - attributes.readReceiptsCallback?.onReadMarkerLongBound(attributes.readMarkerId ?: "", isDisplayed) - } - } - override fun getViewType() = STUB_ID override fun bind(holder: Holder) { @@ -77,16 +69,6 @@ abstract class MergedHeaderItem : BaseEventItem() { } // No read receipt for this item holder.readReceiptsView.isVisible = false - holder.readMarkerView.bindView( - attributes.readMarkerId, - !attributes.readMarkerId.isNullOrEmpty(), - attributes.showReadMarker, - _readMarkerCallback) - } - - override fun unbind(holder: Holder) { - holder.readMarkerView.unbind() - super.unbind(holder) } override fun getEventIds(): List { @@ -102,9 +84,7 @@ abstract class MergedHeaderItem : BaseEventItem() { ) data class Attributes( - val readMarkerId: String?, val isCollapsed: Boolean, - val showReadMarker: Boolean, val mergeData: List, val avatarRenderer: AvatarRenderer, val readReceiptsCallback: TimelineEventController.ReadReceiptsCallback? = null, diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MessageInformationData.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MessageInformationData.kt index 96c74ccb88..2dd581ce6f 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MessageInformationData.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MessageInformationData.kt @@ -33,9 +33,7 @@ data class MessageInformationData( val orderedReactionList: List? = null, val hasBeenEdited: Boolean = false, val hasPendingEdits: Boolean = false, - val readReceipts: List = emptyList(), - val hasReadMarker: Boolean = false, - val displayReadMarker: Boolean = false + val readReceipts: List = emptyList() ) : Parcelable @Parcelize 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 1f39ae3ca4..804990cc5c 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 @@ -22,7 +22,6 @@ import android.widget.TextView import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyModelClass import im.vector.riotx.R -import im.vector.riotx.core.ui.views.ReadMarkerView import im.vector.riotx.core.utils.DebouncedClickListener import im.vector.riotx.features.home.AvatarRenderer import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController @@ -37,12 +36,6 @@ abstract class NoticeItem : BaseEventItem() { attributes.readReceiptsCallback?.onReadReceiptsClicked(attributes.informationData.readReceipts) }) - private val _readMarkerCallback = object : ReadMarkerView.Callback { - - override fun onReadMarkerLongBound(isDisplayed: Boolean) { - attributes.readReceiptsCallback?.onReadMarkerLongBound(attributes.informationData.eventId, isDisplayed) - } - } override fun bind(holder: Holder) { super.bind(holder) @@ -56,17 +49,6 @@ abstract class NoticeItem : BaseEventItem() { ) holder.view.setOnLongClickListener(attributes.itemLongClickListener) holder.readReceiptsView.render(attributes.informationData.readReceipts, attributes.avatarRenderer, _readReceiptsClickListener) - holder.readMarkerView.bindView( - attributes.informationData.eventId, - attributes.informationData.hasReadMarker, - attributes.informationData.displayReadMarker, - _readMarkerCallback - ) - } - - override fun unbind(holder: Holder) { - holder.readMarkerView.unbind() - super.unbind(holder) } override fun getEventIds(): List { diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/TimelineReadMarkerItem.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/TimelineReadMarkerItem.kt new file mode 100644 index 0000000000..4d867156d3 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/TimelineReadMarkerItem.kt @@ -0,0 +1,31 @@ +/* + * 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.item + +import com.airbnb.epoxy.EpoxyModelClass +import im.vector.riotx.R +import im.vector.riotx.core.epoxy.VectorEpoxyHolder +import im.vector.riotx.core.epoxy.VectorEpoxyModel + +@EpoxyModelClass(layout = R.layout.item_timeline_read_marker) +abstract class TimelineReadMarkerItem : VectorEpoxyModel() { + + override fun bind(holder: Holder) { + } + + class Holder : VectorEpoxyHolder() +} diff --git a/vector/src/main/res/layout/item_timeline_event_base.xml b/vector/src/main/res/layout/item_timeline_event_base.xml index 50ed0aae23..ce47847550 100644 --- a/vector/src/main/res/layout/item_timeline_event_base.xml +++ b/vector/src/main/res/layout/item_timeline_event_base.xml @@ -11,7 +11,7 @@ android:id="@+id/messageSelectedBackground" android:layout_width="match_parent" android:layout_height="match_parent" - android:layout_alignBottom="@+id/readMarkerView" + android:layout_alignBottom="@+id/informationBottom" android:layout_alignParentTop="true" android:background="?riotx_highlighted_message_background" /> @@ -145,15 +145,4 @@ - - \ No newline at end of file diff --git a/vector/src/main/res/layout/item_timeline_event_base_noinfo.xml b/vector/src/main/res/layout/item_timeline_event_base_noinfo.xml index 583997577a..b72933b94f 100644 --- a/vector/src/main/res/layout/item_timeline_event_base_noinfo.xml +++ b/vector/src/main/res/layout/item_timeline_event_base_noinfo.xml @@ -53,31 +53,13 @@ - - - - - - - + android:layout_alignParentEnd="true" + android:layout_marginEnd="8dp" + android:layout_marginBottom="4dp" /> \ No newline at end of file diff --git a/vector/src/main/res/layout/item_timeline_read_marker.xml b/vector/src/main/res/layout/item_timeline_read_marker.xml new file mode 100644 index 0000000000..93150b45ea --- /dev/null +++ b/vector/src/main/res/layout/item_timeline_read_marker.xml @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file