diff --git a/CHANGES.md b/CHANGES.md
index 5f83727cd7..50279bcf0e 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -15,6 +15,7 @@ Bugfix 🐛:
- Scroll breadcrumbs to top when opened
- Render default room name when it starts with an emoji (#477)
- Do not display " (IRC)" in display names https://github.com/vector-im/riot-android/issues/444
+ - Fix rendering issue with HTML formatted body
Translations 🗣:
-
diff --git a/vector/build.gradle b/vector/build.gradle
index 8a2df7c120..de15a67fbd 100644
--- a/vector/build.gradle
+++ b/vector/build.gradle
@@ -293,6 +293,7 @@ dependencies {
implementation 'me.gujun.android:span:1.7'
implementation "io.noties.markwon:core:$markwon_version"
implementation "io.noties.markwon:html:$markwon_version"
+ implementation 'com.googlecode.htmlcompressor:htmlcompressor:1.4'
implementation 'me.saket:better-link-movement-method:2.2.0'
implementation 'com.google.android:flexbox:1.1.1'
implementation "androidx.autofill:autofill:$autofill_version"
diff --git a/vector/src/main/assets/open_source_licenses.html b/vector/src/main/assets/open_source_licenses.html
index 3cd500138a..2b08270c89 100755
--- a/vector/src/main/assets/open_source_licenses.html
+++ b/vector/src/main/assets/open_source_licenses.html
@@ -359,6 +359,11 @@ SOFTWARE.
Copyright 2018 Kumar Bibek
+
Apache License diff --git a/vector/src/main/java/im/vector/riotx/core/di/VectorComponent.kt b/vector/src/main/java/im/vector/riotx/core/di/VectorComponent.kt index c8c5d697f6..b78e291506 100644 --- a/vector/src/main/java/im/vector/riotx/core/di/VectorComponent.kt +++ b/vector/src/main/java/im/vector/riotx/core/di/VectorComponent.kt @@ -38,6 +38,7 @@ import im.vector.riotx.features.home.AvatarRenderer import im.vector.riotx.features.home.HomeRoomListDataSource import im.vector.riotx.features.home.group.SelectedGroupDataSource import im.vector.riotx.features.html.EventHtmlRenderer +import im.vector.riotx.features.html.VectorHtmlCompressor import im.vector.riotx.features.navigation.Navigator import im.vector.riotx.features.notifications.* import im.vector.riotx.features.rageshake.BugReporter @@ -87,6 +88,8 @@ interface VectorComponent { fun eventHtmlRenderer(): EventHtmlRenderer + fun vectorHtmlCompressor(): VectorHtmlCompressor + fun navigator(): Navigator fun errorFormatter(): ErrorFormatter diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt index 102412948b..3c7e4624fe 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt @@ -41,6 +41,7 @@ import im.vector.riotx.core.resources.StringProvider import im.vector.riotx.features.home.room.detail.timeline.format.NoticeEventFormatter import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData import im.vector.riotx.features.html.EventHtmlRenderer +import im.vector.riotx.features.html.VectorHtmlCompressor import java.text.SimpleDateFormat import java.util.* @@ -82,6 +83,7 @@ data class MessageActionState( class MessageActionsViewModel @AssistedInject constructor(@Assisted initialState: MessageActionState, private val eventHtmlRenderer: Lazy, + private val htmlCompressor: VectorHtmlCompressor, private val session: Session, private val noticeEventFormatter: NoticeEventFormatter, private val stringProvider: StringProvider @@ -170,8 +172,12 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted EventType.MESSAGE -> { val messageContent: MessageContent? = timelineEvent()?.getLastMessageContent() if (messageContent is MessageTextContent && messageContent.format == MessageType.FORMAT_MATRIX_HTML) { - eventHtmlRenderer.get().render(messageContent.formattedBody - ?: messageContent.body) + val html = messageContent.formattedBody + ?.takeIf { it.isNotBlank() } + ?.let { htmlCompressor.compress(it) } + ?: messageContent.body + + eventHtmlRenderer.get().render(html) } else { messageContent?.body } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MessageItemFactory.kt index 9c96f17022..30f4e94cfb 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MessageItemFactory.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MessageItemFactory.kt @@ -46,6 +46,7 @@ import im.vector.riotx.features.home.room.detail.timeline.tools.createLinkMoveme import im.vector.riotx.features.home.room.detail.timeline.tools.linkify import im.vector.riotx.features.html.CodeVisitor import im.vector.riotx.features.html.EventHtmlRenderer +import im.vector.riotx.features.html.VectorHtmlCompressor import im.vector.riotx.features.media.ImageContentRenderer import im.vector.riotx.features.media.VideoContentRenderer import me.gujun.android.span.span @@ -57,6 +58,7 @@ class MessageItemFactory @Inject constructor( private val dimensionConverter: DimensionConverter, private val timelineMediaSizeProvider: TimelineMediaSizeProvider, private val htmlRenderer: Lazy , + private val htmlCompressor: VectorHtmlCompressor, private val stringProvider: StringProvider, private val imageContentRenderer: ImageContentRenderer, private val messageInformationDataFactory: MessageInformationDataFactory, @@ -227,6 +229,7 @@ class MessageItemFactory @Inject constructor( attributes: AbsMessageItem.Attributes): VectorEpoxyModel<*>? { val isFormatted = messageContent.formattedBody.isNullOrBlank().not() return if (isFormatted) { + // First detect if the message contains some code block(s) or inline code val localFormattedBody = htmlRenderer.get().parse(messageContent.body) as Document val codeVisitor = CodeVisitor() codeVisitor.visit(localFormattedBody) @@ -240,7 +243,8 @@ class MessageItemFactory @Inject constructor( buildMessageTextItem(codeFormatted, false, informationData, highlight, callback, attributes) } CodeVisitor.Kind.NONE -> { - val formattedBody = htmlRenderer.get().render(messageContent.formattedBody!!) + val compressed = htmlCompressor.compress(messageContent.formattedBody!!) + val formattedBody = htmlRenderer.get().render(compressed) buildMessageTextItem(formattedBody, true, informationData, highlight, callback, attributes) } } diff --git a/vector/src/main/java/im/vector/riotx/features/html/VectorHtmlCompressor.kt b/vector/src/main/java/im/vector/riotx/features/html/VectorHtmlCompressor.kt new file mode 100644 index 0000000000..9f3cf96a7e --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/html/VectorHtmlCompressor.kt @@ -0,0 +1,40 @@ +/* + * Copyright 2019 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.riotx.features.html + +import com.googlecode.htmlcompressor.compressor.Compressor +import com.googlecode.htmlcompressor.compressor.HtmlCompressor +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class VectorHtmlCompressor @Inject constructor() { + + // All default options are suitable so far + private val htmlCompressor: Compressor = HtmlCompressor() + + fun compress(html: String): String { + var result = htmlCompressor.compress(html) + + // Trim space after
and, unfortunately the method setRemoveSurroundingSpaces() from the doc does not exist + result = result.replace("
", "
") + result = result.replace("
", "
") + result = result.replace("", "
") + + return result + } +}