mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2025-03-17 19:58:57 +03:00
Markdown and sploiler in roomlist + spoiler in notifications (#4483)
Render markdown in room list and make notifications spoiler aware, per MSC3124 Reorder when case to put the most common on top Co-authored-by: Onuray Sahin <onurays@element.io> Co-authored-by: Wasabi\preston <1337paf92@gmail.com>
This commit is contained in:
parent
855b672f48
commit
0fd29d763c
6 changed files with 75 additions and 56 deletions
1
changelog.d/3477.feature
Normal file
1
changelog.d/3477.feature
Normal file
|
@ -0,0 +1 @@
|
|||
Make notification text spoiler aware
|
1
changelog.d/452.bugfix
Normal file
1
changelog.d/452.bugfix
Normal file
|
@ -0,0 +1 @@
|
|||
Render markdown in room list
|
|
@ -28,8 +28,10 @@ import org.matrix.android.sdk.api.session.room.model.EventAnnotationsSummary
|
|||
import org.matrix.android.sdk.api.session.room.model.ReadReceipt
|
||||
import org.matrix.android.sdk.api.session.room.model.message.MessageContent
|
||||
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.extractUsefulTextFromReply
|
||||
|
||||
/**
|
||||
|
@ -131,20 +133,6 @@ fun TimelineEvent.getLastMessageContent(): MessageContent? {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get last Message body, after a possible edition
|
||||
*/
|
||||
fun TimelineEvent.getLastMessageBody(): String? {
|
||||
val lastMessageContent = getLastMessageContent()
|
||||
|
||||
if (lastMessageContent != null) {
|
||||
return lastMessageContent.newContent?.toModel<MessageContent>()?.body
|
||||
?: lastMessageContent.body
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if it's a reply
|
||||
*/
|
||||
|
@ -156,11 +144,25 @@ fun TimelineEvent.isEdition(): Boolean {
|
|||
return root.isEdition()
|
||||
}
|
||||
|
||||
fun TimelineEvent.getTextEditableContent(): String? {
|
||||
val lastContent = getLastMessageContent()
|
||||
/**
|
||||
* Get the latest message body, after a possible edition, stripping the reply prefix if necessary
|
||||
*/
|
||||
fun TimelineEvent.getTextEditableContent(): String {
|
||||
val lastContentBody = getLastMessageContent()?.body ?: return ""
|
||||
return if (isReply()) {
|
||||
return extractUsefulTextFromReply(lastContent?.body ?: "")
|
||||
extractUsefulTextFromReply(lastContentBody)
|
||||
} else {
|
||||
lastContent?.body ?: ""
|
||||
lastContentBody
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the latest displayable content.
|
||||
* Will take care to hide spoiler text
|
||||
*/
|
||||
fun MessageContent.getTextDisplayableContent(): String {
|
||||
return newContent?.toModel<MessageTextContent>()?.matrixFormattedBody?.let { ContentUtils.formatSpoilerTextFromHtml(it) }
|
||||
?: newContent?.toModel<MessageContent>()?.body
|
||||
?: (this as MessageTextContent?)?.matrixFormattedBody?.let { ContentUtils.formatSpoilerTextFromHtml(it) }
|
||||
?: body
|
||||
}
|
||||
|
|
|
@ -15,6 +15,8 @@
|
|||
*/
|
||||
package org.matrix.android.sdk.api.util
|
||||
|
||||
import org.matrix.android.sdk.internal.util.unescapeHtml
|
||||
|
||||
object ContentUtils {
|
||||
fun extractUsefulTextFromReply(repliedBody: String): String {
|
||||
val lines = repliedBody.lines()
|
||||
|
@ -44,4 +46,15 @@ object ContentUtils {
|
|||
}
|
||||
return repliedBody
|
||||
}
|
||||
|
||||
@Suppress("RegExpRedundantEscape")
|
||||
fun formatSpoilerTextFromHtml(formattedBody: String): String {
|
||||
// var reason = "",
|
||||
// can capture the spoiler reason for better formatting? ex. { reason = it.value; ">"}
|
||||
return formattedBody.replace("(?<=<span data-mx-spoiler)=\\\".+?\\\">".toRegex(), ">")
|
||||
.replace("(?<=<span data-mx-spoiler>).+?(?=</span>)".toRegex()) { SPOILER_CHAR.repeat(it.value.length) }
|
||||
.unescapeHtml()
|
||||
}
|
||||
|
||||
private const val SPOILER_CHAR = "█"
|
||||
}
|
||||
|
|
|
@ -122,7 +122,7 @@ class TextComposerViewModel @AssistedInject constructor(
|
|||
|
||||
private fun handleEnterEditMode(action: TextComposerAction.EnterEditMode) {
|
||||
room.getTimeLineEvent(action.eventId)?.let { timelineEvent ->
|
||||
setState { copy(sendMode = SendMode.EDIT(timelineEvent, timelineEvent.getTextEditableContent() ?: "")) }
|
||||
setState { copy(sendMode = SendMode.EDIT(timelineEvent, timelineEvent.getTextEditableContent())) }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -16,29 +16,33 @@
|
|||
|
||||
package im.vector.app.features.home.room.detail.timeline.format
|
||||
|
||||
import dagger.Lazy
|
||||
import im.vector.app.EmojiCompatWrapper
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.resources.ColorProvider
|
||||
import im.vector.app.core.resources.StringProvider
|
||||
import im.vector.app.features.html.EventHtmlRenderer
|
||||
import me.gujun.android.span.span
|
||||
import org.commonmark.node.Document
|
||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||
import org.matrix.android.sdk.api.session.room.model.message.MessageAudioContent
|
||||
import org.matrix.android.sdk.api.session.room.model.message.MessageOptionsContent
|
||||
import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent
|
||||
import org.matrix.android.sdk.api.session.room.model.message.MessageType
|
||||
import org.matrix.android.sdk.api.session.room.model.message.OPTION_TYPE_BUTTONS
|
||||
import org.matrix.android.sdk.api.session.room.model.relation.ReactionContent
|
||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||
import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent
|
||||
import org.matrix.android.sdk.api.session.room.timeline.getTextEditableContent
|
||||
import org.matrix.android.sdk.api.session.room.timeline.isReply
|
||||
import org.matrix.android.sdk.api.session.room.timeline.getTextDisplayableContent
|
||||
import javax.inject.Inject
|
||||
|
||||
class DisplayableEventFormatter @Inject constructor(
|
||||
private val stringProvider: StringProvider,
|
||||
private val colorProvider: ColorProvider,
|
||||
private val emojiCompatWrapper: EmojiCompatWrapper,
|
||||
private val noticeEventFormatter: NoticeEventFormatter
|
||||
private val noticeEventFormatter: NoticeEventFormatter,
|
||||
private val htmlRenderer: Lazy<EventHtmlRenderer>
|
||||
) {
|
||||
|
||||
fun format(timelineEvent: TimelineEvent, isDm: Boolean, appendAuthor: Boolean): CharSequence {
|
||||
|
@ -53,54 +57,45 @@ class DisplayableEventFormatter @Inject constructor(
|
|||
|
||||
val senderName = timelineEvent.senderInfo.disambiguatedDisplayName
|
||||
|
||||
when (timelineEvent.root.getClearType()) {
|
||||
EventType.STICKER -> {
|
||||
return simpleFormat(senderName, stringProvider.getString(R.string.send_a_sticker), appendAuthor)
|
||||
}
|
||||
EventType.REACTION -> {
|
||||
timelineEvent.root.getClearContent().toModel<ReactionContent>()?.relatesTo?.let {
|
||||
val emojiSpanned = emojiCompatWrapper.safeEmojiSpanify(stringProvider.getString(R.string.sent_a_reaction, it.key))
|
||||
return simpleFormat(senderName, emojiSpanned, appendAuthor)
|
||||
}
|
||||
}
|
||||
return when (timelineEvent.root.getClearType()) {
|
||||
EventType.MESSAGE -> {
|
||||
timelineEvent.getLastMessageContent()?.let { messageContent ->
|
||||
when (messageContent.msgType) {
|
||||
MessageType.MSGTYPE_TEXT -> {
|
||||
val body = messageContent.getTextDisplayableContent()
|
||||
if (messageContent is MessageTextContent && messageContent.matrixFormattedBody.isNullOrBlank().not()) {
|
||||
val localFormattedBody = htmlRenderer.get().parse(body) as Document
|
||||
val renderedBody = htmlRenderer.get().render(localFormattedBody) ?: body
|
||||
simpleFormat(senderName, renderedBody, appendAuthor)
|
||||
} else {
|
||||
simpleFormat(senderName, body, appendAuthor)
|
||||
}
|
||||
}
|
||||
MessageType.MSGTYPE_VERIFICATION_REQUEST -> {
|
||||
return simpleFormat(senderName, stringProvider.getString(R.string.verification_request), appendAuthor)
|
||||
simpleFormat(senderName, stringProvider.getString(R.string.verification_request), appendAuthor)
|
||||
}
|
||||
MessageType.MSGTYPE_IMAGE -> {
|
||||
return simpleFormat(senderName, stringProvider.getString(R.string.sent_an_image), appendAuthor)
|
||||
simpleFormat(senderName, stringProvider.getString(R.string.sent_an_image), appendAuthor)
|
||||
}
|
||||
MessageType.MSGTYPE_AUDIO -> {
|
||||
if ((messageContent as? MessageAudioContent)?.voiceMessageIndicator != null) {
|
||||
return simpleFormat(senderName, stringProvider.getString(R.string.sent_a_voice_message), appendAuthor)
|
||||
simpleFormat(senderName, stringProvider.getString(R.string.sent_a_voice_message), appendAuthor)
|
||||
} else {
|
||||
return simpleFormat(senderName, stringProvider.getString(R.string.sent_an_audio_file), appendAuthor)
|
||||
simpleFormat(senderName, stringProvider.getString(R.string.sent_an_audio_file), appendAuthor)
|
||||
}
|
||||
}
|
||||
MessageType.MSGTYPE_VIDEO -> {
|
||||
return simpleFormat(senderName, stringProvider.getString(R.string.sent_a_video), appendAuthor)
|
||||
simpleFormat(senderName, stringProvider.getString(R.string.sent_a_video), appendAuthor)
|
||||
}
|
||||
MessageType.MSGTYPE_FILE -> {
|
||||
return simpleFormat(senderName, stringProvider.getString(R.string.sent_a_file), appendAuthor)
|
||||
}
|
||||
MessageType.MSGTYPE_TEXT -> {
|
||||
return if (timelineEvent.isReply()) {
|
||||
// Skip reply prefix, and show important
|
||||
// TODO add a reply image span ?
|
||||
simpleFormat(senderName, timelineEvent.getTextEditableContent()
|
||||
?: messageContent.body, appendAuthor)
|
||||
} else {
|
||||
simpleFormat(senderName, messageContent.body, appendAuthor)
|
||||
}
|
||||
}
|
||||
MessageType.MSGTYPE_RESPONSE -> {
|
||||
// do not show that?
|
||||
return span { }
|
||||
span { }
|
||||
}
|
||||
MessageType.MSGTYPE_OPTIONS -> {
|
||||
return when (messageContent) {
|
||||
when (messageContent) {
|
||||
is MessageOptionsContent -> {
|
||||
val previewText = if (messageContent.optionType == OPTION_TYPE_BUTTONS) {
|
||||
stringProvider.getString(R.string.sent_a_bot_buttons)
|
||||
|
@ -115,15 +110,24 @@ class DisplayableEventFormatter @Inject constructor(
|
|||
}
|
||||
}
|
||||
else -> {
|
||||
return simpleFormat(senderName, messageContent.body, appendAuthor)
|
||||
simpleFormat(senderName, messageContent.body, appendAuthor)
|
||||
}
|
||||
}
|
||||
}
|
||||
} ?: span { }
|
||||
}
|
||||
EventType.STICKER -> {
|
||||
simpleFormat(senderName, stringProvider.getString(R.string.send_a_sticker), appendAuthor)
|
||||
}
|
||||
EventType.REACTION -> {
|
||||
timelineEvent.root.getClearContent().toModel<ReactionContent>()?.relatesTo?.let {
|
||||
val emojiSpanned = emojiCompatWrapper.safeEmojiSpanify(stringProvider.getString(R.string.sent_a_reaction, it.key))
|
||||
simpleFormat(senderName, emojiSpanned, appendAuthor)
|
||||
} ?: span { }
|
||||
}
|
||||
EventType.KEY_VERIFICATION_CANCEL,
|
||||
EventType.KEY_VERIFICATION_DONE -> {
|
||||
// cancel and done can appear in timeline, so should have representation
|
||||
return simpleFormat(senderName, stringProvider.getString(R.string.sent_verification_conclusion), appendAuthor)
|
||||
simpleFormat(senderName, stringProvider.getString(R.string.sent_verification_conclusion), appendAuthor)
|
||||
}
|
||||
EventType.KEY_VERIFICATION_START,
|
||||
EventType.KEY_VERIFICATION_ACCEPT,
|
||||
|
@ -131,17 +135,15 @@ class DisplayableEventFormatter @Inject constructor(
|
|||
EventType.KEY_VERIFICATION_KEY,
|
||||
EventType.KEY_VERIFICATION_READY,
|
||||
EventType.CALL_CANDIDATES -> {
|
||||
return span { }
|
||||
span { }
|
||||
}
|
||||
else -> {
|
||||
return span {
|
||||
span {
|
||||
text = noticeEventFormatter.format(timelineEvent, isDm) ?: ""
|
||||
textStyle = "italic"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return span { }
|
||||
}
|
||||
|
||||
private fun simpleFormat(senderName: String, body: CharSequence, appendAuthor: Boolean): CharSequence {
|
||||
|
|
Loading…
Add table
Reference in a new issue