-
Notifications
You must be signed in to change notification settings - Fork 858
Markdown and sploiler in roomlist + spoiler in notifications #4483
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
b781a64
e1de1a2
97fff27
babc2e4
293291c
e4b1edb
8e78328
a526397
8996970
06587c6
ddc1824
4ce361e
5f59d67
153694c
936f5c8
8f06337
2a34cd2
b5127c7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| Make notification text spoiler aware |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| Render markdown in room list |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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 | ||
| } | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This fun should now be used when we want to display an Event, but not when user wants to edit it |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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 { | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This would deserve some unit test...
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. although it uses |
||
| // 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 = "█" | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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 -> { | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Move below since this is less common than |
||
| 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 -> { | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Moved to the beginning since this is the most common case |
||
| 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,33 +110,40 @@ 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, | ||
| EventType.KEY_VERIFICATION_MAC, | ||
| 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 { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a small clean up here, should not change anything.