From 99a906fe9b584e5adaeb4fdddd62a158bc1f2efc Mon Sep 17 00:00:00 2001 From: ClaireG Date: Fri, 22 Jul 2022 15:35:01 +0200 Subject: [PATCH] replyTo are not updated if the original message is edited (#6404) --- changelog.d/5546.bugfix | 1 + .../room/send/LocalEchoEventFactory.kt | 30 +++++--- .../session/room/timeline/DefaultTimeline.kt | 3 + .../room/timeline/DefaultTimelineService.kt | 3 + .../room/timeline/LoadTimelineStrategy.kt | 4 ++ .../session/room/timeline/TimelineChunk.kt | 69 ++++++++++++++++++- 6 files changed, 98 insertions(+), 12 deletions(-) create mode 100644 changelog.d/5546.bugfix diff --git a/changelog.d/5546.bugfix b/changelog.d/5546.bugfix new file mode 100644 index 0000000000..2dea6591ce --- /dev/null +++ b/changelog.d/5546.bugfix @@ -0,0 +1 @@ +ReplyTo are not updated if the original message is edited diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt index f9fa64ddfe..6c5df1f4e4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt @@ -574,17 +574,13 @@ internal class LocalEchoEventFactory @Inject constructor( return clock.epochMillis() } - /** - * Creates a reply to a regular timeline Event or a thread Event if needed. - */ - fun createReplyTextEvent( - roomId: String, + fun createReplyTextContent( eventReplied: TimelineEvent, replyText: CharSequence, autoMarkdown: Boolean, rootThreadEventId: String? = null, showInThread: Boolean - ): Event? { + ): MessageContent? { // Fallbacks and event representation // TODO Add error/warning logs when any of this is null val permalink = permalinkFactory.createPermalink(eventReplied.root, false) ?: return null @@ -610,7 +606,7 @@ internal class LocalEchoEventFactory @Inject constructor( val replyFallback = buildReplyFallback(body, userId, replyText.toString()) val eventId = eventReplied.root.eventId ?: return null - val content = MessageTextContent( + return MessageTextContent( msgType = MessageType.MSGTYPE_TEXT, format = MessageFormat.FORMAT_MATRIX_HTML, body = replyFallback, @@ -621,7 +617,23 @@ internal class LocalEchoEventFactory @Inject constructor( showInThread = showInThread ) ) - return createMessageEvent(roomId, content) + } + + /** + * Creates a reply to a regular timeline Event or a thread Event if needed. + */ + fun createReplyTextEvent( + roomId: String, + eventReplied: TimelineEvent, + replyText: CharSequence, + autoMarkdown: Boolean, + rootThreadEventId: String? = null, + showInThread: Boolean, + ): Event? { + val content = createReplyTextContent(eventReplied, replyText, autoMarkdown, rootThreadEventId, showInThread) + return content?.let { + createMessageEvent(roomId, it) + } } /** @@ -682,7 +694,7 @@ internal class LocalEchoEventFactory @Inject constructor( * In case of an edit of a reply the last content is not * himself a reply, but it will contain the fallbacks, so we have to trim them. */ - private fun bodyForReply(content: MessageContent?, isReply: Boolean): TextContent { + fun bodyForReply(content: MessageContent?, isReply: Boolean): TextContent { when (content?.msgType) { MessageType.MSGTYPE_EMOTE, MessageType.MSGTYPE_TEXT, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt index 4eaac67e5a..66bb8ae073 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt @@ -42,6 +42,7 @@ import org.matrix.android.sdk.api.settings.LightweightSettingsStorage import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask import org.matrix.android.sdk.internal.session.room.relation.threads.FetchThreadTimelineTask +import org.matrix.android.sdk.internal.session.room.send.LocalEchoEventFactory import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource import org.matrix.android.sdk.internal.session.sync.handler.room.ReadReceiptHandler import org.matrix.android.sdk.internal.session.sync.handler.room.ThreadsAwarenessHandler @@ -63,6 +64,7 @@ internal class DefaultTimeline( private val settings: TimelineSettings, private val coroutineDispatchers: MatrixCoroutineDispatchers, private val clock: Clock, + localEchoEventFactory: LocalEchoEventFactory, stateEventDataSource: StateEventDataSource, paginationTask: PaginationTask, getEventTask: GetContextOfEventTask, @@ -114,6 +116,7 @@ internal class DefaultTimeline( onNewTimelineEvents = this::onNewTimelineEvents, stateEventDataSource = stateEventDataSource, matrixCoroutineDispatchers = coroutineDispatchers, + localEchoEventFactory = localEchoEventFactory ) private var strategy: LoadTimelineStrategy = buildStrategy(LoadTimelineStrategy.Mode.Live) 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 53c0253876..e99a916d06 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 @@ -32,6 +32,7 @@ import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask import org.matrix.android.sdk.internal.session.room.relation.threads.FetchThreadTimelineTask +import org.matrix.android.sdk.internal.session.room.send.LocalEchoEventFactory import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource import org.matrix.android.sdk.internal.session.sync.handler.room.ReadReceiptHandler import org.matrix.android.sdk.internal.session.sync.handler.room.ThreadsAwarenessHandler @@ -55,6 +56,7 @@ internal class DefaultTimelineService @AssistedInject constructor( private val timelineEventDataSource: TimelineEventDataSource, private val clock: Clock, private val stateEventDataSource: StateEventDataSource, + private val localEchoEventFactory: LocalEchoEventFactory ) : TimelineService { @AssistedFactory @@ -82,6 +84,7 @@ internal class DefaultTimelineService @AssistedInject constructor( lightweightSettingsStorage = lightweightSettingsStorage, clock = clock, stateEventDataSource = stateEventDataSource, + localEchoEventFactory = localEchoEventFactory ) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LoadTimelineStrategy.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LoadTimelineStrategy.kt index d81a115676..3d82f77b75 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LoadTimelineStrategy.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LoadTimelineStrategy.kt @@ -43,6 +43,7 @@ import org.matrix.android.sdk.internal.database.query.findAllIncludingEvents import org.matrix.android.sdk.internal.database.query.findLastForwardChunkOfThread import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.session.room.relation.threads.FetchThreadTimelineTask +import org.matrix.android.sdk.internal.session.room.send.LocalEchoEventFactory import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource import org.matrix.android.sdk.internal.session.sync.handler.room.ThreadsAwarenessHandler import org.matrix.android.sdk.internal.util.time.Clock @@ -106,6 +107,7 @@ internal class LoadTimelineStrategy constructor( val onNewTimelineEvents: (List) -> Unit, val stateEventDataSource: StateEventDataSource, val matrixCoroutineDispatchers: MatrixCoroutineDispatchers, + val localEchoEventFactory: LocalEchoEventFactory ) private var getContextLatch: CompletableDeferred? = null @@ -341,6 +343,8 @@ internal class LoadTimelineStrategy constructor( initialEventId = mode.originEventId(), onBuiltEvents = dependencies.onEventsUpdated, onEventsDeleted = dependencies.onEventsDeleted, + realm = dependencies.realm, + localEchoEventFactory = dependencies.localEchoEventFactory ) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt index 7fa36969b1..3068dec0dd 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt @@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.session.room.timeline import io.realm.OrderedCollectionChangeSet import io.realm.OrderedRealmCollectionChangeListener +import io.realm.Realm import io.realm.RealmConfiguration import io.realm.RealmObjectChangeListener import io.realm.RealmQuery @@ -26,10 +27,18 @@ import io.realm.Sort import kotlinx.coroutines.CompletableDeferred import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.extensions.tryOrNull +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.UnsignedData +import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent +import org.matrix.android.sdk.api.session.events.model.toContent +import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.room.model.message.MessageContent import org.matrix.android.sdk.api.session.room.timeline.Timeline import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings +import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent +import org.matrix.android.sdk.api.session.room.timeline.isReply import org.matrix.android.sdk.api.settings.LightweightSettingsStorage import org.matrix.android.sdk.internal.database.mapper.EventMapper import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper @@ -37,12 +46,15 @@ import org.matrix.android.sdk.internal.database.model.ChunkEntity import org.matrix.android.sdk.internal.database.model.ChunkEntityFields 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.where import org.matrix.android.sdk.internal.session.room.relation.threads.DefaultFetchThreadTimelineTask import org.matrix.android.sdk.internal.session.room.relation.threads.FetchThreadTimelineTask +import org.matrix.android.sdk.internal.session.room.send.LocalEchoEventFactory import org.matrix.android.sdk.internal.session.sync.handler.room.ThreadsAwarenessHandler import timber.log.Timber import java.util.Collections import java.util.concurrent.atomic.AtomicBoolean +import java.util.concurrent.atomic.AtomicReference /** * This is a wrapper around a ChunkEntity in the database. @@ -66,6 +78,8 @@ internal class TimelineChunk( private val initialEventId: String?, private val onBuiltEvents: (Boolean) -> Unit, private val onEventsDeleted: () -> Unit, + private val realm: AtomicReference, + val localEchoEventFactory: LocalEchoEventFactory, ) { private val isLastForward = AtomicBoolean(chunkEntity.isLastForward) @@ -411,9 +425,56 @@ internal class TimelineChunk( private fun buildTimelineEvent(eventEntity: TimelineEventEntity) = timelineEventMapper.map( timelineEventEntity = eventEntity, buildReadReceipts = timelineSettings.buildReadReceipts - ).let { + ).let { timelineEvent -> // eventually enhance with ui echo? - (uiEchoManager?.decorateEventWithReactionUiEcho(it) ?: it) + uiEchoManager?.decorateEventWithReactionUiEcho(timelineEvent) + + if (timelineEvent.isReply()) { + createNewEncryptedRepliedEvent(timelineEvent)?.let { newEvent -> + timelineEvent.copy(root = newEvent) + } ?: timelineEvent + } else timelineEvent + } + + private fun createNewEncryptedRepliedEvent(currentTimelineEvent: TimelineEvent): Event? { + val relatesEventId = if (currentTimelineEvent.isEncrypted()) { + currentTimelineEvent.root.content.toModel()?.relatesTo?.inReplyTo?.eventId + } else { + currentTimelineEvent.root.content.toModel()?.relatesTo?.inReplyTo?.eventId + } + return relatesEventId?.let { eventId -> + val timeLineEventEntity = TimelineEventEntity.where( + realm.get(), + roomId, + eventId + ).findFirst() + + val replyText = localEchoEventFactory + .bodyForReply(currentTimelineEvent.getLastMessageContent(), true).formattedText ?: "" + + timeLineEventEntity?.let { timelineEventEntity -> + val newContent = localEchoEventFactory.createReplyTextContent( + timelineEventMapper.map(timelineEventEntity), + replyText, + false, + showInThread = false + ).toContent() + val event = currentTimelineEvent.root + Event( + roomId = event.roomId, + originServerTs = event.originServerTs, + senderId = event.senderId, + eventId = currentTimelineEvent.eventId, + type = EventType.MESSAGE, + content = newContent, + unsignedData = UnsignedData(age = null, transactionId = currentTimelineEvent.eventId), + ).apply { + this.sendState = event.sendState + this.ageLocalTs = event.ageLocalTs + this.threadDetails = event.threadDetails + } + } + } } /** @@ -580,7 +641,9 @@ internal class TimelineChunk( lightweightSettingsStorage = lightweightSettingsStorage, initialEventId = null, onBuiltEvents = this.onBuiltEvents, - onEventsDeleted = this.onEventsDeleted + onEventsDeleted = this.onEventsDeleted, + realm = realm, + localEchoEventFactory = localEchoEventFactory ) }