From 13cb81b92f6b995a318f964df12351bfb23b9ae8 Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 30 Mar 2021 18:07:05 +0200 Subject: [PATCH] Timeline: fix SendState decoration + some filtering issues --- .../app/core/epoxy/TimelineEmptyItem.kt | 7 ++- .../timeline/TimelineEventController.kt | 51 +++++++++++++++---- .../timeline/factory/CallItemFactory.kt | 24 ++++----- .../timeline/factory/DefaultItemFactory.kt | 11 ++-- .../timeline/factory/EncryptedItemFactory.kt | 17 +++---- .../timeline/factory/EncryptionItemFactory.kt | 13 ++--- .../factory/MergedHeaderItemFactory.kt | 2 +- .../timeline/factory/MessageItemFactory.kt | 17 +++---- .../timeline/factory/NoticeItemFactory.kt | 17 +++---- .../factory/ReadReceiptsItemFactory.kt | 14 ++--- .../timeline/factory/RoomCreateItemFactory.kt | 13 +++-- .../timeline/factory/TimelineItemFactory.kt | 46 ++++++++--------- .../factory/TimelineItemFactoryParams.kt | 33 ++++++++++++ .../factory/VerificationItemFactory.kt | 41 +++++++-------- .../timeline/factory/WidgetItemFactory.kt | 19 +++---- .../helper/MessageInformationDataFactory.kt | 22 ++++---- .../TimelineControllerInterceptorHelper.kt | 23 ++++----- .../helper/TimelineEventVisibilityHelper.kt | 12 ++--- .../detail/timeline/item/BaseEventItem.kt | 1 + .../detail/timeline/item/ItemWithEvents.kt | 5 ++ .../detail/timeline/item/ReadReceiptsItem.kt | 2 + 21 files changed, 209 insertions(+), 181 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactoryParams.kt diff --git a/vector/src/main/java/im/vector/app/core/epoxy/TimelineEmptyItem.kt b/vector/src/main/java/im/vector/app/core/epoxy/TimelineEmptyItem.kt index 9c49a5d458..c51573bf21 100644 --- a/vector/src/main/java/im/vector/app/core/epoxy/TimelineEmptyItem.kt +++ b/vector/src/main/java/im/vector/app/core/epoxy/TimelineEmptyItem.kt @@ -26,12 +26,15 @@ import im.vector.app.features.home.room.detail.timeline.item.ItemWithEvents abstract class TimelineEmptyItem : VectorEpoxyModel(), ItemWithEvents { @EpoxyAttribute lateinit var eventId: String - @EpoxyAttribute var visible: Boolean = false + @EpoxyAttribute var notBlank: Boolean = false + + override fun isVisible() = false override fun bind(holder: Holder) { super.bind(holder) holder.view.updateLayoutParams { - this.height = if (visible) 1 else 0 + // Force height to 1px so scrolling works correctly + this.height = if (notBlank) 1 else 0 } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt index 972736fb2a..aff94f7157 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt @@ -32,9 +32,7 @@ import im.vector.app.core.extensions.localDateTime import im.vector.app.core.extensions.nextOrNull import im.vector.app.core.extensions.prevOrNull import im.vector.app.core.resources.UserPreferencesProvider -import im.vector.app.core.utils.DebouncedClickListener import im.vector.app.features.call.webrtc.WebRtcCallManager -import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.room.detail.RoomDetailAction import im.vector.app.features.home.room.detail.RoomDetailViewState import im.vector.app.features.home.room.detail.UnreadState @@ -47,6 +45,7 @@ import im.vector.app.features.home.room.detail.timeline.helper.TimelineControlle import im.vector.app.features.home.room.detail.timeline.helper.TimelineEventDiffUtilCallback import im.vector.app.features.home.room.detail.timeline.helper.TimelineEventVisibilityHelper import im.vector.app.features.home.room.detail.timeline.helper.TimelineEventVisibilityStateChangedListener +import im.vector.app.features.home.room.detail.timeline.factory.TimelineItemFactoryParams import im.vector.app.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider import im.vector.app.features.home.room.detail.timeline.item.AbsMessageItem import im.vector.app.features.home.room.detail.timeline.item.BasedMergedItem @@ -55,7 +54,6 @@ import im.vector.app.features.home.room.detail.timeline.item.DaySeparatorItem_ import im.vector.app.features.home.room.detail.timeline.item.MessageInformationData import im.vector.app.features.home.room.detail.timeline.item.ReadReceiptData import im.vector.app.features.home.room.detail.timeline.item.ReadReceiptsItem -import im.vector.app.features.home.room.detail.timeline.item.ReadReceiptsItem_ import im.vector.app.features.home.room.detail.timeline.item.SendStateDecoration import im.vector.app.features.home.room.detail.timeline.url.PreviewUrlRetriever import im.vector.app.features.media.ImageContentRenderer @@ -337,13 +335,22 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec return } val receiptsByEvents = getReadReceiptsByShownEvent() + val lastSentEventWithoutReadReceipts = searchLastSentEventWithoutReadReceipts(receiptsByEvents) (0 until modelCache.size).forEach { position -> val event = currentSnapshot[position] val nextEvent = currentSnapshot.nextOrNull(position) val prevEvent = currentSnapshot.prevOrNull(position) + val params = TimelineItemFactoryParams( + event = event, + prevEvent = prevEvent, + nextEvent = nextEvent, + highlightedEventId = eventIdToHighlight, + lastSentEventIdWithoutReadReceipts = lastSentEventWithoutReadReceipts, + callback = callback + ) // Should be build if not cached or if model should be refreshed if (modelCache[position] == null || modelCache[position]?.shouldTriggerBuild == true) { - modelCache[position] = buildCacheItem(event, nextEvent, prevEvent) + modelCache[position] = buildCacheItem(params) } val itemCachedData = modelCache[position] ?: return@forEach // Then update with additional models if needed @@ -351,15 +358,13 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec } } - private fun buildCacheItem(event: TimelineEvent, - nextEvent: TimelineEvent?, - prevEvent: TimelineEvent? - ): CacheItemData { + private fun buildCacheItem(params: TimelineItemFactoryParams): CacheItemData { + val event = params.event if (hasReachedInvite && hasUTD) { return CacheItemData(event.localId, event.root.eventId) } - updateUTDStates(event, nextEvent) - val eventModel = timelineItemFactory.create(event, prevEvent, nextEvent, eventIdToHighlight, callback).also { + updateUTDStates(event, params.nextEvent) + val eventModel = timelineItemFactory.create(params).also { it.id(event.localId) it.setOnVisibilityStateChanged(TimelineEventVisibilityStateChangedListener(callback, event)) } @@ -399,13 +404,37 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec ) } + private fun searchLastSentEventWithoutReadReceipts(receiptsByEvent: Map>): String? { + if (timeline?.isLive == false) { + // If timeline is not live we don't want to show SentStatus + return null + } + for (event in currentSnapshot) { + // If there is any RR on the event, we stop searching for Sent event + if (receiptsByEvent[event.eventId]?.isNotEmpty() == true) { + return null + } + // If the event is not shown, we go to the next one + if (!timelineEventVisibilityHelper.shouldShowEvent(event, eventIdToHighlight)) { + continue + } + // If the event is sent by us, we update the holder with the eventId and stop the search + if (event.root.senderId == session.myUserId && event.root.sendState.isSent()) { + return event.eventId + } + } + return null + } + private fun getReadReceiptsByShownEvent(): Map> { val receiptsByEvent = HashMap>() var lastShownEventId: String? = null val itr = currentSnapshot.listIterator(currentSnapshot.size) while (itr.hasPrevious()) { val event = itr.previous() - val currentReadReceipts = ArrayList(event.readReceipts) + val currentReadReceipts = ArrayList(event.readReceipts).filter { + it.user.userId != session.myUserId + } if (timelineEventVisibilityHelper.shouldShowEvent(event, eventIdToHighlight)) { lastShownEventId = event.eventId } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/CallItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/CallItemFactory.kt index 548f7a3b1c..3df9898078 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/CallItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/CallItemFactory.kt @@ -46,13 +46,11 @@ class CallItemFactory @Inject constructor( private val callManager: WebRtcCallManager ) { - fun create(event: TimelineEvent, - highlight: Boolean, - callback: TimelineEventController.Callback? - ): VectorEpoxyModel<*>? { + fun create(params: TimelineItemFactoryParams): VectorEpoxyModel<*>? { + val event = params.event if (event.root.eventId == null) return null val roomId = event.roomId - val informationData = messageInformationDataFactory.create(event, null, null) + val informationData = messageInformationDataFactory.create(params) val callSignalingContent = event.getCallSignallingContent() ?: return null val callId = callSignalingContent.callId ?: return null val call = callManager.getCallById(callId) @@ -68,8 +66,8 @@ class CallItemFactory @Inject constructor( callId = callId, callStatus = CallTileTimelineItem.CallStatus.IN_CALL, callKind = callKind, - callback = callback, - highlight = highlight, + callback = params.callback, + highlight = params.isHighlighted, informationData = informationData, isStillActive = call != null ) @@ -80,8 +78,8 @@ class CallItemFactory @Inject constructor( callId = callId, callStatus = CallTileTimelineItem.CallStatus.INVITED, callKind = callKind, - callback = callback, - highlight = highlight, + callback = params.callback, + highlight = params.isHighlighted, informationData = informationData, isStillActive = call != null ) @@ -92,8 +90,8 @@ class CallItemFactory @Inject constructor( callId = callId, callStatus = CallTileTimelineItem.CallStatus.REJECTED, callKind = callKind, - callback = callback, - highlight = highlight, + callback = params.callback, + highlight = params.isHighlighted, informationData = informationData, isStillActive = false ) @@ -104,8 +102,8 @@ class CallItemFactory @Inject constructor( callId = callId, callStatus = CallTileTimelineItem.CallStatus.ENDED, callKind = callKind, - callback = callback, - highlight = highlight, + callback = params.callback, + highlight = params.isHighlighted, informationData = informationData, isStillActive = false ) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/DefaultItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/DefaultItemFactory.kt index 5f5d3f5156..db7b84ed06 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/DefaultItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/DefaultItemFactory.kt @@ -25,7 +25,6 @@ import im.vector.app.features.home.room.detail.timeline.helper.MessageInformatio import im.vector.app.features.home.room.detail.timeline.item.DefaultItem import im.vector.app.features.home.room.detail.timeline.item.DefaultItem_ import im.vector.app.features.home.room.detail.timeline.item.MessageInformationData -import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import javax.inject.Inject class DefaultItemFactory @Inject constructor(private val avatarSizeProvider: AvatarSizeProvider, @@ -51,16 +50,14 @@ class DefaultItemFactory @Inject constructor(private val avatarSizeProvider: Ava .attributes(attributes) } - fun create(event: TimelineEvent, - highlight: Boolean, - callback: TimelineEventController.Callback?, - throwable: Throwable? = null): DefaultItem { + fun create(params: TimelineItemFactoryParams, throwable: Throwable? = null): DefaultItem { + val event = params.event val text = if (throwable == null) { stringProvider.getString(R.string.rendering_event_error_type_of_event_not_handled, event.root.getClearType()) } else { stringProvider.getString(R.string.rendering_event_error_exception, event.root.eventId) } - val informationData = informationDataFactory.create(event, null, null) - return create(text, informationData, highlight, callback) + val informationData = informationDataFactory.create(params) + return create(text, informationData, params.isHighlighted, params.callback) } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/EncryptedItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/EncryptedItemFactory.kt index b531e08359..82d3dea311 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/EncryptedItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/EncryptedItemFactory.kt @@ -21,7 +21,6 @@ import im.vector.app.core.epoxy.VectorEpoxyModel import im.vector.app.core.resources.ColorProvider import im.vector.app.core.resources.DrawableProvider import im.vector.app.core.resources.StringProvider -import im.vector.app.features.home.room.detail.timeline.TimelineEventController import im.vector.app.features.home.room.detail.timeline.helper.AvatarSizeProvider import im.vector.app.features.home.room.detail.timeline.helper.MessageInformationDataFactory import im.vector.app.features.home.room.detail.timeline.helper.MessageItemAttributesFactory @@ -33,7 +32,6 @@ import me.gujun.android.span.span import org.matrix.android.sdk.api.session.crypto.MXCryptoError import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toModel -import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent import javax.inject.Inject @@ -46,11 +44,8 @@ class EncryptedItemFactory @Inject constructor(private val messageInformationDat private val attributesFactory: MessageItemAttributesFactory, private val vectorPreferences: VectorPreferences) { - fun create(event: TimelineEvent, - prevEvent: TimelineEvent?, - nextEvent: TimelineEvent?, - highlight: Boolean, - callback: TimelineEventController.Callback?): VectorEpoxyModel<*>? { + fun create(params: TimelineItemFactoryParams): VectorEpoxyModel<*>? { + val event = params.event event.root.eventId ?: return null return when { @@ -109,14 +104,14 @@ class EncryptedItemFactory @Inject constructor(private val messageInformationDat } } - val informationData = messageInformationDataFactory.create(event, prevEvent, nextEvent) - val attributes = attributesFactory.create(event.root.content.toModel(), informationData, callback) + val informationData = messageInformationDataFactory.create(params) + val attributes = attributesFactory.create(event.root.content.toModel(), informationData, params.callback) return MessageTextItem_() .leftGuideline(avatarSizeProvider.leftGuideline) - .highlighted(highlight) + .highlighted(params.isHighlighted) .attributes(attributes) .message(spannableStr) - .movementMethod(createLinkMovementMethod(callback)) + .movementMethod(createLinkMovementMethod(params.callback)) } else -> null } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/EncryptionItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/EncryptionItemFactory.kt index 68716a3eba..1d30136f27 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/EncryptionItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/EncryptionItemFactory.kt @@ -19,7 +19,6 @@ package im.vector.app.features.home.room.detail.timeline.factory import im.vector.app.R import im.vector.app.core.resources.StringProvider import im.vector.app.features.home.room.detail.timeline.MessageColorProvider -import im.vector.app.features.home.room.detail.timeline.TimelineEventController import im.vector.app.features.home.room.detail.timeline.helper.AvatarSizeProvider import im.vector.app.features.home.room.detail.timeline.helper.MessageInformationDataFactory import im.vector.app.features.home.room.detail.timeline.helper.MessageItemAttributesFactory @@ -28,7 +27,6 @@ import im.vector.app.features.home.room.detail.timeline.item.StatusTileTimelineI import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.toModel -import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM import org.matrix.android.sdk.internal.crypto.model.event.EncryptionEventContent import javax.inject.Inject @@ -41,15 +39,14 @@ class EncryptionItemFactory @Inject constructor( private val avatarSizeProvider: AvatarSizeProvider, private val session: Session) { - fun create(event: TimelineEvent, - highlight: Boolean, - callback: TimelineEventController.Callback?): StatusTileTimelineItem? { + fun create(params: TimelineItemFactoryParams): StatusTileTimelineItem? { + val event = params.event if (!event.root.isStateEvent()) { return null } val algorithm = event.root.getClearContent().toModel()?.algorithm - val informationData = informationDataFactory.create(event, null, null) - val attributes = messageItemAttributesFactory.create(null, informationData, callback) + val informationData = informationDataFactory.create(params) + val attributes = messageItemAttributesFactory.create(null, informationData, params.callback) val isSafeAlgorithm = algorithm == MXCRYPTO_ALGORITHM_MEGOLM val title: String @@ -86,7 +83,7 @@ class EncryptionItemFactory @Inject constructor( readReceiptsCallback = attributes.readReceiptsCallback ) ) - .highlighted(highlight) + .highlighted(params.isHighlighted) .leftGuideline(avatarSizeProvider.leftGuideline) } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt index 4e4a7fce02..e9340feaca 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt @@ -86,7 +86,7 @@ private val timelineEventVisibilityHelper: TimelineEventVisibilityHelper) { eventIdToHighlight: String?, requestModelBuild: () -> Unit, callback: TimelineEventController.Callback?): MergedMembershipEventsItem_? { - val prevSameTypeEvents = timelineEventVisibilityHelper.prevSameTypeEvents(items, currentPosition, 2) + val prevSameTypeEvents = timelineEventVisibilityHelper.prevSameTypeEvents(items, currentPosition, 2, eventIdToHighlight) return if (prevSameTypeEvents.isEmpty()) { null } else { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt index e969998613..0f214ffb13 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt @@ -85,7 +85,6 @@ import org.matrix.android.sdk.api.session.room.model.message.OPTION_TYPE_BUTTONS import org.matrix.android.sdk.api.session.room.model.message.OPTION_TYPE_POLL import org.matrix.android.sdk.api.session.room.model.message.getFileName import org.matrix.android.sdk.api.session.room.model.message.getFileUrl -import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent import org.matrix.android.sdk.api.util.MimeTypes import org.matrix.android.sdk.internal.crypto.attachments.toElementToDecrypt @@ -118,15 +117,13 @@ class MessageItemFactory @Inject constructor( pillsPostProcessorFactory.create(roomId) } - fun create(event: TimelineEvent, - prevEvent: TimelineEvent?, - nextEvent: TimelineEvent?, - highlight: Boolean, - callback: TimelineEventController.Callback? - ): VectorEpoxyModel<*>? { + fun create(params: TimelineItemFactoryParams): VectorEpoxyModel<*>? { + val event = params.event + val highlight = params.isHighlighted + val callback = params.callback event.root.eventId ?: return null roomId = event.roomId - val informationData = messageInformationDataFactory.create(event, prevEvent, nextEvent) + val informationData = messageInformationDataFactory.create(params) if (event.root.isRedacted()) { // message is redacted val attributes = messageItemAttributesFactory.create(null, informationData, callback) @@ -142,7 +139,7 @@ class MessageItemFactory @Inject constructor( || event.isEncrypted() && event.root.content.toModel()?.relatesTo?.type == RelationType.REPLACE ) { // This is an edit event, we should display it when debugging as a notice event - return noticeItemFactory.create(event, highlight, callback) + return noticeItemFactory.create(params) } val attributes = messageItemAttributesFactory.create(messageContent, informationData, callback) @@ -158,7 +155,7 @@ class MessageItemFactory @Inject constructor( is MessageAudioContent -> buildAudioMessageItem(messageContent, informationData, highlight, attributes) is MessageVerificationRequestContent -> buildVerificationRequestMessageItem(messageContent, informationData, highlight, callback, attributes) is MessageOptionsContent -> buildOptionsMessageItem(messageContent, informationData, highlight, callback, attributes) - is MessagePollResponseContent -> noticeItemFactory.create(event, highlight, callback) + is MessagePollResponseContent -> noticeItemFactory.create(params) else -> buildNotHandledMessageItem(messageContent, informationData, highlight, callback, attributes) } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/NoticeItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/NoticeItemFactory.kt index dfabf96199..e757b6b47b 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/NoticeItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/NoticeItemFactory.kt @@ -17,13 +17,11 @@ package im.vector.app.features.home.room.detail.timeline.factory import im.vector.app.features.home.AvatarRenderer -import im.vector.app.features.home.room.detail.timeline.TimelineEventController import im.vector.app.features.home.room.detail.timeline.format.NoticeEventFormatter import im.vector.app.features.home.room.detail.timeline.helper.AvatarSizeProvider import im.vector.app.features.home.room.detail.timeline.helper.MessageInformationDataFactory import im.vector.app.features.home.room.detail.timeline.item.NoticeItem import im.vector.app.features.home.room.detail.timeline.item.NoticeItem_ -import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import javax.inject.Inject class NoticeItemFactory @Inject constructor(private val eventFormatter: NoticeEventFormatter, @@ -31,24 +29,23 @@ class NoticeItemFactory @Inject constructor(private val eventFormatter: NoticeEv private val informationDataFactory: MessageInformationDataFactory, private val avatarSizeProvider: AvatarSizeProvider) { - fun create(event: TimelineEvent, - highlight: Boolean, - callback: TimelineEventController.Callback?): NoticeItem? { + fun create(params: TimelineItemFactoryParams): NoticeItem? { + val event = params.event val formattedText = eventFormatter.format(event) ?: return null - val informationData = informationDataFactory.create(event, null, null) + val informationData = informationDataFactory.create(params) val attributes = NoticeItem.Attributes( avatarRenderer = avatarRenderer, informationData = informationData, noticeText = formattedText, itemLongClickListener = { view -> - callback?.onEventLongClicked(informationData, null, view) ?: false + params.callback?.onEventLongClicked(informationData, null, view) ?: false }, - readReceiptsCallback = callback, - avatarClickListener = { callback?.onAvatarClicked(informationData) } + readReceiptsCallback = params.callback, + avatarClickListener = { params.callback?.onAvatarClicked(informationData) } ) return NoticeItem_() .leftGuideline(avatarSizeProvider.leftGuideline) - .highlighted(highlight) + .highlighted(params.isHighlighted) .attributes(attributes) } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/ReadReceiptsItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/ReadReceiptsItemFactory.kt index a3e8541b05..1d015d1bca 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/ReadReceiptsItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/ReadReceiptsItemFactory.kt @@ -22,27 +22,21 @@ import im.vector.app.features.home.room.detail.timeline.TimelineEventController import im.vector.app.features.home.room.detail.timeline.item.ReadReceiptData import im.vector.app.features.home.room.detail.timeline.item.ReadReceiptsItem import im.vector.app.features.home.room.detail.timeline.item.ReadReceiptsItem_ -import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.room.model.ReadReceipt import javax.inject.Inject -class ReadReceiptsItemFactory @Inject constructor(private val session: Session, - private val avatarRenderer: AvatarRenderer) { +class ReadReceiptsItemFactory @Inject constructor(private val avatarRenderer: AvatarRenderer) { fun create(eventId: String, readReceipts: List, callback: TimelineEventController.Callback?): ReadReceiptsItem? { + if (readReceipts.isEmpty()) { + return null + } val readReceiptsData = readReceipts - .asSequence() - .filter { - it.user.userId != session.myUserId - } .map { ReadReceiptData(it.user.userId, it.user.avatarUrl, it.user.displayName, it.originServerTs) } .toList() - if (readReceiptsData.isEmpty()) { - return null - } return ReadReceiptsItem_() .id("read_receipts_$eventId") .eventId(eventId) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/RoomCreateItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/RoomCreateItemFactory.kt index 31adbdb8a6..382962f98d 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/RoomCreateItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/RoomCreateItemFactory.kt @@ -20,13 +20,11 @@ import im.vector.app.R import im.vector.app.core.epoxy.VectorEpoxyModel import im.vector.app.core.resources.StringProvider import im.vector.app.core.resources.UserPreferencesProvider -import im.vector.app.features.home.room.detail.timeline.TimelineEventController import im.vector.app.features.home.room.detail.timeline.item.RoomCreateItem_ import me.gujun.android.span.span import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent -import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import javax.inject.Inject class RoomCreateItemFactory @Inject constructor(private val stringProvider: StringProvider, @@ -34,25 +32,26 @@ class RoomCreateItemFactory @Inject constructor(private val stringProvider: Stri private val session: Session, private val noticeItemFactory: NoticeItemFactory) { - fun create(event: TimelineEvent, callback: TimelineEventController.Callback?): VectorEpoxyModel<*>? { + fun create(params: TimelineItemFactoryParams): VectorEpoxyModel<*>? { + val event = params.event val createRoomContent = event.root.getClearContent().toModel() ?: return null - val predecessorId = createRoomContent.predecessor?.roomId ?: return defaultRendering(event, callback) + val predecessorId = createRoomContent.predecessor?.roomId ?: return defaultRendering(params) val roomLink = session.permalinkService().createRoomPermalink(predecessorId) ?: return null val text = span { +stringProvider.getString(R.string.room_tombstone_continuation_description) +"\n" span(stringProvider.getString(R.string.room_tombstone_predecessor_link)) { textDecorationLine = "underline" - onClick = { callback?.onRoomCreateLinkClicked(roomLink) } + onClick = { params.callback?.onRoomCreateLinkClicked(roomLink) } } } return RoomCreateItem_() .text(text) } - private fun defaultRendering(event: TimelineEvent, callback: TimelineEventController.Callback?): VectorEpoxyModel<*>? { + private fun defaultRendering(params: TimelineItemFactoryParams): VectorEpoxyModel<*>? { return if (userPreferencesProvider.shouldShowHiddenEvents()) { - noticeItemFactory.create(event, false, callback) + noticeItemFactory.create(params) } else { null } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt index df4eab0efe..09b17eb901 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt @@ -19,8 +19,6 @@ package im.vector.app.features.home.room.detail.timeline.factory import im.vector.app.core.epoxy.TimelineEmptyItem import im.vector.app.core.epoxy.TimelineEmptyItem_ import im.vector.app.core.epoxy.VectorEpoxyModel -import im.vector.app.core.resources.UserPreferencesProvider -import im.vector.app.features.home.room.detail.timeline.TimelineEventController import im.vector.app.features.home.room.detail.timeline.helper.TimelineEventVisibilityHelper import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent @@ -41,20 +39,16 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me /** * Reminder: nextEvent is older and prevEvent is newer. */ - fun create(event: TimelineEvent, - prevEvent: TimelineEvent?, - nextEvent: TimelineEvent?, - eventIdToHighlight: String?, - callback: TimelineEventController.Callback?): VectorEpoxyModel<*> { - val highlight = event.root.eventId == eventIdToHighlight + fun create(params: TimelineItemFactoryParams): VectorEpoxyModel<*> { + val event = params.event val computedModel = try { - if (!timelineEventVisibilityHelper.shouldShowEvent(event, eventIdToHighlight)) { - return buildEmptyItem(event, prevEvent, eventIdToHighlight) + if (!timelineEventVisibilityHelper.shouldShowEvent(event, params.highlightedEventId)) { + return buildEmptyItem(event, params.prevEvent, params.highlightedEventId) } when (event.root.getClearType()) { - // Message items + // Message itemsX EventType.STICKER, - EventType.MESSAGE -> messageItemFactory.create(event, prevEvent, nextEvent, highlight, callback) + EventType.MESSAGE -> messageItemFactory.create(params) EventType.STATE_ROOM_TOMBSTONE, EventType.STATE_ROOM_NAME, EventType.STATE_ROOM_TOPIC, @@ -78,49 +72,49 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me EventType.CALL_SELECT_ANSWER, EventType.CALL_NEGOTIATE, EventType.REACTION, - EventType.STATE_ROOM_POWER_LEVELS -> noticeItemFactory.create(event, highlight, callback) + EventType.STATE_ROOM_POWER_LEVELS -> noticeItemFactory.create(params) EventType.STATE_ROOM_WIDGET_LEGACY, - EventType.STATE_ROOM_WIDGET -> widgetItemFactory.create(event, highlight, callback) - EventType.STATE_ROOM_ENCRYPTION -> encryptionItemFactory.create(event, highlight, callback) + EventType.STATE_ROOM_WIDGET -> widgetItemFactory.create(params) + EventType.STATE_ROOM_ENCRYPTION -> encryptionItemFactory.create(params) // State room create - EventType.STATE_ROOM_CREATE -> roomCreateItemFactory.create(event, callback) + EventType.STATE_ROOM_CREATE -> roomCreateItemFactory.create(params) // Calls EventType.CALL_INVITE, EventType.CALL_HANGUP, EventType.CALL_REJECT, - EventType.CALL_ANSWER -> callItemFactory.create(event, highlight, callback) + EventType.CALL_ANSWER -> callItemFactory.create(params) // Crypto EventType.ENCRYPTED -> { if (event.root.isRedacted()) { // Redacted event, let the MessageItemFactory handle it - messageItemFactory.create(event, prevEvent, nextEvent, highlight, callback) + messageItemFactory.create(params) } else { - encryptedItemFactory.create(event, prevEvent, nextEvent, highlight, callback) + encryptedItemFactory.create(params) } } EventType.KEY_VERIFICATION_CANCEL, EventType.KEY_VERIFICATION_DONE -> { - verificationConclusionItemFactory.create(event, highlight, callback) + verificationConclusionItemFactory.create(params) } // Unhandled event types else -> { // Should only happen when shouldShowHiddenEvents() settings is ON Timber.v("Type ${event.root.getClearType()} not handled") - defaultItemFactory.create(event, highlight, callback) + defaultItemFactory.create(params) } } } catch (throwable: Throwable) { Timber.e(throwable, "failed to create message item") - defaultItemFactory.create(event, highlight, callback, throwable) + defaultItemFactory.create(params, throwable) } - return computedModel ?: buildEmptyItem(event, prevEvent, eventIdToHighlight) + return computedModel ?: buildEmptyItem(event, params.prevEvent, params.highlightedEventId) } - private fun buildEmptyItem(timelineEvent: TimelineEvent, prevEvent: TimelineEvent?, eventIdToHighlight: String?): TimelineEmptyItem { - val makesEmptyItemVisible = prevEvent == null || timelineEventVisibilityHelper.shouldShowEvent(prevEvent, eventIdToHighlight) + private fun buildEmptyItem(timelineEvent: TimelineEvent, prevEvent: TimelineEvent?, highlightedEventId: String?): TimelineEmptyItem { + val isNotBlank = prevEvent == null || timelineEventVisibilityHelper.shouldShowEvent(prevEvent, highlightedEventId) return TimelineEmptyItem_() .id(timelineEvent.localId) .eventId(timelineEvent.eventId) - .visible(makesEmptyItemVisible) + .notBlank(isNotBlank) } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactoryParams.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactoryParams.kt new file mode 100644 index 0000000000..dfd9fd2370 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactoryParams.kt @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2021 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.app.features.home.room.detail.timeline.factory + +import im.vector.app.features.home.room.detail.timeline.TimelineEventController +import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent + +data class TimelineItemFactoryParams( + val event: TimelineEvent, + val prevEvent: TimelineEvent? = null, + val nextEvent: TimelineEvent? = null, + val highlightedEventId: String? = null , + val lastSentEventIdWithoutReadReceipts: String? = null , + val callback: TimelineEventController.Callback? = null +) { + + val isHighlighted: Boolean + get() = highlightedEventId == event.eventId +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VerificationItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VerificationItemFactory.kt index 960487140d..51951fdc8d 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VerificationItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VerificationItemFactory.kt @@ -20,7 +20,6 @@ import im.vector.app.core.epoxy.VectorEpoxyModel import im.vector.app.core.resources.StringProvider import im.vector.app.core.resources.UserPreferencesProvider import im.vector.app.features.home.room.detail.timeline.MessageColorProvider -import im.vector.app.features.home.room.detail.timeline.TimelineEventController import im.vector.app.features.home.room.detail.timeline.helper.AvatarSizeProvider import im.vector.app.features.home.room.detail.timeline.helper.MessageInformationDataFactory import im.vector.app.features.home.room.detail.timeline.helper.MessageItemAttributesFactory @@ -35,7 +34,6 @@ import org.matrix.android.sdk.api.session.events.model.RelationType import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.model.message.MessageRelationContent import org.matrix.android.sdk.api.session.room.model.message.MessageVerificationCancelContent -import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import javax.inject.Inject /** @@ -54,37 +52,35 @@ class VerificationItemFactory @Inject constructor( private val session: Session ) { - fun create(event: TimelineEvent, - highlight: Boolean, - callback: TimelineEventController.Callback? - ): VectorEpoxyModel<*>? { + fun create(params: TimelineItemFactoryParams): VectorEpoxyModel<*>? { + val event = params.event if (event.root.eventId == null) return null val relContent: MessageRelationContent = event.root.content.toModel() ?: event.root.getClearContent().toModel() - ?: return ignoredConclusion(event, highlight, callback) + ?: return ignoredConclusion(params) - if (relContent.relatesTo?.type != RelationType.REFERENCE) return ignoredConclusion(event, highlight, callback) + if (relContent.relatesTo?.type != RelationType.REFERENCE) return ignoredConclusion(params) val refEventId = relContent.relatesTo?.eventId - ?: return ignoredConclusion(event, highlight, callback) + ?: return ignoredConclusion(params) // If we cannot find the referenced request we do not display the done event val refEvent = session.getRoom(event.root.roomId ?: "")?.getTimeLineEvent(refEventId) - ?: return ignoredConclusion(event, highlight, callback) + ?: return ignoredConclusion(params) // If it's not a request ignore this event // if (refEvent.root.getClearContent().toModel() == null) return ignoredConclusion(event, highlight, callback) - val referenceInformationData = messageInformationDataFactory.create(refEvent, null, null) + val referenceInformationData = messageInformationDataFactory.create(TimelineItemFactoryParams(refEvent)) - val informationData = messageInformationDataFactory.create(event, null, null) - val attributes = messageItemAttributesFactory.create(null, informationData, callback) + val informationData = messageInformationDataFactory.create(params) + val attributes = messageItemAttributesFactory.create(null, informationData,params.callback) when (event.root.getClearType()) { EventType.KEY_VERIFICATION_CANCEL -> { // Is the request referenced is actually really cancelled? val cancelContent = event.root.getClearContent().toModel() - ?: return ignoredConclusion(event, highlight, callback) + ?: return ignoredConclusion(params) when (safeValueOf(cancelContent.code)) { CancelCode.MismatchedCommitment, @@ -107,22 +103,22 @@ class VerificationItemFactory @Inject constructor( readReceiptsCallback = attributes.readReceiptsCallback ) ) - .highlighted(highlight) + .highlighted(params.isHighlighted) .leftGuideline(avatarSizeProvider.leftGuideline) } - else -> return ignoredConclusion(event, highlight, callback) + else -> return ignoredConclusion(params) } } EventType.KEY_VERIFICATION_DONE -> { // Is the request referenced is actually really completed? if (referenceInformationData.referencesInfoData?.verificationStatus != VerificationState.DONE) { - return ignoredConclusion(event, highlight, callback) + return ignoredConclusion(params) } // We only tale the one sent by me if (informationData.sentByMe) { // We only display the done sent by the other user, the done send by me is ignored - return ignoredConclusion(event, highlight, callback) + return ignoredConclusion(params) } return StatusTileTimelineItem_() .attributes( @@ -140,18 +136,15 @@ class VerificationItemFactory @Inject constructor( readReceiptsCallback = attributes.readReceiptsCallback ) ) - .highlighted(highlight) + .highlighted(params.isHighlighted) .leftGuideline(avatarSizeProvider.leftGuideline) } } return null } - private fun ignoredConclusion(event: TimelineEvent, - highlight: Boolean, - callback: TimelineEventController.Callback? - ): VectorEpoxyModel<*>? { - if (userPreferencesProvider.shouldShowHiddenEvents()) return noticeItemFactory.create(event, highlight, callback) + private fun ignoredConclusion(params: TimelineItemFactoryParams): VectorEpoxyModel<*>? { + if (userPreferencesProvider.shouldShowHiddenEvents()) return noticeItemFactory.create(params) return null } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/WidgetItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/WidgetItemFactory.kt index a6a88a3444..1fc57489a5 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/WidgetItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/WidgetItemFactory.kt @@ -20,7 +20,6 @@ import im.vector.app.ActiveSessionDataSource import im.vector.app.R import im.vector.app.core.epoxy.VectorEpoxyModel import im.vector.app.core.resources.StringProvider -import im.vector.app.features.home.room.detail.timeline.TimelineEventController import im.vector.app.features.home.room.detail.timeline.helper.AvatarSizeProvider import im.vector.app.features.home.room.detail.timeline.helper.MessageInformationDataFactory import im.vector.app.features.home.room.detail.timeline.helper.MessageItemAttributesFactory @@ -29,7 +28,6 @@ import im.vector.app.features.home.room.detail.timeline.item.WidgetTileTimelineI import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.toModel -import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.session.widgets.model.WidgetContent import org.matrix.android.sdk.api.session.widgets.model.WidgetType import javax.inject.Inject @@ -47,25 +45,24 @@ class WidgetItemFactory @Inject constructor( private fun Event.isSentByCurrentUser() = senderId != null && senderId == currentUserId - fun create(event: TimelineEvent, - highlight: Boolean, - callback: TimelineEventController.Callback?): VectorEpoxyModel<*>? { + fun create(params: TimelineItemFactoryParams): VectorEpoxyModel<*>? { + val event = params.event val widgetContent: WidgetContent = event.root.getClearContent().toModel() ?: return null val previousWidgetContent: WidgetContent? = event.root.resolvedPrevContent().toModel() return when (WidgetType.fromString(widgetContent.type ?: previousWidgetContent?.type ?: "")) { - WidgetType.Jitsi -> createJitsiItem(event, callback, widgetContent, previousWidgetContent) + WidgetType.Jitsi -> createJitsiItem(params, widgetContent, previousWidgetContent) // There is lot of other widget types we could improve here - else -> noticeItemFactory.create(event, highlight, callback) + else -> noticeItemFactory.create(params) } } - private fun createJitsiItem(timelineEvent: TimelineEvent, - callback: TimelineEventController.Callback?, + private fun createJitsiItem(params: TimelineItemFactoryParams, widgetContent: WidgetContent, previousWidgetContent: WidgetContent?): VectorEpoxyModel<*> { - val informationData = informationDataFactory.create(timelineEvent, null, null) - val attributes = messageItemAttributesFactory.create(null, informationData, callback) + val timelineEvent = params.event + val informationData = informationDataFactory.create(params) + val attributes = messageItemAttributesFactory.create(null, informationData, params.callback) val disambiguatedDisplayName = timelineEvent.senderInfo.disambiguatedDisplayName val message = if (widgetContent.isActive()) { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt index abaa2ffa17..14dd311265 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt @@ -19,11 +19,11 @@ package im.vector.app.features.home.room.detail.timeline.helper import im.vector.app.core.date.DateFormatKind import im.vector.app.core.date.VectorDateFormatter import im.vector.app.core.extensions.localDateTime +import im.vector.app.features.home.room.detail.timeline.factory.TimelineItemFactoryParams import im.vector.app.features.home.room.detail.timeline.item.E2EDecoration import im.vector.app.features.home.room.detail.timeline.item.MessageInformationData import im.vector.app.features.home.room.detail.timeline.item.PollResponseData import im.vector.app.features.home.room.detail.timeline.item.ReactionInfoData -import im.vector.app.features.home.room.detail.timeline.item.ReadReceiptData import im.vector.app.features.home.room.detail.timeline.item.ReferencesInfoData import im.vector.app.features.home.room.detail.timeline.item.SendStateDecoration import im.vector.app.features.settings.VectorPreferences @@ -51,9 +51,10 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses private val dateFormatter: VectorDateFormatter, private val vectorPreferences: VectorPreferences) { - fun create(event: TimelineEvent, prevEvent: TimelineEvent?, nextEvent: TimelineEvent?): MessageInformationData { - // Non nullability has been tested before - val eventId = event.root.eventId!! + fun create(params: TimelineItemFactoryParams): MessageInformationData { + val event = params.event + val nextEvent = params.nextEvent + val eventId = event.eventId val date = event.root.localDateTime() val nextDate = nextEvent?.root?.localDateTime() @@ -76,9 +77,8 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses val isSentByMe = event.root.senderId == session.myUserId val sendStateDecoration = if (isSentByMe) { getSendStateDecoration( - eventSendState = event.root.sendState, - prevEventSendState = prevEvent?.root?.sendState, - anyReadReceipts = event.readReceipts.any { it.user.userId != session.myUserId }, + event = event, + lastSentEventWithoutReadReceipts = params.lastSentEventIdWithoutReadReceipts, isMedia = event.root.isAttachmentMessage() ) } else { @@ -122,15 +122,15 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses ) } - private fun getSendStateDecoration(eventSendState: SendState, - prevEventSendState: SendState?, - anyReadReceipts: Boolean, + private fun getSendStateDecoration(event: TimelineEvent, + lastSentEventWithoutReadReceipts: String?, isMedia: Boolean): SendStateDecoration { + val eventSendState = event.root.sendState return if (eventSendState.isSending()) { if (isMedia) SendStateDecoration.SENDING_MEDIA else SendStateDecoration.SENDING_NON_MEDIA } else if (eventSendState.hasFailed()) { SendStateDecoration.FAILED - } else if (eventSendState.isSent() && !prevEventSendState?.isSent().orFalse() && !anyReadReceipts) { + } else if (lastSentEventWithoutReadReceipts == event.eventId) { SendStateDecoration.SENT } else { SendStateDecoration.NONE diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineControllerInterceptorHelper.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineControllerInterceptorHelper.kt index 392cb0ae57..e8353ff264 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineControllerInterceptorHelper.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineControllerInterceptorHelper.kt @@ -19,7 +19,6 @@ package im.vector.app.features.home.room.detail.timeline.helper import com.airbnb.epoxy.EpoxyModel import com.airbnb.epoxy.VisibilityState import im.vector.app.core.epoxy.LoadingItem_ -import im.vector.app.core.epoxy.TimelineEmptyItem import im.vector.app.core.epoxy.TimelineEmptyItem_ import im.vector.app.core.resources.UserPreferencesProvider import im.vector.app.features.call.webrtc.WebRtcCallManager @@ -29,7 +28,6 @@ import im.vector.app.features.home.room.detail.timeline.item.CallTileTimelineIte import im.vector.app.features.home.room.detail.timeline.item.DaySeparatorItem import im.vector.app.features.home.room.detail.timeline.item.ItemWithEvents import im.vector.app.features.home.room.detail.timeline.item.TimelineReadMarkerItem_ -import im.vector.app.features.settings.VectorPreferences import org.matrix.android.sdk.api.session.room.timeline.Timeline import kotlin.reflect.KMutableProperty0 @@ -67,30 +65,29 @@ class TimelineControllerInterceptorHelper(private val positionOfReadMarker: KMut // Then iterate on models so we have the exact positions in the adapter modelsIterator.forEach { epoxyModel -> - if(epoxyModel !is TimelineEmptyItem){ - atLeastOneVisibleItemSinceLastDaySeparator = true - atLeastOneVisibleItemsBeforeReadMarker = true - } if (epoxyModel is ItemWithEvents) { + if (epoxyModel.isVisible()) { + atLeastOneVisibleItemSinceLastDaySeparator = true + atLeastOneVisibleItemsBeforeReadMarker = true + } epoxyModel.getEventIds().forEach { eventId -> adapterPositionMapping[eventId] = index - if (eventId == firstUnreadEventId && atLeastOneVisibleItemsBeforeReadMarker) { + if (eventId == firstUnreadEventId && atLeastOneVisibleItemsBeforeReadMarker && epoxyModel.canAppendReadMarker()) { modelsIterator.addReadMarkerItem(callback) index++ positionOfReadMarker.set(index) } } } - if(epoxyModel is DaySeparatorItem){ - if(!atLeastOneVisibleItemSinceLastDaySeparator){ + if (epoxyModel is DaySeparatorItem) { + if (!atLeastOneVisibleItemSinceLastDaySeparator) { modelsIterator.remove() return@forEach } atLeastOneVisibleItemSinceLastDaySeparator = false - } - else if (epoxyModel is CallTileTimelineItem) { + } else if (epoxyModel is CallTileTimelineItem) { val hasBeenRemoved = modelsIterator.removeCallItemIfNeeded(epoxyModel, callIds, showHiddenEvents) - if(!hasBeenRemoved){ + if (!hasBeenRemoved) { atLeastOneVisibleItemSinceLastDaySeparator = true } } @@ -126,7 +123,7 @@ class TimelineControllerInterceptorHelper(private val positionOfReadMarker: KMut val emptyItem = TimelineEmptyItem_() .id(epoxyModel.id()) .eventId(epoxyModel.attributes.informationData.eventId) - .visible(false) + .notBlank(false) add(emptyItem) } callIds.add(callId) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventVisibilityHelper.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventVisibilityHelper.kt index 54d76bc681..39c95a5915 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventVisibilityHelper.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventVisibilityHelper.kt @@ -29,7 +29,7 @@ import javax.inject.Inject class TimelineEventVisibilityHelper @Inject constructor(private val userPreferencesProvider: UserPreferencesProvider) { - fun nextSameTypeEvents(timelineEvents: List, index: Int, minSize: Int): List { + fun nextSameTypeEvents(timelineEvents: List, index: Int, minSize: Int, eventIdToHighlight: String?): List { if (index >= timelineEvents.size - 1) { return emptyList() } @@ -51,30 +51,30 @@ class TimelineEventVisibilityHelper @Inject constructor(private val userPreferen } else { nextSameDayEvents.subList(0, indexOfFirstDifferentEventType) } - val filteredSameTypeEvents = sameTypeEvents.filter { shouldShowEvent(it)} + val filteredSameTypeEvents = sameTypeEvents.filter { shouldShowEvent(it, eventIdToHighlight)} if (filteredSameTypeEvents.size < minSize) { return emptyList() } return filteredSameTypeEvents } - fun prevSameTypeEvents(timelineEvents: List, index: Int, minSize: Int): List { + fun prevSameTypeEvents(timelineEvents: List, index: Int, minSize: Int, eventIdToHighlight: String?): List { val prevSub = timelineEvents.subList(0, index + 1) return prevSub .reversed() .let { - nextSameTypeEvents(it, 0, minSize) + nextSameTypeEvents(it, 0, minSize, eventIdToHighlight) } .reversed() } - fun shouldShowEvent(timelineEvent: TimelineEvent, highlightEventId: String? = null): Boolean { + fun shouldShowEvent(timelineEvent: TimelineEvent, highlightedEventId: String?): Boolean { // If show hidden events is true we should always display something if (userPreferencesProvider.shouldShowHiddenEvents()) { return true } // We always show highlighted event - if (timelineEvent.eventId == highlightEventId) { + if (timelineEvent.eventId == highlightedEventId) { return true } if (!timelineEvent.isDisplayable()) { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/BaseEventItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/BaseEventItem.kt index aae1edbed1..85fcc306f6 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/BaseEventItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/BaseEventItem.kt @@ -44,6 +44,7 @@ abstract class BaseEventItem : VectorEpoxyModel @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) lateinit var dimensionConverter: DimensionConverter + @CallSuper override fun bind(holder: H) { super.bind(holder) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/ItemWithEvents.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/ItemWithEvents.kt index cf4211bb2c..eeb3826ccd 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/ItemWithEvents.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/ItemWithEvents.kt @@ -22,4 +22,9 @@ interface ItemWithEvents { * Will generally get only one, but it handles the merged items. */ fun getEventIds(): List + + fun canAppendReadMarker(): Boolean = true + + fun isVisible(): Boolean = true + } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/ReadReceiptsItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/ReadReceiptsItem.kt index 84b2662687..b88afb0598 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/ReadReceiptsItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/ReadReceiptsItem.kt @@ -33,6 +33,8 @@ abstract class ReadReceiptsItem : EpoxyModelWithHolder( @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) lateinit var avatarRenderer: AvatarRenderer @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) lateinit var clickListener: View.OnClickListener + override fun canAppendReadMarker(): Boolean = false + override fun getEventIds(): List = listOf(eventId) override fun bind(holder: Holder) {