Add Markdown support to thread summaries and thread list
This commit is contained in:
parent
eda723c230
commit
214e0efcd9
|
@ -16,6 +16,7 @@
|
|||
|
||||
package org.matrix.android.sdk.api.session.threads
|
||||
|
||||
import org.matrix.android.sdk.api.session.events.model.Event
|
||||
import org.matrix.android.sdk.api.session.room.sender.SenderInfo
|
||||
|
||||
/**
|
||||
|
@ -26,7 +27,7 @@ data class ThreadDetails(
|
|||
val isRootThread: Boolean = false,
|
||||
val numberOfThreads: Int = 0,
|
||||
val threadSummarySenderInfo: SenderInfo? = null,
|
||||
val threadSummaryLatestTextMessage: String? = null,
|
||||
val threadSummaryLatestEvent: Event? = null,
|
||||
val lastMessageTimestamp: Long? = null,
|
||||
var threadNotificationState: ThreadNotificationState = ThreadNotificationState.NO_NEW_MESSAGE,
|
||||
val isThread: Boolean = false,
|
||||
|
|
|
@ -114,7 +114,7 @@ internal object EventMapper {
|
|||
)
|
||||
},
|
||||
threadNotificationState = eventEntity.threadNotificationState,
|
||||
threadSummaryLatestTextMessage = eventEntity.threadSummaryLatestMessage?.root?.asDomain()?.getDecryptedTextSummary(),
|
||||
threadSummaryLatestEvent = eventEntity.threadSummaryLatestMessage?.root?.asDomain(),
|
||||
lastMessageTimestamp = eventEntity.threadSummaryLatestMessage?.root?.originServerTs
|
||||
|
||||
)
|
||||
|
|
|
@ -32,6 +32,7 @@ import im.vector.app.core.resources.StringProvider
|
|||
import im.vector.app.core.resources.UserPreferencesProvider
|
||||
import im.vector.app.core.ui.list.GenericHeaderItem_
|
||||
import im.vector.app.features.home.AvatarRenderer
|
||||
import im.vector.app.features.home.room.detail.timeline.format.DisplayableEventFormatter
|
||||
import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.session.events.model.Content
|
||||
|
@ -45,6 +46,7 @@ class SearchResultController @Inject constructor(
|
|||
private val avatarRenderer: AvatarRenderer,
|
||||
private val stringProvider: StringProvider,
|
||||
private val dateFormatter: VectorDateFormatter,
|
||||
private val displayableEventFormatter: DisplayableEventFormatter,
|
||||
private val userPreferencesProvider: UserPreferencesProvider
|
||||
) : TypedEpoxyController<SearchViewState>() {
|
||||
|
||||
|
@ -125,6 +127,7 @@ class SearchResultController @Inject constructor(
|
|||
.sender(eventAndSender.sender
|
||||
?: eventAndSender.event.senderId?.let { session.getRoomMember(it, data.roomId) }?.toMatrixItem())
|
||||
.threadDetails(event.threadDetails)
|
||||
.threadSummaryFormatted(displayableEventFormatter.formatThreadSummary(event.threadDetails?.threadSummaryLatestEvent).toString())
|
||||
.areThreadMessagesEnabled(userPreferencesProvider.areThreadMessagesEnabled())
|
||||
.listener { listener?.onItemClicked(eventAndSender.event) }
|
||||
.let { result.add(it) }
|
||||
|
|
|
@ -42,6 +42,7 @@ abstract class SearchResultItem : VectorEpoxyModel<SearchResultItem.Holder>() {
|
|||
@EpoxyAttribute lateinit var spannable: EpoxyCharSequence
|
||||
@EpoxyAttribute var sender: MatrixItem? = null
|
||||
@EpoxyAttribute var threadDetails: ThreadDetails? = null
|
||||
@EpoxyAttribute var threadSummaryFormatted: String? = null
|
||||
@EpoxyAttribute var areThreadMessagesEnabled: Boolean = false
|
||||
|
||||
@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var listener: ClickListener? = null
|
||||
|
@ -60,8 +61,7 @@ abstract class SearchResultItem : VectorEpoxyModel<SearchResultItem.Holder>() {
|
|||
if (it.isRootThread) {
|
||||
showThreadSummary(holder)
|
||||
holder.threadSummaryCounterTextView.text = it.numberOfThreads.toString()
|
||||
holder.threadSummaryInfoTextView.text = it.threadSummaryLatestTextMessage.orEmpty()
|
||||
|
||||
holder.threadSummaryInfoTextView.text = threadSummaryFormatted.orEmpty()
|
||||
val userId = it.threadSummarySenderInfo?.userId ?: return@let
|
||||
val displayName = it.threadSummarySenderInfo?.displayName
|
||||
val avatarUrl = it.threadSummarySenderInfo?.avatarUrl
|
||||
|
|
|
@ -24,9 +24,11 @@ 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.Event
|
||||
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.MessageContent
|
||||
import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent
|
||||
import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent
|
||||
import org.matrix.android.sdk.api.session.room.model.message.MessageType
|
||||
|
@ -139,6 +141,98 @@ class DisplayableEventFormatter @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
fun formatThreadSummary(
|
||||
event: Event?,
|
||||
latestEdition: String? = null): CharSequence {
|
||||
event ?: return ""
|
||||
|
||||
// There event have been edited
|
||||
if (latestEdition != null) {
|
||||
return run {
|
||||
val localFormattedBody = htmlRenderer.get().parse(latestEdition) as Document
|
||||
val renderedBody = htmlRenderer.get().render(localFormattedBody) ?: latestEdition
|
||||
renderedBody
|
||||
}
|
||||
}
|
||||
|
||||
// The event have been redacted
|
||||
if (event.isRedacted()) {
|
||||
return noticeEventFormatter.formatRedactedEvent(event)
|
||||
}
|
||||
|
||||
// The event is encrypted
|
||||
if (event.isEncrypted() &&
|
||||
event.mxDecryptionResult == null) {
|
||||
return stringProvider.getString(R.string.encrypted_message)
|
||||
}
|
||||
|
||||
return when (event.getClearType()) {
|
||||
EventType.MESSAGE -> {
|
||||
(event.getClearContent().toModel() as? MessageContent)?.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
|
||||
renderedBody
|
||||
} else {
|
||||
body
|
||||
}
|
||||
}
|
||||
MessageType.MSGTYPE_VERIFICATION_REQUEST -> {
|
||||
stringProvider.getString(R.string.verification_request)
|
||||
}
|
||||
MessageType.MSGTYPE_IMAGE -> {
|
||||
stringProvider.getString(R.string.sent_an_image)
|
||||
}
|
||||
MessageType.MSGTYPE_AUDIO -> {
|
||||
if ((messageContent as? MessageAudioContent)?.voiceMessageIndicator != null) {
|
||||
stringProvider.getString(R.string.sent_a_voice_message)
|
||||
} else {
|
||||
stringProvider.getString(R.string.sent_an_audio_file)
|
||||
}
|
||||
}
|
||||
MessageType.MSGTYPE_VIDEO -> {
|
||||
stringProvider.getString(R.string.sent_a_video)
|
||||
}
|
||||
MessageType.MSGTYPE_FILE -> {
|
||||
stringProvider.getString(R.string.sent_a_file)
|
||||
}
|
||||
MessageType.MSGTYPE_LOCATION -> {
|
||||
stringProvider.getString(R.string.sent_location)
|
||||
}
|
||||
else -> {
|
||||
messageContent.body
|
||||
}
|
||||
}
|
||||
} ?: span { }
|
||||
}
|
||||
EventType.STICKER -> {
|
||||
stringProvider.getString(R.string.send_a_sticker)
|
||||
}
|
||||
EventType.REACTION -> {
|
||||
event.getClearContent().toModel<ReactionContent>()?.relatesTo?.let {
|
||||
emojiSpanify.spanify(stringProvider.getString(R.string.sent_a_reaction, it.key))
|
||||
} ?: span { }
|
||||
}
|
||||
EventType.POLL_START -> {
|
||||
event.getClearContent().toModel<MessagePollContent>(catchError = true)?.pollCreationInfo?.question?.question
|
||||
?: stringProvider.getString(R.string.sent_a_poll)
|
||||
}
|
||||
EventType.POLL_RESPONSE -> {
|
||||
stringProvider.getString(R.string.poll_response_room_list_preview)
|
||||
}
|
||||
EventType.POLL_END -> {
|
||||
stringProvider.getString(R.string.poll_end_room_list_preview)
|
||||
}
|
||||
else -> {
|
||||
span {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun simpleFormat(senderName: String, body: CharSequence, appendAuthor: Boolean): CharSequence {
|
||||
return if (appendAuthor) {
|
||||
span {
|
||||
|
|
|
@ -22,6 +22,7 @@ import im.vector.app.core.resources.UserPreferencesProvider
|
|||
import im.vector.app.features.home.AvatarRenderer
|
||||
import im.vector.app.features.home.room.detail.timeline.MessageColorProvider
|
||||
import im.vector.app.features.home.room.detail.timeline.TimelineEventController
|
||||
import im.vector.app.features.home.room.detail.timeline.format.DisplayableEventFormatter
|
||||
import im.vector.app.features.home.room.detail.timeline.item.AbsMessageItem
|
||||
import im.vector.app.features.home.room.detail.timeline.item.MessageInformationData
|
||||
import org.matrix.android.sdk.api.session.threads.ThreadDetails
|
||||
|
@ -32,6 +33,7 @@ class MessageItemAttributesFactory @Inject constructor(
|
|||
private val messageColorProvider: MessageColorProvider,
|
||||
private val avatarSizeProvider: AvatarSizeProvider,
|
||||
private val stringProvider: StringProvider,
|
||||
private val displayableEventFormatter: DisplayableEventFormatter,
|
||||
private val preferencesProvider: UserPreferencesProvider,
|
||||
private val emojiCompatFontProvider: EmojiCompatFontProvider) {
|
||||
|
||||
|
@ -59,6 +61,7 @@ class MessageItemAttributesFactory @Inject constructor(
|
|||
readReceiptsCallback = callback,
|
||||
emojiTypeFace = emojiCompatFontProvider.typeface,
|
||||
decryptionErrorMessage = stringProvider.getString(R.string.encrypted_message),
|
||||
threadSummaryFormatted = displayableEventFormatter.formatThreadSummary(threadDetails?.threadSummaryLatestEvent).toString(),
|
||||
threadDetails = threadDetails,
|
||||
areThreadMessagesEnabled = preferencesProvider.areThreadMessagesEnabled()
|
||||
)
|
||||
|
|
|
@ -115,7 +115,7 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : AbsBaseMessageItem<H>
|
|||
attributes.threadDetails?.let { threadDetails ->
|
||||
holder.threadSummaryConstraintLayout.isVisible = threadDetails.isRootThread
|
||||
holder.threadSummaryCounterTextView.text = threadDetails.numberOfThreads.toString()
|
||||
holder.threadSummaryInfoTextView.text = threadDetails.threadSummaryLatestTextMessage ?: attributes.decryptionErrorMessage
|
||||
holder.threadSummaryInfoTextView.text = attributes.threadSummaryFormatted ?: attributes.decryptionErrorMessage
|
||||
|
||||
val userId = threadDetails.threadSummarySenderInfo?.userId ?: return@let
|
||||
val displayName = threadDetails.threadSummarySenderInfo?.displayName
|
||||
|
@ -183,6 +183,7 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : AbsBaseMessageItem<H>
|
|||
override val readReceiptsCallback: TimelineEventController.ReadReceiptsCallback? = null,
|
||||
val emojiTypeFace: Typeface? = null,
|
||||
val decryptionErrorMessage: String? = null,
|
||||
val threadSummaryFormatted: String? = null,
|
||||
val threadDetails: ThreadDetails? = null,
|
||||
val areThreadMessagesEnabled: Boolean = false
|
||||
) : AbsBaseMessageItem.Attributes {
|
||||
|
|
|
@ -17,11 +17,11 @@
|
|||
package im.vector.app.features.home.room.threads.list.viewmodel
|
||||
|
||||
import com.airbnb.epoxy.EpoxyController
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.date.DateFormatKind
|
||||
import im.vector.app.core.date.VectorDateFormatter
|
||||
import im.vector.app.core.resources.StringProvider
|
||||
import im.vector.app.features.home.AvatarRenderer
|
||||
import im.vector.app.features.home.room.detail.timeline.format.DisplayableEventFormatter
|
||||
import im.vector.app.features.home.room.threads.list.model.threadListItem
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.session.room.threads.model.ThreadSummary
|
||||
|
@ -35,6 +35,7 @@ class ThreadListController @Inject constructor(
|
|||
private val avatarRenderer: AvatarRenderer,
|
||||
private val stringProvider: StringProvider,
|
||||
private val dateFormatter: VectorDateFormatter,
|
||||
private val displayableEventFormatter: DisplayableEventFormatter,
|
||||
private val session: Session
|
||||
) : EpoxyController() {
|
||||
|
||||
|
@ -70,9 +71,18 @@ class ThreadListController @Inject constructor(
|
|||
}
|
||||
?.forEach { threadSummary ->
|
||||
val date = dateFormatter.format(threadSummary.latestEvent?.originServerTs, DateFormatKind.ROOM_LIST)
|
||||
val decryptionErrorMessage = stringProvider.getString(R.string.encrypted_message)
|
||||
val rootThreadEdition = threadSummary.threadEditions.rootThreadEdition
|
||||
val latestThreadEdition = threadSummary.threadEditions.latestThreadEdition
|
||||
val lastMessageFormatted = threadSummary.let {
|
||||
displayableEventFormatter.formatThreadSummary(
|
||||
event = it.latestEvent,
|
||||
latestEdition = it.threadEditions.latestThreadEdition
|
||||
).toString()
|
||||
}
|
||||
val rootMessageFormatted = threadSummary.let {
|
||||
displayableEventFormatter.formatThreadSummary(
|
||||
event = it.rootEvent,
|
||||
latestEdition = it.threadEditions.rootThreadEdition
|
||||
).toString()
|
||||
}
|
||||
threadListItem {
|
||||
id(threadSummary.rootEvent?.eventId)
|
||||
avatarRenderer(host.avatarRenderer)
|
||||
|
@ -82,8 +92,8 @@ class ThreadListController @Inject constructor(
|
|||
rootMessageDeleted(threadSummary.rootEvent?.isRedacted() ?: false)
|
||||
// TODO refactor notifications that with the new thread summary
|
||||
threadNotificationState(threadSummary.rootEvent?.threadDetails?.threadNotificationState ?: ThreadNotificationState.NO_NEW_MESSAGE)
|
||||
rootMessage(rootThreadEdition ?: threadSummary.rootEvent?.getDecryptedTextSummary() ?: decryptionErrorMessage)
|
||||
lastMessage(latestThreadEdition ?: threadSummary.latestEvent?.getDecryptedTextSummary() ?: decryptionErrorMessage)
|
||||
rootMessage(rootMessageFormatted)
|
||||
lastMessage(lastMessageFormatted)
|
||||
lastMessageCounter(threadSummary.numberOfThreads.toString())
|
||||
lastMessageMatrixItem(threadSummary.latestThreadSenderInfo.toMatrixItemOrNull())
|
||||
itemClickListener {
|
||||
|
@ -112,8 +122,18 @@ class ThreadListController @Inject constructor(
|
|||
}
|
||||
?.forEach { timelineEvent ->
|
||||
val date = dateFormatter.format(timelineEvent.root.threadDetails?.lastMessageTimestamp, DateFormatKind.ROOM_LIST)
|
||||
val decryptionErrorMessage = stringProvider.getString(R.string.encrypted_message)
|
||||
val lastRootThreadEdition = timelineEvent.root.threadDetails?.lastRootThreadEdition
|
||||
val lastMessageFormatted = timelineEvent.root.threadDetails?.threadSummaryLatestEvent.let {
|
||||
displayableEventFormatter.formatThreadSummary(
|
||||
event = it,
|
||||
).toString()
|
||||
}
|
||||
val rootMessageFormatted = timelineEvent.root.let {
|
||||
displayableEventFormatter.formatThreadSummary(
|
||||
event = it,
|
||||
latestEdition = lastRootThreadEdition
|
||||
).toString()
|
||||
}
|
||||
threadListItem {
|
||||
id(timelineEvent.eventId)
|
||||
avatarRenderer(host.avatarRenderer)
|
||||
|
@ -122,8 +142,8 @@ class ThreadListController @Inject constructor(
|
|||
date(date)
|
||||
rootMessageDeleted(timelineEvent.root.isRedacted())
|
||||
threadNotificationState(timelineEvent.root.threadDetails?.threadNotificationState ?: ThreadNotificationState.NO_NEW_MESSAGE)
|
||||
rootMessage(lastRootThreadEdition ?: timelineEvent.root.getDecryptedTextSummary() ?: decryptionErrorMessage)
|
||||
lastMessage(timelineEvent.root.threadDetails?.threadSummaryLatestTextMessage ?: decryptionErrorMessage)
|
||||
rootMessage(rootMessageFormatted)
|
||||
lastMessage(lastMessageFormatted)
|
||||
lastMessageCounter(timelineEvent.root.threadDetails?.numberOfThreads.toString())
|
||||
lastMessageMatrixItem(timelineEvent.root.threadDetails?.threadSummarySenderInfo?.toMatrixItem())
|
||||
itemClickListener {
|
||||
|
|
Loading…
Reference in New Issue