Merge pull request #1398 from vector-im/feature/date_time

Bugfixes
This commit is contained in:
Benoit Marty 2020-05-25 10:13:30 +02:00 committed by GitHub
commit ebbc432570
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 126 additions and 45 deletions

View file

@ -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 🗣:
-

View file

@ -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 }
}

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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) {

View file

@ -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()
}
}

View file

@ -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()
}

View file

@ -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)
}
}

View file

@ -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_()