From 358a7d0ec41eee83adab2fe5134023b905d68814 Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Thu, 27 Jan 2022 13:22:04 +0200 Subject: [PATCH] Handle latest thread message & root thread edition to update thread summary and thread list appropriately --- .../session/room/timeline/TimelineService.kt | 7 ++++ .../sdk/api/session/threads/ThreadDetails.kt | 3 +- .../database/helper/ThreadEventsHelper.kt | 35 ++++++++++++------- .../EventRelationsAggregationProcessor.kt | 26 +++++++++++++- .../room/timeline/DefaultTimelineService.kt | 7 ++++ .../list/viewmodel/ThreadListController.kt | 3 +- .../list/viewmodel/ThreadListViewModel.kt | 1 + 7 files changed, 66 insertions(+), 16 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineService.kt index aefda755f1..2899e98da8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineService.kt @@ -86,6 +86,13 @@ interface TimelineService { */ fun isUserParticipatingInThread(rootThreadEventId: String): Boolean + /** + * Enhance the thread list with the edited events if needed + * @return the [LiveData] of [TimelineEvent] + */ + fun mapEventsWithEdition(threads: List): List + + /** * Marks the current thread as read. This is a local implementation * @param rootThreadEventId the eventId of the current thread 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 dc62d4ae35..fafe17b2c0 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 @@ -29,5 +29,6 @@ data class ThreadDetails( val threadSummaryLatestTextMessage: String? = null, val lastMessageTimestamp: Long? = null, var threadNotificationState: ThreadNotificationState = ThreadNotificationState.NO_NEW_MESSAGE, - val isThread: Boolean = false + val isThread: Boolean = false, + val lastRootThreadEdition: String? = null ) 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 cb83094c73..b0cbc607ca 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 @@ -19,9 +19,11 @@ package org.matrix.android.sdk.internal.database.helper import io.realm.Realm import io.realm.RealmQuery import io.realm.Sort +import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.session.threads.ThreadNotificationState import org.matrix.android.sdk.internal.database.mapper.asDomain import org.matrix.android.sdk.internal.database.model.ChunkEntity +import org.matrix.android.sdk.internal.database.model.EventAnnotationsSummaryEntity 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 @@ -88,17 +90,6 @@ 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() - /** * 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 @@ -124,7 +115,7 @@ internal fun EventEntity.threadSummaryInThread(realm: Realm, rootThreadEventId: result = chunk.timelineEvents.sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING)?.firstOrNull { it.root?.rootThreadEventId == rootThreadEventId } - chunk = ChunkEntity.find(realm, roomId, nextToken = chunk.prevToken) ?: break + chunk = ChunkEntity.find(realm, roomId, nextToken = chunk.prevToken) ?: break } result ?: return null @@ -139,7 +130,25 @@ internal fun TimelineEventEntity.Companion.findAllThreadsForRoomId(realm: Realm, TimelineEventEntity .whereRoomId(realm, roomId = roomId) .equalTo(TimelineEventEntityFields.ROOT.IS_ROOT_THREAD, true) - .sort("${TimelineEventEntityFields.ROOT.THREAD_SUMMARY_LATEST_MESSAGE}.${TimelineEventEntityFields.DISPLAY_INDEX}", Sort.DESCENDING) + .sort("${TimelineEventEntityFields.ROOT.THREAD_SUMMARY_LATEST_MESSAGE}.${TimelineEventEntityFields.ROOT.ORIGIN_SERVER_TS}", Sort.DESCENDING) + +internal fun List.mapEventsWithEdition(realm: Realm, roomId: String): List = + this.map { + EventAnnotationsSummaryEntity + .where(realm, roomId, eventId = it.eventId) + .findFirst() + ?.editSummary + ?.editions + ?.lastOrNull() + ?.eventId + ?.let { editedEventId -> + TimelineEventEntity.where(realm, roomId, eventId = editedEventId).findFirst()?.let { editedEvent -> + it.root.threadDetails = it.root.threadDetails?.copy(lastRootThreadEdition = editedEvent.root?.asDomain()?.getDecryptedTextSummary() + ?: "(edited)") + it + } ?: it + } ?: it + } /** * Find the number of all the local notifications for the specified room diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt index e1d2ed4e6b..56704474bc 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt @@ -40,6 +40,7 @@ import org.matrix.android.sdk.api.session.room.model.relation.ReactionContent import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent import org.matrix.android.sdk.internal.crypto.verification.toState +import org.matrix.android.sdk.internal.database.helper.findRootThreadEvent 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.EditAggregatedSummaryEntity @@ -64,7 +65,7 @@ import javax.inject.Inject internal class EventRelationsAggregationProcessor @Inject constructor( @UserId private val userId: String, private val stateEventDataSource: StateEventDataSource - ) : EventInsertLiveProcessor { +) : EventInsertLiveProcessor { private val allowedTypes = listOf( EventType.MESSAGE, @@ -302,6 +303,29 @@ internal class EventRelationsAggregationProcessor @Inject constructor( ) } } + + if(!isLocalEcho) { + val replaceEvent = TimelineEventEntity.where(realm, roomId, eventId).findFirst() + handleThreadSummaryEdition(editedEvent, replaceEvent, existingSummary?.editions) + } + } + + /** + * Check if the edition is on the latest thread event, and update it accordingly + */ + private fun handleThreadSummaryEdition(editedEvent: EventEntity?, + replaceEvent: TimelineEventEntity?, + editions: List?) { + replaceEvent ?: return + editedEvent ?: return + editedEvent.findRootThreadEvent()?.apply { + val threadSummaryEventId = threadSummaryLatestMessage?.eventId + if (editedEvent.eventId == threadSummaryEventId || editions?.any { it.eventId == threadSummaryEventId } == true) { + // The edition is for the latest event or for any event replaced, this is to handle multiple + // edits of the same latest event + threadSummaryLatestMessage = replaceEvent + } + } } private fun handleResponse(realm: Realm, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt index 200de10e86..cc6485698b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt @@ -37,6 +37,7 @@ import org.matrix.android.sdk.internal.database.RealmSessionProvider import org.matrix.android.sdk.internal.database.helper.findAllLocalThreadNotificationsForRoomId import org.matrix.android.sdk.internal.database.helper.findAllThreadsForRoomId import org.matrix.android.sdk.internal.database.helper.isUserParticipatingInThread +import org.matrix.android.sdk.internal.database.helper.mapEventsWithEdition import org.matrix.android.sdk.internal.database.lightweight.LightweightSettingsStorage import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper import org.matrix.android.sdk.internal.database.model.EventEntity @@ -157,6 +158,12 @@ internal class DefaultTimelineService @AssistedInject constructor( } } + override fun mapEventsWithEdition(threads: List): List { + return Realm.getInstance(monarchy.realmConfiguration).use { + threads.mapEventsWithEdition(it, roomId) + } + } + override suspend fun markThreadAsRead(rootThreadEventId: String) { monarchy.awaitTransaction { EventEntity.where( diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListController.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListController.kt index c123bceafb..8bc6bd73e9 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListController.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListController.kt @@ -60,6 +60,7 @@ class ThreadListController @Inject constructor( ?.forEach { timelineEvent -> val date = dateFormatter.format(timelineEvent.root.threadDetails?.lastMessageTimestamp, DateFormatKind.ROOM_LIST) val decryptionErrorMessage = stringProvider.getString(R.string.encrypted_message) + val lastRootThreadEdition = timelineEvent.root.threadDetails?.lastRootThreadEdition threadListItem { id(timelineEvent.eventId) avatarRenderer(host.avatarRenderer) @@ -68,7 +69,7 @@ class ThreadListController @Inject constructor( date(date) rootMessageDeleted(timelineEvent.root.isRedacted()) threadNotificationState(timelineEvent.root.threadDetails?.threadNotificationState ?: ThreadNotificationState.NO_NEW_MESSAGE) - rootMessage(timelineEvent.root.getDecryptedTextSummary() ?: decryptionErrorMessage) + rootMessage(lastRootThreadEdition ?: timelineEvent.root.getDecryptedTextSummary() ?: decryptionErrorMessage) lastMessage(timelineEvent.root.threadDetails?.threadSummaryLatestTextMessage ?: decryptionErrorMessage) lastMessageCounter(timelineEvent.root.threadDetails?.numberOfThreads.toString()) lastMessageMatrixItem(timelineEvent.root.threadDetails?.threadSummarySenderInfo?.toMatrixItem()) diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewModel.kt index 88751004a4..d82b5d6ccf 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewModel.kt @@ -61,6 +61,7 @@ class ThreadListViewModel @AssistedInject constructor(@Assisted val initialState private fun observeThreadsList() { room?.flow() ?.liveThreadList() + ?.map { room.mapEventsWithEdition(it) } ?.map { it.map { threadRootEvent -> val isParticipating = room.isUserParticipatingInThread(threadRootEvent.eventId)