diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml
index f05a2a11e6..eccd40b2a9 100644
--- a/library/ui-strings/src/main/res/values/strings.xml
+++ b/library/ui-strings/src/main/res/values/strings.xml
@@ -3458,4 +3458,13 @@
Apply underline format
Toggle full screen mode
+
+ In reply to
+ sent a file.
+ sent an audio file.
+ sent a voice message.
+ sent an image.
+ sent a video.
+ sent a sticker.
+ created a poll.
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt
index 57bd3dbc41..1c09b49298 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt
@@ -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()?.membership == Membership.INVITE
fun Event.getPollContent(): MessagePollContent? {
- return content.toModel()
+ return getDecryptedContent().toModel()
}
fun Event.supportsNotification() =
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt
index f4d506fa4b..373410775b 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt
@@ -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(
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/render/EventTextRenderer.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/render/EventTextRenderer.kt
index 920f3e3b80..c46112f995 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/render/EventTextRenderer.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/render/EventTextRenderer.kt
@@ -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,
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/render/ProcessBodyOfReplyToEventUseCase.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/render/ProcessBodyOfReplyToEventUseCase.kt
new file mode 100644
index 0000000000..44fd5a397c
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/render/ProcessBodyOfReplyToEventUseCase.kt
@@ -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 = "
"
+private const val ENDING_BLOCK_QUOTE = ""
+
+// 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
+}