From c19b52cded7c4968372ca81709c5ff93e53ad061 Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Tue, 25 Jan 2022 18:21:42 +0200 Subject: [PATCH] Enhance thread summary Fix deleted root thread messages when show deleted messages are enabled/disabled --- .../sdk/api/session/threads/ThreadDetails.kt | 3 +- .../database/helper/ThreadEventsHelper.kt | 68 +++++++++++++++---- .../internal/database/mapper/EventMapper.kt | 1 + .../room/prune/RedactionEventProcessor.kt | 1 + .../helper/TimelineEventVisibilityHelper.kt | 14 +++- 5 files changed, 70 insertions(+), 17 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/threads/ThreadDetails.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/threads/ThreadDetails.kt index 26e8688d34..dc62d4ae35 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/threads/ThreadDetails.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/threads/ThreadDetails.kt @@ -28,5 +28,6 @@ data class ThreadDetails( val threadSummarySenderInfo: SenderInfo? = null, val threadSummaryLatestTextMessage: String? = null, val lastMessageTimestamp: Long? = null, - var threadNotificationState: ThreadNotificationState = ThreadNotificationState.NO_NEW_MESSAGE + var threadNotificationState: ThreadNotificationState = ThreadNotificationState.NO_NEW_MESSAGE, + val isThread: Boolean = false ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt index 33829712c7..b54f6b3ecf 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt @@ -18,7 +18,6 @@ package org.matrix.android.sdk.internal.database.helper import io.realm.Realm import io.realm.RealmQuery -import io.realm.RealmResults import io.realm.Sort import org.matrix.android.sdk.api.session.threads.ThreadNotificationState import org.matrix.android.sdk.internal.database.mapper.asDomain @@ -27,10 +26,14 @@ import org.matrix.android.sdk.internal.database.model.EventEntity import org.matrix.android.sdk.internal.database.model.ReadReceiptEntity import org.matrix.android.sdk.internal.database.model.TimelineEventEntity import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields +import org.matrix.android.sdk.internal.database.query.find import org.matrix.android.sdk.internal.database.query.findIncludingEvent +import org.matrix.android.sdk.internal.database.query.findLastForwardChunkOfRoom import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.database.query.whereRoomId +private typealias ThreadSummary = Pair? + /** * Finds the root thread event and update it with the latest message summary along with the number * of threads included. If there is no root thread event no action is done @@ -39,19 +42,21 @@ internal fun Map.updateThreadSummaryIfNeeded( roomId: String, realm: Realm, currentUserId: String, shouldUpdateNotifications: Boolean = true) { - for ((rootThreadEventId, eventEntity) in this) { - eventEntity.findAllThreadsForRootEventId(eventEntity.realm, rootThreadEventId).let { - if (it.isNullOrEmpty()) return@let - val latestMessage = it.firstOrNull() + for ((rootThreadEventId, eventEntity) in this) { + eventEntity.threadSummaryInThread(eventEntity.realm, rootThreadEventId)?.let { threadSummary -> + + val numberOfMessages = threadSummary.first + val latestEventInThread = threadSummary.second // If this is a thread message, find its root event if exists val rootThreadEvent = if (eventEntity.isThread()) eventEntity.findRootThreadEvent() else eventEntity rootThreadEvent?.markEventAsRoot( - threadsCounted = it.size, - latestMessageTimelineEventEntity = latestMessage + threadsCounted = numberOfMessages, + latestMessageTimelineEventEntity = latestEventInThread ) + } } @@ -82,16 +87,49 @@ internal fun EventEntity.markEventAsRoot( threadSummaryLatestMessage = latestMessageTimelineEventEntity } +///** +// * Find all TimelineEventEntity that are threads bind to the Event with rootThreadEventId +// * @param rootThreadEventId The root eventId that will try to find bind threads +// */ +//internal fun EventEntity.findAllThreadsForRootEventId(realm: Realm, rootThreadEventId: String): RealmResults = +// TimelineEventEntity +// .whereRoomId(realm, roomId = roomId) +// .equalTo(TimelineEventEntityFields.ROOT.ROOT_THREAD_EVENT_ID, rootThreadEventId) +// .sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING) +// .findAll() + /** - * Find all TimelineEventEntity that are threads bind to the Event with rootThreadEventId - * @param rootThreadEventId The root eventId that will try to find bind threads + * Count the number of threads for the provided root thread eventId, and finds the latest event message + * @param rootThreadEventId The root eventId that will find the number of threads + * @return A ThreadSummary containing the counted threads and the latest event message */ -internal fun EventEntity.findAllThreadsForRootEventId(realm: Realm, rootThreadEventId: String): RealmResults = - TimelineEventEntity - .whereRoomId(realm, roomId = roomId) - .equalTo(TimelineEventEntityFields.ROOT.ROOT_THREAD_EVENT_ID, rootThreadEventId) - .sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING) - .findAll() +internal fun EventEntity.threadSummaryInThread(realm: Realm, rootThreadEventId: String): ThreadSummary { + + // Number of messages + val messages = TimelineEventEntity + .whereRoomId(realm, roomId = roomId) + .equalTo(TimelineEventEntityFields.ROOT.ROOT_THREAD_EVENT_ID, rootThreadEventId) + .count() + .toInt() + + if (messages <= 0) return null + + // Find latest thread event, we know it exists + var chunk = ChunkEntity.findLastForwardChunkOfRoom(realm, roomId) ?: return null + var result: TimelineEventEntity? = null + + // Iterate the chunk until we find our latest event + while (result == null) { + result = chunk.timelineEvents.sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING)?.firstOrNull { + it.root?.rootThreadEventId == rootThreadEventId + } + chunk = ChunkEntity.find(realm, roomId, nextToken = chunk.prevToken) ?: break + } + + result ?: return null + + return ThreadSummary(messages, result) +} /** * Find all TimelineEventEntity that are root threads for the specified room diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventMapper.kt index c10b9084ea..9c420e81fd 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventMapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventMapper.kt @@ -103,6 +103,7 @@ internal object EventMapper { it.mCryptoErrorReason = eventEntity.decryptionErrorReason it.threadDetails = ThreadDetails( isRootThread = eventEntity.isRootThread, + isThread = if (it.threadDetails?.isThread == true) true else eventEntity.isThread(), numberOfThreads = eventEntity.numberOfThreads, threadSummarySenderInfo = eventEntity.threadSummaryLatestMessage?.let { timelineEventEntity -> SenderInfo( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/prune/RedactionEventProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/prune/RedactionEventProcessor.kt index 5ae4007c63..ac06d674d0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/prune/RedactionEventProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/prune/RedactionEventProcessor.kt @@ -21,6 +21,7 @@ import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.LocalEcho import org.matrix.android.sdk.api.session.events.model.UnsignedData +import org.matrix.android.sdk.api.session.room.model.relation.RelationContent import org.matrix.android.sdk.internal.database.mapper.ContentMapper import org.matrix.android.sdk.internal.database.mapper.EventMapper import org.matrix.android.sdk.internal.database.model.EventEntity 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 815202a4e7..2cde1e0ef8 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 @@ -27,6 +27,7 @@ import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomMemberContent import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent +import timber.log.Timber import javax.inject.Inject class TimelineEventVisibilityHelper @Inject constructor(private val userPreferencesProvider: UserPreferencesProvider) { @@ -137,9 +138,19 @@ class TimelineEventVisibilityHelper @Inject constructor(private val userPreferen } private fun TimelineEvent.shouldBeHidden(rootThreadEventId: String?, isFromThreadTimeline: Boolean): Boolean { - if (root.isRedacted() && !userPreferencesProvider.shouldShowRedactedMessages()) { + + if (root.isRedacted() && !userPreferencesProvider.shouldShowRedactedMessages() && root.threadDetails?.isRootThread == false ) { return true } + + // We should not display deleted thread messages within the normal timeline + if (root.isRedacted() && + userPreferencesProvider.areThreadMessagesEnabled() && + root.threadDetails?.isThread == true){ + return true + } + + if (root.getRelationContent()?.type == RelationType.REPLACE) { return true } @@ -155,6 +166,7 @@ class TimelineEventVisibilityHelper @Inject constructor(private val userPreferen return true } + // Allow only the the threads within the rootThreadEventId along with the root event if (userPreferencesProvider.areThreadMessagesEnabled() && isFromThreadTimeline) { return if (root.getRootThreadEventId() == rootThreadEventId) { false