Use case to process formatted body of reply to events

This commit is contained in:
Maxime NATUREL 2022-11-09 17:31:00 +01:00
parent ab90da0e51
commit 235b629130
5 changed files with 176 additions and 18 deletions

View File

@ -3458,4 +3458,13 @@
<string name="rich_text_editor_format_underline">Apply underline format</string>
<string name="rich_text_editor_full_screen_toggle">Toggle full screen mode</string>
<!-- ReplyTo events -->
<string name="message_reply_to_prefix">In reply to</string>
<string name="message_reply_to_sender_sent_file">sent a file.</string>
<string name="message_reply_to_sender_sent_audio_file">sent an audio file.</string>
<string name="message_reply_to_sender_sent_voice_message">sent a voice message.</string>
<string name="message_reply_to_sender_sent_image">sent an image.</string>
<string name="message_reply_to_sender_sent_video">sent a video.</string>
<string name="message_reply_to_sender_sent_sticker">sent a sticker.</string>
<string name="message_reply_to_sender_created_poll">created a poll.</string>
</resources>

View File

@ -229,11 +229,14 @@ data class Event(
return when {
isReplyRenderedInThread() || isQuote() -> ContentUtils.extractUsefulTextFromReply(text)
isFileMessage() -> "sent a file."
isVoiceMessage() -> "sent a voice message."
isAudioMessage() -> "sent an audio file."
isImageMessage() -> "sent an image."
isVideoMessage() -> "sent a video."
isSticker() -> "sent a sticker"
isSticker() -> "sent a sticker."
isPoll() -> getPollQuestion() ?: "created a poll."
isLiveLocation() -> "Live location."
isLocationMessage() -> "has shared their location."
else -> text
}
}
@ -444,7 +447,7 @@ fun Event.isInvitation(): Boolean = type == EventType.STATE_ROOM_MEMBER &&
content?.toModel<RoomMemberContent>()?.membership == Membership.INVITE
fun Event.getPollContent(): MessagePollContent? {
return content.toModel<MessagePollContent>()
return getDecryptedContent().toModel<MessagePollContent>()
}
fun Event.supportsNotification() =

View File

@ -65,6 +65,7 @@ import im.vector.app.features.home.room.detail.timeline.item.RedactedMessageItem
import im.vector.app.features.home.room.detail.timeline.item.VerificationRequestItem
import im.vector.app.features.home.room.detail.timeline.item.VerificationRequestItem_
import im.vector.app.features.home.room.detail.timeline.render.EventTextRenderer
import im.vector.app.features.home.room.detail.timeline.render.ProcessBodyOfReplyToEventUseCase
import im.vector.app.features.home.room.detail.timeline.tools.createLinkMovementMethod
import im.vector.app.features.home.room.detail.timeline.tools.linkify
import im.vector.app.features.html.EventHtmlRenderer
@ -106,6 +107,7 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageVideoContent
import org.matrix.android.sdk.api.session.room.model.message.asMessageAudioEvent
import org.matrix.android.sdk.api.session.room.model.message.getFileUrl
import org.matrix.android.sdk.api.session.room.model.message.getThumbnailUrl
import org.matrix.android.sdk.api.session.room.model.relation.ReplyToContent
import org.matrix.android.sdk.api.settings.LightweightSettingsStorage
import org.matrix.android.sdk.api.util.MimeTypes
import javax.inject.Inject
@ -139,6 +141,7 @@ class MessageItemFactory @Inject constructor(
private val liveLocationShareMessageItemFactory: LiveLocationShareMessageItemFactory,
private val pollItemViewStateFactory: PollItemViewStateFactory,
private val voiceBroadcastItemFactory: VoiceBroadcastItemFactory,
private val processBodyOfReplyToEventUseCase: ProcessBodyOfReplyToEventUseCase,
) {
// TODO inject this properly?
@ -200,7 +203,7 @@ class MessageItemFactory @Inject constructor(
is MessageVerificationRequestContent -> buildVerificationRequestMessageItem(messageContent, informationData, highlight, callback, attributes)
is MessagePollContent -> buildPollItem(messageContent, informationData, highlight, callback, attributes)
is MessageLocationContent -> buildLocationItem(messageContent, informationData, highlight, attributes)
is MessageBeaconInfoContent -> liveLocationShareMessageItemFactory.create(params.event, highlight, attributes)
is MessageBeaconInfoContent -> liveLocationShareMessageItemFactory.create(event, highlight, attributes)
is MessageVoiceBroadcastInfoContent -> voiceBroadcastItemFactory.create(params, messageContent, highlight, attributes)
else -> buildNotHandledMessageItem(messageContent, informationData, highlight, callback, attributes)
}
@ -437,7 +440,14 @@ class MessageItemFactory @Inject constructor(
attributes: AbsMessageItem.Attributes
): MessageTextItem? {
// For compatibility reason we should display the body
return buildMessageTextItem(messageContent.body, false, informationData, highlight, callback, attributes)
return buildMessageTextItem(
messageContent.body,
false,
informationData,
highlight,
callback,
attributes,
)
}
private fun buildImageMessageItem(
@ -540,7 +550,8 @@ class MessageItemFactory @Inject constructor(
): VectorEpoxyModel<*>? {
val matrixFormattedBody = messageContent.matrixFormattedBody
return if (matrixFormattedBody != null) {
buildFormattedTextItem(matrixFormattedBody, informationData, highlight, callback, attributes)
val replyToContent = messageContent.relatesTo?.inReplyTo
buildFormattedTextItem(matrixFormattedBody, informationData, highlight, callback, attributes, replyToContent)
} else {
buildMessageTextItem(messageContent.body, false, informationData, highlight, callback, attributes)
}
@ -552,10 +563,21 @@ class MessageItemFactory @Inject constructor(
highlight: Boolean,
callback: TimelineEventController.Callback?,
attributes: AbsMessageItem.Attributes,
replyToContent: ReplyToContent?,
): MessageTextItem? {
val compressed = htmlCompressor.compress(matrixFormattedBody)
val processedBody = replyToContent
?.let { processBodyOfReplyToEventUseCase.execute(roomId, matrixFormattedBody, it) }
?: matrixFormattedBody
val compressed = htmlCompressor.compress(processedBody)
val renderedFormattedBody = htmlRenderer.get().render(compressed, pillsPostProcessor) as Spanned
return buildMessageTextItem(renderedFormattedBody, true, informationData, highlight, callback, attributes)
return buildMessageTextItem(
renderedFormattedBody,
true,
informationData,
highlight,
callback,
attributes,
)
}
private fun buildMessageTextItem(

View File

@ -34,22 +34,22 @@ class EventTextRenderer @AssistedInject constructor(
@Assisted private val roomId: String?,
private val context: Context,
private val avatarRenderer: AvatarRenderer,
private val sessionHolder: ActiveSessionHolder
private val activeSessionHolder: ActiveSessionHolder,
) {
/* ==========================================================================================
* Public api
* ========================================================================================== */
@AssistedFactory
interface Factory {
fun create(roomId: String?): EventTextRenderer
}
/**
* @param text the text you want to render
* @param text the text to be rendered
*/
fun render(text: CharSequence): CharSequence {
return renderNotifyEveryone(text)
}
private fun renderNotifyEveryone(text: CharSequence): CharSequence {
return if (roomId != null && text.contains(MatrixItem.NOTIFY_EVERYONE)) {
SpannableStringBuilder(text).apply {
addNotifyEveryoneSpans(this, roomId)
@ -59,12 +59,8 @@ class EventTextRenderer @AssistedInject constructor(
}
}
/* ==========================================================================================
* Helper methods
* ========================================================================================== */
private fun addNotifyEveryoneSpans(text: Spannable, roomId: String) {
val room: RoomSummary? = sessionHolder.getSafeActiveSession()?.roomService()?.getRoomSummary(roomId)
val room: RoomSummary? = activeSessionHolder.getSafeActiveSession()?.roomService()?.getRoomSummary(roomId)
val matrixItem = MatrixItem.EveryoneInRoomItem(
id = roomId,
avatarUrl = room?.avatarUrl,

View File

@ -0,0 +1,128 @@
/*
* Copyright (c) 2022 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.app.features.home.room.detail.timeline.render
import im.vector.app.R
import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.resources.StringProvider
import org.matrix.android.sdk.api.session.events.model.getPollQuestion
import org.matrix.android.sdk.api.session.events.model.isAudioMessage
import org.matrix.android.sdk.api.session.events.model.isFileMessage
import org.matrix.android.sdk.api.session.events.model.isImageMessage
import org.matrix.android.sdk.api.session.events.model.isLiveLocation
import org.matrix.android.sdk.api.session.events.model.isPoll
import org.matrix.android.sdk.api.session.events.model.isSticker
import org.matrix.android.sdk.api.session.events.model.isVideoMessage
import org.matrix.android.sdk.api.session.events.model.isVoiceMessage
import org.matrix.android.sdk.api.session.getRoom
import org.matrix.android.sdk.api.session.room.getTimelineEvent
import org.matrix.android.sdk.api.session.room.model.relation.ReplyToContent
import javax.inject.Inject
private const val IN_REPLY_TO = "In reply to"
private const val BREAKING_LINE = "<br />"
private const val ENDING_BLOCK_QUOTE = "</blockquote>"
// TODO add unit tests
class ProcessBodyOfReplyToEventUseCase @Inject constructor(
private val activeSessionHolder: ActiveSessionHolder,
private val stringProvider: StringProvider,
) {
fun execute(roomId: String, matrixFormattedBody: String, replyToContent: ReplyToContent): String {
val repliedToEvent = replyToContent.eventId?.let { getEvent(it, roomId) }
val breakingLineIndex = matrixFormattedBody.lastIndexOf(BREAKING_LINE)
val endOfBlockQuoteIndex = matrixFormattedBody.lastIndexOf(ENDING_BLOCK_QUOTE)
// TODO check in other platform how is handled the case of no repliedToEvent fetched
val withTranslatedContent = if (repliedToEvent != null && breakingLineIndex != -1 && endOfBlockQuoteIndex != -1) {
val afterBreakingLineIndex = breakingLineIndex + BREAKING_LINE.length
when {
repliedToEvent.isFileMessage() -> {
matrixFormattedBody.replaceRange(
afterBreakingLineIndex,
endOfBlockQuoteIndex,
stringProvider.getString(R.string.message_reply_to_sender_sent_file)
)
}
repliedToEvent.isVoiceMessage() -> {
matrixFormattedBody.replaceRange(
afterBreakingLineIndex,
endOfBlockQuoteIndex,
stringProvider.getString(R.string.message_reply_to_sender_sent_voice_message)
)
}
repliedToEvent.isAudioMessage() -> {
matrixFormattedBody.replaceRange(
afterBreakingLineIndex,
endOfBlockQuoteIndex,
stringProvider.getString(R.string.message_reply_to_sender_sent_audio_file)
)
}
repliedToEvent.isImageMessage() -> {
matrixFormattedBody.replaceRange(
afterBreakingLineIndex,
endOfBlockQuoteIndex,
stringProvider.getString(R.string.message_reply_to_sender_sent_image)
)
}
repliedToEvent.isVideoMessage() -> {
matrixFormattedBody.replaceRange(
afterBreakingLineIndex,
endOfBlockQuoteIndex,
stringProvider.getString(R.string.message_reply_to_sender_sent_video)
)
}
repliedToEvent.isSticker() -> {
matrixFormattedBody.replaceRange(
afterBreakingLineIndex,
endOfBlockQuoteIndex,
stringProvider.getString(R.string.message_reply_to_sender_sent_sticker)
)
}
repliedToEvent.isPoll() -> {
matrixFormattedBody.replaceRange(
afterBreakingLineIndex,
endOfBlockQuoteIndex,
repliedToEvent.getPollQuestion() ?: stringProvider.getString(R.string.message_reply_to_sender_created_poll)
)
}
repliedToEvent.isLiveLocation() -> {
matrixFormattedBody.replaceRange(
afterBreakingLineIndex,
endOfBlockQuoteIndex,
stringProvider.getString(R.string.live_location_description)
)
}
else -> matrixFormattedBody
}
} else {
matrixFormattedBody
}
return withTranslatedContent.replace(
IN_REPLY_TO,
stringProvider.getString(R.string.message_reply_to_prefix)
)
}
private fun getEvent(eventId: String, roomId: String) =
activeSessionHolder.getSafeActiveSession()
?.getRoom(roomId)
?.getTimelineEvent(eventId)
?.root
}