mirror of
https://github.com/element-hq/element-android
synced 2024-11-27 20:06:51 +03:00
commit
ebbc432570
11 changed files with 126 additions and 45 deletions
|
@ -8,10 +8,13 @@ Features ✨:
|
|||
Improvements 🙌:
|
||||
- Better connectivity lost indicator when airplane mode is on
|
||||
- Add a setting to hide redacted events (#951)
|
||||
- Render formatted_body for m.notice and m.emote (#1196)
|
||||
|
||||
Bugfix 🐛:
|
||||
- After jump to unread, newer messages are never loaded (#1008)
|
||||
- Fix issues with FontScale switch (#69, #645)
|
||||
- "Seen by" uses 12h time (#1378)
|
||||
- Enable markdown (if active) when sending emote (#734)
|
||||
|
||||
Translations 🗣:
|
||||
-
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Copyright (c) 2020 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.matrix.android.api.session.room.model.message
|
||||
|
||||
interface MessageContentWithFormattedBody : MessageContent {
|
||||
/**
|
||||
* The format used in the formatted_body. Currently only "org.matrix.custom.html" is supported.
|
||||
*/
|
||||
val format: String?
|
||||
|
||||
/**
|
||||
* The formatted version of the body. This is required if format is specified.
|
||||
*/
|
||||
val formattedBody: String?
|
||||
|
||||
/**
|
||||
* Get the formattedBody, only if not blank and if the format is equal to "org.matrix.custom.html"
|
||||
*/
|
||||
val matrixFormattedBody: String?
|
||||
get() = formattedBody?.takeIf { it.isNotBlank() && format == MessageFormat.FORMAT_MATRIX_HTML }
|
||||
}
|
|
@ -34,15 +34,15 @@ data class MessageEmoteContent(
|
|||
@Json(name = "body") override val body: String,
|
||||
|
||||
/**
|
||||
* The format used in the formatted_body. Currently only org.matrix.custom.html is supported.
|
||||
* The format used in the formatted_body. Currently only "org.matrix.custom.html" is supported.
|
||||
*/
|
||||
@Json(name = "format") val format: String? = null,
|
||||
@Json(name = "format") override val format: String? = null,
|
||||
|
||||
/**
|
||||
* The formatted version of the body. This is required if format is specified.
|
||||
*/
|
||||
@Json(name = "formatted_body") val formattedBody: String? = null,
|
||||
@Json(name = "formatted_body") override val formattedBody: String? = null,
|
||||
|
||||
@Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
|
||||
@Json(name = "m.new_content") override val newContent: Content? = null
|
||||
) : MessageContent
|
||||
) : MessageContentWithFormattedBody
|
||||
|
|
|
@ -34,15 +34,15 @@ data class MessageNoticeContent(
|
|||
@Json(name = "body") override val body: String,
|
||||
|
||||
/**
|
||||
* The format used in the formatted_body. Currently only org.matrix.custom.html is supported.
|
||||
* The format used in the formatted_body. Currently only "org.matrix.custom.html" is supported.
|
||||
*/
|
||||
@Json(name = "format") val format: String? = null,
|
||||
@Json(name = "format") override val format: String? = null,
|
||||
|
||||
/**
|
||||
* The formatted version of the body. This is required if format is specified.
|
||||
*/
|
||||
@Json(name = "formatted_body") val formattedBody: String? = null,
|
||||
@Json(name = "formatted_body") override val formattedBody: String? = null,
|
||||
|
||||
@Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
|
||||
@Json(name = "m.new_content") override val newContent: Content? = null
|
||||
) : MessageContent
|
||||
) : MessageContentWithFormattedBody
|
||||
|
|
|
@ -34,15 +34,15 @@ data class MessageTextContent(
|
|||
@Json(name = "body") override val body: String,
|
||||
|
||||
/**
|
||||
* The format used in the formatted_body. Currently only org.matrix.custom.html is supported.
|
||||
* The format used in the formatted_body. Currently only "org.matrix.custom.html" is supported.
|
||||
*/
|
||||
@Json(name = "format") val format: String? = null,
|
||||
@Json(name = "format") override val format: String? = null,
|
||||
|
||||
/**
|
||||
* The formatted version of the body. This is required if format is specified.
|
||||
*/
|
||||
@Json(name = "formatted_body") val formattedBody: String? = null,
|
||||
@Json(name = "formatted_body") override val formattedBody: String? = null,
|
||||
|
||||
@Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
|
||||
@Json(name = "m.new_content") override val newContent: Content? = null
|
||||
) : MessageContent
|
||||
) : MessageContentWithFormattedBody
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* Copyright (c) 2020 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.matrix.android.internal.extensions
|
||||
|
||||
/**
|
||||
* Ex: "abcdef".subStringBetween("a", "f") -> "bcde"
|
||||
* Ex: "abcdefff".subStringBetween("a", "f") -> "bcdeff"
|
||||
* Ex: "aaabcdef".subStringBetween("a", "f") -> "aabcde"
|
||||
*/
|
||||
internal fun String.subStringBetween(prefix: String, suffix: String) = substringAfter(prefix).substringBeforeLast(suffix)
|
|
@ -35,6 +35,7 @@ import im.vector.matrix.android.api.session.room.model.message.FileInfo
|
|||
import im.vector.matrix.android.api.session.room.model.message.ImageInfo
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageAudioContent
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageContent
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageContentWithFormattedBody
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageFileContent
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageFormat
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageImageContent
|
||||
|
@ -56,6 +57,7 @@ import im.vector.matrix.android.api.session.room.model.relation.ReplyToContent
|
|||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||
import im.vector.matrix.android.api.session.room.timeline.getLastMessageContent
|
||||
import im.vector.matrix.android.internal.di.UserId
|
||||
import im.vector.matrix.android.internal.extensions.subStringBetween
|
||||
import im.vector.matrix.android.internal.session.content.ThumbnailExtractor
|
||||
import im.vector.matrix.android.internal.session.room.send.pills.TextPillsUtils
|
||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||
|
@ -84,6 +86,7 @@ internal class LocalEchoEventFactory @Inject constructor(
|
|||
) {
|
||||
// TODO Inject
|
||||
private val parser = Parser.builder().build()
|
||||
|
||||
// TODO Inject
|
||||
private val renderer = HtmlRenderer.builder().build()
|
||||
|
||||
|
@ -102,8 +105,15 @@ internal class LocalEchoEventFactory @Inject constructor(
|
|||
val document = parser.parse(source)
|
||||
val htmlText = renderer.render(document)
|
||||
|
||||
if (isFormattedTextPertinent(source, htmlText)) {
|
||||
return TextContent(text.toString(), htmlText)
|
||||
// Cleanup extra paragraph
|
||||
val cleanHtmlText = if (htmlText.startsWith("<p>") && htmlText.endsWith("</p>\n")) {
|
||||
htmlText.subStringBetween("<p>", "</p>\n")
|
||||
} else {
|
||||
htmlText
|
||||
}
|
||||
|
||||
if (isFormattedTextPertinent(source, cleanHtmlText)) {
|
||||
return TextContent(text.toString(), cleanHtmlText)
|
||||
}
|
||||
} else {
|
||||
// Try to detect pills
|
||||
|
@ -433,10 +443,8 @@ internal class LocalEchoEventFactory @Inject constructor(
|
|||
MessageType.MSGTYPE_TEXT,
|
||||
MessageType.MSGTYPE_NOTICE -> {
|
||||
var formattedText: String? = null
|
||||
if (content is MessageTextContent) {
|
||||
if (content.format == MessageFormat.FORMAT_MATRIX_HTML) {
|
||||
formattedText = content.formattedBody
|
||||
}
|
||||
if (content is MessageContentWithFormattedBody) {
|
||||
formattedText = content.matrixFormattedBody
|
||||
}
|
||||
val isReply = content.isReply() || originalContent.isReply()
|
||||
return if (isReply) {
|
||||
|
|
|
@ -45,11 +45,12 @@ class VectorDateFormatter @Inject constructor(private val context: Context,
|
|||
if (time == null) {
|
||||
return ""
|
||||
}
|
||||
return DateUtils.getRelativeDateTimeString(context,
|
||||
time,
|
||||
DateUtils.DAY_IN_MILLIS,
|
||||
2 * DateUtils.DAY_IN_MILLIS,
|
||||
DateUtils.FORMAT_SHOW_WEEKDAY
|
||||
return DateUtils.getRelativeDateTimeString(
|
||||
context,
|
||||
time,
|
||||
DateUtils.DAY_IN_MILLIS,
|
||||
2 * DateUtils.DAY_IN_MILLIS,
|
||||
DateUtils.FORMAT_SHOW_WEEKDAY or DateUtils.FORMAT_SHOW_TIME
|
||||
).toString()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -117,8 +117,10 @@ class RoomDetailViewModel @AssistedInject constructor(
|
|||
|
||||
// Slot to keep a pending action during permission request
|
||||
var pendingAction: RoomDetailAction? = null
|
||||
|
||||
// Slot to keep a pending uri during permission request
|
||||
var pendingUri: Uri? = null
|
||||
|
||||
// Slot to store if we want to prevent preview of attachment
|
||||
var preventAttachmentPreview = false
|
||||
|
||||
|
@ -390,7 +392,7 @@ class RoomDetailViewModel @AssistedInject constructor(
|
|||
_viewEvents.post(RoomDetailViewEvents.SlashCommandNotImplemented)
|
||||
}
|
||||
is ParsedCommand.SendEmote -> {
|
||||
room.sendTextMessage(slashCommandResult.message, msgType = MessageType.MSGTYPE_EMOTE)
|
||||
room.sendTextMessage(slashCommandResult.message, msgType = MessageType.MSGTYPE_EMOTE, autoMarkdown = action.autoMarkdown)
|
||||
_viewEvents.post(RoomDetailViewEvents.SlashCommandHandled())
|
||||
popDraft()
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ import im.vector.matrix.android.api.session.events.model.Event
|
|||
import im.vector.matrix.android.api.session.events.model.toModel
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageTextContent
|
||||
import im.vector.matrix.android.api.util.ContentUtils.extractUsefulTextFromReply
|
||||
import im.vector.matrix.android.internal.session.room.send.TextContent
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.date.VectorDateFormatter
|
||||
import im.vector.riotx.core.extensions.localDateTime
|
||||
|
@ -90,17 +91,15 @@ class ViewEditHistoryEpoxyController(private val context: Context,
|
|||
}
|
||||
lastDate = evDate
|
||||
val cContent = getCorrectContent(timelineEvent, isOriginalReply)
|
||||
val body = cContent.second?.let { eventHtmlRenderer.render(it) }
|
||||
?: cContent.first
|
||||
val body = cContent.formattedText?.let { eventHtmlRenderer.render(it) } ?: cContent.text
|
||||
|
||||
val nextEvent = sourceEvents.getOrNull(index + 1)
|
||||
|
||||
var spannedDiff: Spannable? = null
|
||||
if (nextEvent != null && cContent.second == null /*No diff for html*/) {
|
||||
if (nextEvent != null && cContent.formattedText == null /*No diff for html*/) {
|
||||
// compares the body
|
||||
val nContent = getCorrectContent(nextEvent, isOriginalReply)
|
||||
val nextBody = nContent.second?.let { eventHtmlRenderer.render(it) }
|
||||
?: nContent.first
|
||||
val nextBody = nContent.formattedText?.let { eventHtmlRenderer.render(it) } ?: nContent.text
|
||||
val dmp = diff_match_patch()
|
||||
val diff = dmp.diff_main(nextBody.toString(), body.toString())
|
||||
dmp.diff_cleanupSemantic(diff)
|
||||
|
@ -138,15 +137,14 @@ class ViewEditHistoryEpoxyController(private val context: Context,
|
|||
}
|
||||
}
|
||||
|
||||
private fun getCorrectContent(event: Event, isOriginalReply: Boolean): Pair<String, String?> {
|
||||
private fun getCorrectContent(event: Event, isOriginalReply: Boolean): TextContent {
|
||||
val clearContent = event.getClearContent().toModel<MessageTextContent>()
|
||||
val newContent = clearContent
|
||||
?.newContent
|
||||
?.toModel<MessageTextContent>()
|
||||
if (isOriginalReply) {
|
||||
return extractUsefulTextFromReply(newContent?.body ?: clearContent?.body ?: "") to null
|
||||
return TextContent(extractUsefulTextFromReply(newContent?.body ?: clearContent?.body ?: ""))
|
||||
}
|
||||
return (newContent?.body ?: clearContent?.body ?: "") to (newContent?.formattedBody
|
||||
?: clearContent?.formattedBody)
|
||||
return TextContent(newContent?.body ?: clearContent?.body ?: "", newContent?.formattedBody ?: clearContent?.formattedBody)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,6 +29,7 @@ import im.vector.matrix.android.api.session.events.model.RelationType
|
|||
import im.vector.matrix.android.api.session.events.model.toModel
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageAudioContent
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageContent
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageContentWithFormattedBody
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageEmoteContent
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageFileContent
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageImageInfoContent
|
||||
|
@ -350,7 +351,7 @@ class MessageItemFactory @Inject constructor(
|
|||
highlight: Boolean,
|
||||
callback: TimelineEventController.Callback?,
|
||||
attributes: AbsMessageItem.Attributes): VectorEpoxyModel<*>? {
|
||||
val isFormatted = messageContent.formattedBody.isNullOrBlank().not()
|
||||
val isFormatted = messageContent.matrixFormattedBody.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
|
||||
|
@ -462,14 +463,14 @@ class MessageItemFactory @Inject constructor(
|
|||
highlight: Boolean,
|
||||
callback: TimelineEventController.Callback?,
|
||||
attributes: AbsMessageItem.Attributes): MessageTextItem? {
|
||||
val message = messageContent.body.let {
|
||||
val formattedBody = span {
|
||||
text = it
|
||||
textColor = colorProvider.getColorFromAttribute(R.attr.riotx_text_secondary)
|
||||
textStyle = "italic"
|
||||
}
|
||||
formattedBody.linkify(callback)
|
||||
val formattedBody = span {
|
||||
text = messageContent.getHtmlBody()
|
||||
textColor = colorProvider.getColorFromAttribute(R.attr.riotx_text_secondary)
|
||||
textStyle = "italic"
|
||||
}
|
||||
|
||||
val message = formattedBody.linkify(callback)
|
||||
|
||||
return MessageTextItem_()
|
||||
.leftGuideline(avatarSizeProvider.leftGuideline)
|
||||
.attributes(attributes)
|
||||
|
@ -483,10 +484,12 @@ class MessageItemFactory @Inject constructor(
|
|||
highlight: Boolean,
|
||||
callback: TimelineEventController.Callback?,
|
||||
attributes: AbsMessageItem.Attributes): MessageTextItem? {
|
||||
val message = messageContent.body.let {
|
||||
val formattedBody = "* ${informationData.memberName} $it"
|
||||
formattedBody.linkify(callback)
|
||||
}
|
||||
val formattedBody = SpannableStringBuilder()
|
||||
formattedBody.append("* ${informationData.memberName} ")
|
||||
formattedBody.append(messageContent.getHtmlBody())
|
||||
|
||||
val message = formattedBody.linkify(callback)
|
||||
|
||||
return MessageTextItem_()
|
||||
.apply {
|
||||
if (informationData.hasBeenEdited) {
|
||||
|
@ -502,6 +505,13 @@ class MessageItemFactory @Inject constructor(
|
|||
.movementMethod(createLinkMovementMethod(callback))
|
||||
}
|
||||
|
||||
private fun MessageContentWithFormattedBody.getHtmlBody(): CharSequence {
|
||||
return matrixFormattedBody
|
||||
?.let { htmlCompressor.compress(it) }
|
||||
?.let { htmlRenderer.get().render(it) }
|
||||
?: body
|
||||
}
|
||||
|
||||
private fun buildRedactedItem(attributes: AbsMessageItem.Attributes,
|
||||
highlight: Boolean): RedactedMessageItem? {
|
||||
return RedactedMessageItem_()
|
||||
|
|
Loading…
Reference in a new issue