From b72039e735f9fee42e6a750ba8e194c5bad80290 Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 20 Dec 2023 16:53:54 +0100 Subject: [PATCH 1/3] Fix a bunch of issues related to edition and reply #5969 --- changelog.d/5969.bugfix | 1 + .../room/model/relation/RelationService.kt | 2 +- .../session/room/timeline/TimelineEvent.kt | 40 +++++-------------- .../android/sdk/api/util/ContentUtils.kt | 31 ++++++++++++-- .../room/relation/DefaultRelationService.kt | 2 +- .../session/room/relation/EventEditor.kt | 2 +- .../room/send/LocalEchoEventFactory.kt | 4 +- .../composer/MessageComposerViewModel.kt | 9 +++-- .../composer/PlainTextComposerLayout.kt | 8 +++- 9 files changed, 56 insertions(+), 43 deletions(-) create mode 100644 changelog.d/5969.bugfix diff --git a/changelog.d/5969.bugfix b/changelog.d/5969.bugfix new file mode 100644 index 0000000000..3995b7755c --- /dev/null +++ b/changelog.d/5969.bugfix @@ -0,0 +1 @@ +Fix some issues related to edition and reply of events. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/RelationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/RelationService.kt index e7fcabf386..5a5cb466bd 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/RelationService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/RelationService.kt @@ -117,7 +117,7 @@ interface RelationService { fun editReply( replyToEdit: TimelineEvent, originalTimelineEvent: TimelineEvent, - newBodyText: String, + newBodyText: CharSequence, newFormattedBodyText: String? = null, compatibilityBodyText: String = "* $newBodyText" ): Cancelable diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt index a49c20ccbb..ef6d103e73 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt @@ -37,13 +37,13 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconLocati import org.matrix.android.sdk.api.session.room.model.message.MessageContent import org.matrix.android.sdk.api.session.room.model.message.MessageContentWithFormattedBody import org.matrix.android.sdk.api.session.room.model.message.MessageEndPollContent -import org.matrix.android.sdk.api.session.room.model.message.MessageFormat import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent import org.matrix.android.sdk.api.session.room.model.message.MessageStickerContent import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent import org.matrix.android.sdk.api.session.room.sender.SenderInfo import org.matrix.android.sdk.api.util.ContentUtils +import org.matrix.android.sdk.api.util.ContentUtils.ensureCorrectFormattedBodyInTextReply import org.matrix.android.sdk.api.util.ContentUtils.extractUsefulTextFromReply /** @@ -160,37 +160,17 @@ fun TimelineEvent.getLastMessageContent(): MessageContent? { fun TimelineEvent.getLastEditNewContent(): Content? { val lastContent = annotations?.editSummary?.latestEdit?.getClearContent()?.toModel()?.newContent - return if (isReply()) { - val previousFormattedBody = root.getClearContent().toModel()?.formattedBody - if (previousFormattedBody?.isNotEmpty() == true) { - val lastMessageContent = lastContent.toModel() - lastMessageContent?.let { ensureCorrectFormattedBodyInTextReply(it, previousFormattedBody) }?.toContent() ?: lastContent - } else { - lastContent - } - } else { - lastContent - } -} - -private const val MX_REPLY_END_TAG = "" - -/** - * Not every client sends a formatted body in the last edited event since this is not required in the - * [Matrix specification](https://spec.matrix.org/v1.4/client-server-api/#applying-mnew_content). - * We must ensure there is one so that it is still considered as a reply when rendering the message. - */ -private fun ensureCorrectFormattedBodyInTextReply(messageTextContent: MessageTextContent, previousFormattedBody: String): MessageTextContent { return when { - messageTextContent.formattedBody.isNullOrEmpty() && previousFormattedBody.contains(MX_REPLY_END_TAG) -> { - // take previous formatted body with the new body content - val newFormattedBody = previousFormattedBody.replaceAfterLast(MX_REPLY_END_TAG, messageTextContent.body) - messageTextContent.copy( - formattedBody = newFormattedBody, - format = MessageFormat.FORMAT_MATRIX_HTML, - ) + isReply() -> { + val originalFormattedBody = root.getClearContent().toModel()?.formattedBody + val lastMessageContent = lastContent.toModel() + if (lastMessageContent != null && originalFormattedBody?.isNotEmpty() == true) { + ensureCorrectFormattedBodyInTextReply(lastMessageContent, originalFormattedBody).toContent() + } else { + lastContent + } } - else -> messageTextContent + else -> lastContent } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/ContentUtils.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/ContentUtils.kt index e453cb2df5..ea4049baba 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/ContentUtils.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/ContentUtils.kt @@ -15,6 +15,8 @@ */ package org.matrix.android.sdk.api.util +import org.matrix.android.sdk.api.session.room.model.message.MessageFormat +import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent import org.matrix.android.sdk.internal.util.unescapeHtml object ContentUtils { @@ -38,15 +40,36 @@ object ContentUtils { } fun extractUsefulTextFromHtmlReply(repliedBody: String): String { - if (repliedBody.startsWith("")) { - val closingTagIndex = repliedBody.lastIndexOf("") + if (repliedBody.startsWith(MX_REPLY_START_TAG)) { + val closingTagIndex = repliedBody.lastIndexOf(MX_REPLY_END_TAG) if (closingTagIndex != -1) { - return repliedBody.substring(closingTagIndex + "".length).trim() + return repliedBody.substring(closingTagIndex + MX_REPLY_END_TAG.length).trim() } } return repliedBody } + /** + * Not every client sends a formatted body in the last edited event since this is not required in the + * [Matrix specification](https://spec.matrix.org/v1.4/client-server-api/#applying-mnew_content). + * We must ensure there is one so that it is still considered as a reply when rendering the message. + */ + fun ensureCorrectFormattedBodyInTextReply(messageTextContent: MessageTextContent, originalFormattedBody: String): MessageTextContent { + return when { + messageTextContent.formattedBody != null && + !messageTextContent.formattedBody.contains(MX_REPLY_END_TAG) && + originalFormattedBody.contains(MX_REPLY_END_TAG) -> { + // take previous formatted body with the new body content + val newFormattedBody = originalFormattedBody.replaceAfterLast(MX_REPLY_END_TAG, messageTextContent.body) + messageTextContent.copy( + formattedBody = newFormattedBody, + format = MessageFormat.FORMAT_MATRIX_HTML, + ) + } + else -> messageTextContent + } + } + @Suppress("RegExpRedundantEscape") fun formatSpoilerTextFromHtml(formattedBody: String): String { // var reason = "", @@ -57,4 +80,6 @@ object ContentUtils { } private const val SPOILER_CHAR = "█" + private const val MX_REPLY_START_TAG = "" + private const val MX_REPLY_END_TAG = "" } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/DefaultRelationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/DefaultRelationService.kt index ddf3e41dff..190dcf7472 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/DefaultRelationService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/DefaultRelationService.kt @@ -115,7 +115,7 @@ internal class DefaultRelationService @AssistedInject constructor( override fun editReply( replyToEdit: TimelineEvent, originalTimelineEvent: TimelineEvent, - newBodyText: String, + newBodyText: CharSequence, newFormattedBodyText: String?, compatibilityBodyText: String ): Cancelable { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/EventEditor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/EventEditor.kt index c83539c8fd..f0ea4c4d07 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/EventEditor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/EventEditor.kt @@ -106,7 +106,7 @@ internal class EventEditor @Inject constructor( fun editReply( replyToEdit: TimelineEvent, originalTimelineEvent: TimelineEvent, - newBodyText: String, + newBodyText: CharSequence, newBodyFormattedText: String?, compatibilityBodyText: String ): Cancelable { 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 9510f50420..1e87a5d005 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 @@ -312,7 +312,7 @@ internal class LocalEchoEventFactory @Inject constructor( roomId: String, eventReplaced: TimelineEvent, originalEvent: TimelineEvent, - newBodyText: String, + newBodyText: CharSequence, autoMarkdown: Boolean, msgType: String, compatibilityText: String, @@ -336,7 +336,7 @@ internal class LocalEchoEventFactory @Inject constructor( // // > <@alice:example.org> This is the original body // - val replyFallback = buildReplyFallback(body, originalEvent.root.senderId ?: "", newBodyText) + val replyFallback = buildReplyFallback(body, originalEvent.root.senderId ?: "", newBodyText.toString()) return createMessageEvent( roomId, diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt index 1cce970968..648c88b4d2 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt @@ -178,7 +178,8 @@ class MessageComposerViewModel @AssistedInject constructor( private fun handleEnterEditMode(room: Room, action: MessageComposerAction.EnterEditMode) { room.getTimelineEvent(action.eventId)?.let { timelineEvent -> val formatted = vectorPreferences.isRichTextEditorEnabled() - setState { copy(sendMode = SendMode.Edit(timelineEvent, timelineEvent.getTextEditableContent(formatted))) } + val editableContent = timelineEvent.getTextEditableContent(formatted) + setState { copy(sendMode = SendMode.Edit(timelineEvent, editableContent)) } } } @@ -578,7 +579,7 @@ class MessageComposerViewModel @AssistedInject constructor( if (inReplyTo != null) { // TODO check if same content? room.getTimelineEvent(inReplyTo)?.let { - room.relationService().editReply(state.sendMode.timelineEvent, it, action.text.toString(), action.formattedText) + room.relationService().editReply(state.sendMode.timelineEvent, it, action.text, action.formattedText) } } else { val messageContent = state.sendMode.timelineEvent.getVectorLastMessageContent() @@ -624,14 +625,14 @@ class MessageComposerViewModel @AssistedInject constructor( state.rootThreadEventId?.let { room.relationService().replyInThread( rootThreadEventId = it, - replyInThreadText = action.text.toString(), + replyInThreadText = action.text, autoMarkdown = action.autoMarkdown, formattedText = action.formattedText, eventReplied = timelineEvent ) } ?: room.relationService().replyToMessage( eventReplied = timelineEvent, - replyText = action.text.toString(), + replyText = action.text, replyFormattedText = action.formattedText, autoMarkdown = action.autoMarkdown, showInThread = showInThread, diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/PlainTextComposerLayout.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/PlainTextComposerLayout.kt index 5529265d54..4570ec6056 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/PlainTextComposerLayout.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/PlainTextComposerLayout.kt @@ -48,6 +48,7 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageEndPollConte import org.matrix.android.sdk.api.session.room.model.message.MessageFormat import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent +import org.matrix.android.sdk.api.util.ContentUtils import org.matrix.android.sdk.api.util.MatrixItem import org.matrix.android.sdk.api.util.toMatrixItem import javax.inject.Inject @@ -188,7 +189,12 @@ class PlainTextComposerLayout @JvmOverloads constructor( var formattedBody: CharSequence? = null if (messageContent is MessageTextContent && messageContent.format == MessageFormat.FORMAT_MATRIX_HTML) { val parser = Parser.builder().build() - val document = parser.parse(messageContent.formattedBody ?: messageContent.body) + + val bodyToParse = messageContent.formattedBody?.let { + ContentUtils.extractUsefulTextFromHtmlReply(it) + } ?: ContentUtils.extractUsefulTextFromReply(messageContent.body) + + val document = parser.parse(bodyToParse) formattedBody = eventHtmlRenderer.render(document, pillsPostProcessor) } views.composerRelatedMessageContent.text = (formattedBody ?: nonFormattedBody) From 320aae1430d7e23d514d09263a990434bd23d4a6 Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 20 Dec 2023 20:46:19 +0100 Subject: [PATCH 2/3] Also fix pills with rich text editor --- .../internal/session/room/relation/EventEditor.kt | 1 + .../session/room/send/LocalEchoEventFactory.kt | 13 +++++++------ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/EventEditor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/EventEditor.kt index f0ea4c4d07..67433033d2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/EventEditor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/EventEditor.kt @@ -131,6 +131,7 @@ internal class EventEditor @Inject constructor( replyToEdit, originalTimelineEvent, newBodyText, + newBodyFormattedText, true, MessageType.MSGTYPE_TEXT, compatibilityBodyText 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 1e87a5d005..78328ea4cc 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 @@ -312,7 +312,8 @@ internal class LocalEchoEventFactory @Inject constructor( roomId: String, eventReplaced: TimelineEvent, originalEvent: TimelineEvent, - newBodyText: CharSequence, + replyText: CharSequence, + replyTextFormatted: String?, autoMarkdown: Boolean, msgType: String, compatibilityText: String, @@ -321,22 +322,22 @@ internal class LocalEchoEventFactory @Inject constructor( val permalink = permalinkFactory.createPermalink(roomId, originalEvent.root.eventId ?: "", false) val userLink = originalEvent.root.senderId?.let { permalinkFactory.createPermalink(it, false) } ?: "" - val body = bodyForReply(timelineEvent = originalEvent) + val bodyOfRepliedEvent = bodyForReply(timelineEvent = originalEvent) // As we always supply formatted body for replies we should force the MarkdownParser to produce html. - val newBodyFormatted = markdownParser.parse(newBodyText, force = true, advanced = autoMarkdown).takeFormatted() + val newBodyFormatted = replyTextFormatted ?: markdownParser.parse(replyText, force = true, advanced = autoMarkdown).takeFormatted() // Body of the original message may not have formatted version, so may also have to convert to html. - val bodyFormatted = body.formattedText ?: markdownParser.parse(body.text, force = true, advanced = autoMarkdown).takeFormatted() + val formattedBodyOfRepliedEvent = bodyOfRepliedEvent.formattedText ?: markdownParser.parse(bodyOfRepliedEvent.text, force = true, advanced = autoMarkdown).takeFormatted() val replyFormatted = buildFormattedReply( permalink, userLink, originalEvent.senderInfo.disambiguatedDisplayName, - bodyFormatted, + formattedBodyOfRepliedEvent, newBodyFormatted ) // // > <@alice:example.org> This is the original body // - val replyFallback = buildReplyFallback(body, originalEvent.root.senderId ?: "", newBodyText.toString()) + val replyFallback = buildReplyFallback(bodyOfRepliedEvent, originalEvent.root.senderId ?: "", replyText.toString()) return createMessageEvent( roomId, From 2ada4c84918d9d0797db8cfafff219017437a6b6 Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 21 Dec 2023 16:14:58 +0100 Subject: [PATCH 3/3] Fix quality --- .../sdk/internal/session/room/send/LocalEchoEventFactory.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 78328ea4cc..d4d20dfdd9 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 @@ -326,7 +326,8 @@ internal class LocalEchoEventFactory @Inject constructor( // As we always supply formatted body for replies we should force the MarkdownParser to produce html. val newBodyFormatted = replyTextFormatted ?: markdownParser.parse(replyText, force = true, advanced = autoMarkdown).takeFormatted() // Body of the original message may not have formatted version, so may also have to convert to html. - val formattedBodyOfRepliedEvent = bodyOfRepliedEvent.formattedText ?: markdownParser.parse(bodyOfRepliedEvent.text, force = true, advanced = autoMarkdown).takeFormatted() + val formattedBodyOfRepliedEvent = + bodyOfRepliedEvent.formattedText ?: markdownParser.parse(text = bodyOfRepliedEvent.text, force = true, advanced = autoMarkdown).takeFormatted() val replyFormatted = buildFormattedReply( permalink, userLink,