diff --git a/CHANGES.md b/CHANGES.md index 7cffa1ff..7fcf6842 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,6 +4,7 @@ Changes in RiotX 0.2.1 (2019-XX-XX) Features: - Message Editing: View edit history (#121) - Rooms filtering (#304) + - Edit in encrypted room Improvements: - Handle click on redacted events: view source and create permalink diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/relation/RelationService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/relation/RelationService.kt index da91ee70..0c4e1beb 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/relation/RelationService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/relation/RelationService.kt @@ -85,14 +85,12 @@ interface RelationService { * Edit a reply. This is a special case because replies contains fallback text as a prefix. * This method will take the new body (stripped from fallbacks) and re-add them before sending. * @param replyToEdit The event to edit - * @param originalSenderId the sender of the message that this reply (being edited) is relating to - * @param originalEventId the event id that this reply (being edited) is relating to + * @param originalTimelineEvent the message that this reply (being edited) is relating to * @param newBodyText The edited body (stripped from in reply to content) * @param compatibilityBodyText The text that will appear on clients that don't support yet edition */ fun editReply(replyToEdit: TimelineEvent, - originalSenderId: String?, - originalEventId : String, + originalTimelineEvent: TimelineEvent, newBodyText: String, compatibilityBodyText: String = "* $newBodyText"): Cancelable diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineEvent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineEvent.kt index 761c3961..044aa957 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineEvent.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineEvent.kt @@ -24,6 +24,7 @@ import im.vector.matrix.android.api.session.room.model.message.MessageContent import im.vector.matrix.android.api.session.room.model.message.isReply import im.vector.matrix.android.api.session.room.send.SendState import im.vector.matrix.android.api.util.ContentUtils.extractUsefulTextFromReply +import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent /** * This data class is a wrapper around an Event. It allows to get useful data in the context of a timeline. @@ -94,7 +95,7 @@ fun TimelineEvent.getLastMessageContent(): MessageContent? = annotations?.editSu fun TimelineEvent.getTextEditableContent(): String? { val originalContent = root.getClearContent().toModel() ?: return null - val isReply = originalContent.isReply() + val isReply = originalContent.isReply() || root.content.toModel()?.relatesTo?.inReplyTo?.eventId != null val lastContent = getLastMessageContent() return if (isReply) { return extractUsefulTextFromReply(lastContent?.body ?: "") diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/event/EncryptedEventContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/event/EncryptedEventContent.kt index 108ce056..4d06b737 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/event/EncryptedEventContent.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/event/EncryptedEventContent.kt @@ -17,6 +17,7 @@ package im.vector.matrix.android.internal.crypto.model.event import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultContent /** * Class representing an encrypted event content @@ -52,5 +53,8 @@ data class EncryptedEventContent( * The session id */ @Json(name = "session_id") - val sessionId: String? = null + val sessionId: String? = null, + + //Relation context is in clear in encrypted message + @Json(name = "m.relates_to") val relatesTo: RelationDefaultContent? = null ) \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationTask.kt index b55a7e14..867ca287 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationTask.kt @@ -17,9 +17,13 @@ package im.vector.matrix.android.internal.session.room import arrow.core.Try import com.zhuinden.monarchy.Monarchy +import im.vector.matrix.android.api.session.crypto.CryptoService +import im.vector.matrix.android.api.session.crypto.MXCryptoError import im.vector.matrix.android.api.session.events.model.* import im.vector.matrix.android.api.session.room.model.message.MessageContent import im.vector.matrix.android.api.session.room.model.relation.ReactionContent +import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult +import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent import im.vector.matrix.android.internal.database.mapper.ContentMapper import im.vector.matrix.android.internal.database.mapper.EventMapper import im.vector.matrix.android.internal.database.model.* @@ -43,7 +47,9 @@ internal interface EventRelationsAggregationTask : Task + EventAnnotationsSummaryEntity.where(realm, event.eventId + ?: "").findFirst()?.let { + TimelineEventEntity.where(realm, eventId = event.eventId + ?: "").findFirst()?.let { tet -> tet.annotations = it } } } + + EventType.ENCRYPTED -> { + //Relation type is in clear + val encryptedEventContent = event.content.toModel() + if (encryptedEventContent?.relatesTo?.type == RelationType.REPLACE) { + //we need to decrypt if needed + if (event.mxDecryptionResult == null) { + try { + val result = cryptoService.decryptEvent(event, event.roomId) + event.mxDecryptionResult = OlmDecryptionResult( + payload = result.clearEvent, + senderKey = result.senderCurve25519Key, + keysClaimed = result.claimedEd25519Key?.let { k -> mapOf("ed25519" to k) }, + forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain + ) + } catch (e: MXCryptoError) { + Timber.w("Failed to decrypt e2e replace") + //TODO -> we should keep track of this and retry, or aggregation will be broken + } + } + event.getClearContent().toModel()?.let { + Timber.v("###REPLACE in room $roomId for event ${event.eventId}") + //A replace! + handleReplace(realm, event, it, roomId, isLocalEcho, encryptedEventContent.relatesTo.eventId) + } + } + } EventType.REDACTION -> { val eventToPrune = event.redacts?.let { EventEntity.where(realm, eventId = it).findFirst() } ?: return@forEach @@ -125,9 +160,9 @@ internal class DefaultEventRelationsAggregationTask @Inject constructor(private } - private fun handleReplace(realm: Realm, event: Event, content: MessageContent, roomId: String, isLocalEcho: Boolean) { + private fun handleReplace(realm: Realm, event: Event, content: MessageContent, roomId: String, isLocalEcho: Boolean, relatedEventId: String? = null) { val eventId = event.eventId ?: return - val targetEventId = content.relatesTo?.eventId ?: return + val targetEventId = relatedEventId ?: content.relatesTo?.eventId ?: return val newContent = content.newContent ?: return //ok, this is a replace var existing = EventAnnotationsSummaryEntity.where(realm, targetEventId).findFirst() diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/prune/PruneEventTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/prune/PruneEventTask.kt index 8ea8c337..24dc14a7 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/prune/PruneEventTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/prune/PruneEventTask.kt @@ -61,13 +61,13 @@ internal class DefaultPruneEventTask @Inject constructor(private val monarchy: M } val redactionEventEntity = EventEntity.where(realm, eventId = redactionEvent.eventId - ?: "").findFirst() - ?: return + ?: "").findFirst() + ?: return val isLocalEcho = redactionEventEntity.sendState == SendState.UNSENT Timber.v("Redact event for ${redactionEvent.redacts} localEcho=$isLocalEcho") val eventToPrune = EventEntity.where(realm, eventId = redactionEvent.redacts).findFirst() - ?: return + ?: return val allowedKeys = computeAllowedKeys(eventToPrune.type) if (allowedKeys.isNotEmpty()) { @@ -75,10 +75,11 @@ internal class DefaultPruneEventTask @Inject constructor(private val monarchy: M eventToPrune.content = ContentMapper.map(prunedContent) } else { when (eventToPrune.type) { + EventType.ENCRYPTED, EventType.MESSAGE -> { Timber.d("REDACTION for message ${eventToPrune.eventId}") val unsignedData = EventMapper.map(eventToPrune).unsignedData - ?: UnsignedData(null, null) + ?: UnsignedData(null, null) //was this event a m.replace // val contentModel = ContentMapper.map(eventToPrune.content)?.toModel() @@ -89,6 +90,8 @@ internal class DefaultPruneEventTask @Inject constructor(private val monarchy: M val modified = unsignedData.copy(redactedEvent = redactionEvent) eventToPrune.content = ContentMapper.map(emptyMap()) eventToPrune.unsignedData = MoshiProvider.providesMoshi().adapter(UnsignedData::class.java).toJson(modified) + eventToPrune.decryptionResultJson = null + eventToPrune.decryptionErrorCode = null } // EventType.REACTION -> { @@ -112,14 +115,14 @@ internal class DefaultPruneEventTask @Inject constructor(private val monarchy: M EventType.STATE_ROOM_CREATE -> listOf("creator") EventType.STATE_ROOM_JOIN_RULES -> listOf("join_rule") EventType.STATE_ROOM_POWER_LEVELS -> listOf("users", - "users_default", - "events", - "events_default", - "state_default", - "ban", - "kick", - "redact", - "invite") + "users_default", + "events", + "events_default", + "state_default", + "ban", + "kick", + "redact", + "invite") EventType.STATE_ROOM_ALIASES -> listOf("aliases") EventType.STATE_CANONICAL_ALIAS -> listOf("alias") EventType.FEEDBACK -> listOf("type", "target_event_id") diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/DefaultRelationService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/DefaultRelationService.kt index 37a7f094..183f2a6c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/DefaultRelationService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/DefaultRelationService.kt @@ -127,32 +127,47 @@ internal class DefaultRelationService @Inject constructor(private val context: C .also { saveLocalEcho(it) } - val workRequest = createSendEventWork(event) - TimelineSendEventWorkCommon.postWork(context, roomId, workRequest) - return CancelableWork(context, workRequest.id) + if (cryptoService.isRoomEncrypted(roomId)) { + val encryptWork = createEncryptEventWork(event, listOf("m.relates_to")) + val workRequest = createSendEventWork(event) + TimelineSendEventWorkCommon.postSequentialWorks(context, roomId, encryptWork, workRequest) + return CancelableWork(context, encryptWork.id) + + } else { + val workRequest = createSendEventWork(event) + TimelineSendEventWorkCommon.postWork(context, roomId, workRequest) + return CancelableWork(context, workRequest.id) + } } override fun editReply(replyToEdit: TimelineEvent, - originalSenderId: String?, - originalEventId: String, + originalEvent: TimelineEvent, newBodyText: String, compatibilityBodyText: String): Cancelable { val event = eventFactory .createReplaceTextOfReply(roomId, replyToEdit, - originalSenderId, originalEventId, + originalEvent, newBodyText, true, MessageType.MSGTYPE_TEXT, compatibilityBodyText) .also { saveLocalEcho(it) } - val workRequest = createSendEventWork(event) - TimelineSendEventWorkCommon.postWork(context, roomId, workRequest) - return CancelableWork(context, workRequest.id) + if (cryptoService.isRoomEncrypted(roomId)) { + val encryptWork = createEncryptEventWork(event, listOf("m.relates_to")) + val workRequest = createSendEventWork(event) + TimelineSendEventWorkCommon.postSequentialWorks(context, roomId, encryptWork, workRequest) + return CancelableWork(context, encryptWork.id) + + } else { + val workRequest = createSendEventWork(event) + TimelineSendEventWorkCommon.postWork(context, roomId, workRequest) + return CancelableWork(context, workRequest.id) + } } override fun fetchEditHistory(eventId: String, callback: MatrixCallback>) { - val params = FetchEditHistoryTask.Params(roomId, eventId) + val params = FetchEditHistoryTask.Params(roomId, cryptoService.isRoomEncrypted(roomId), eventId) fetchEditHistoryTask.configureWith(params) .dispatchTo(callback) .executeBy(taskExecutor) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/FetchEditHistoryTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/FetchEditHistoryTask.kt index e891ece8..7afbe288 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/FetchEditHistoryTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/FetchEditHistoryTask.kt @@ -29,6 +29,7 @@ internal interface FetchEditHistoryTask : Task> { return executeRequest { - apiCall = roomAPI.getRelations(params.roomId, params.eventId, RelationType.REPLACE, EventType.MESSAGE) + apiCall = roomAPI.getRelations(params.roomId, + params.eventId, + RelationType.REPLACE, + if (params.isRoomEncrypted) EventType.ENCRYPTED else EventType.MESSAGE) }.map { resp -> - resp.chunks + val events = resp.chunks.toMutableList() + resp.originalEvent?.let { events.add(it) } + events } } } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/RelationsResponse.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/RelationsResponse.kt index 5d39e81c..737165ff 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/RelationsResponse.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/RelationsResponse.kt @@ -22,6 +22,7 @@ import im.vector.matrix.android.api.session.events.model.Event @JsonClass(generateAdapter = true) internal data class RelationsResponse( @Json(name = "chunk") val chunks: List, + @Json(name = "original_event") val originalEvent: Event?, @Json(name = "next_batch") val nextBatch: String?, @Json(name = "prev_batch") val prevBatch: String? ) \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoEventFactory.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoEventFactory.kt index 40390d5d..c6b68647 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoEventFactory.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoEventFactory.kt @@ -105,28 +105,28 @@ internal class LocalEchoEventFactory @Inject constructor(private val credentials } fun createReplaceTextOfReply(roomId: String, eventReplaced: TimelineEvent, - originalSenderId: String?, - originalEventId: String, + originalEvent: TimelineEvent, newBodyText: String, newBodyAutoMarkdown: Boolean, msgType: String, compatibilityText: String): Event { - val permalink = PermalinkFactory.createPermalink(roomId, originalEventId) - val userLink = originalSenderId?.let { PermalinkFactory.createPermalink(it) } ?: "" + val permalink = PermalinkFactory.createPermalink(roomId, originalEvent.root.eventId ?: "") + val userLink = originalEvent.root.senderId?.let { PermalinkFactory.createPermalink(it) } + ?: "" - val body = bodyForReply(eventReplaced.getLastMessageContent(), eventReplaced.root.getClearContent().toModel()) + val body = bodyForReply(originalEvent.getLastMessageContent(), originalEvent.root.getClearContent().toModel()) val replyFormatted = REPLY_PATTERN.format( permalink, stringProvider.getString(R.string.message_reply_to_prefix), userLink, - originalSenderId, + originalEvent.senderName ?: originalEvent.root.senderId, body.takeFormatted(), createTextContent(newBodyText, newBodyAutoMarkdown).takeFormatted() ) // // > <@alice:example.org> This is the original body // - val replyFallback = buildReplyFallback(body, originalSenderId, newBodyText) + val replyFallback = buildReplyFallback(body, originalEvent.root.senderId ?: "", newBodyText) return createEvent(roomId, MessageTextContent( diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt index a15eae5b..d38561fa 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt @@ -38,6 +38,7 @@ import im.vector.matrix.android.api.session.room.model.message.MessageType import im.vector.matrix.android.api.session.room.model.message.getFileUrl import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.internal.crypto.attachments.toElementToDecrypt +import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent import im.vector.matrix.rx.rx import im.vector.riotx.core.intent.getFilenameFromUri import im.vector.riotx.core.platform.VectorViewModel @@ -229,9 +230,12 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro //is original event a reply? val inReplyTo = state.sendMode.timelineEvent.root.getClearContent().toModel()?.relatesTo?.inReplyTo?.eventId + ?: state.sendMode.timelineEvent.root.content.toModel()?.relatesTo?.inReplyTo?.eventId if (inReplyTo != null) { //TODO check if same content? - room.editReply(state.sendMode.timelineEvent, room.getTimeLineEvent(inReplyTo)?.root?.senderId, inReplyTo, action.text) + room.getTimeLineEvent(inReplyTo)?.let { + room.editReply(state.sendMode.timelineEvent, it, action.text) + } } else { val messageContent: MessageContent? = state.sendMode.timelineEvent.annotations?.editSummary?.aggregatedContent.toModel() diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageMenuViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageMenuViewModel.kt index 54ff3077..5b0dbdfe 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageMenuViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageMenuViewModel.kt @@ -244,7 +244,7 @@ class MessageMenuViewModel @AssistedInject constructor(@Assisted initialState: M //Only event of type Event.EVENT_TYPE_MESSAGE are supported for the moment if (event.root.getClearType() != EventType.MESSAGE) return false //TODO if user is admin or moderator - val messageContent = event.root.content.toModel() + val messageContent = event.root.getClearContent().toModel() return event.root.senderId == myUserId && ( messageContent?.type == MessageType.MSGTYPE_TEXT || messageContent?.type == MessageType.MSGTYPE_EMOTE diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/ViewEditHistoryEpoxyController.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/ViewEditHistoryEpoxyController.kt index 34e34073..fc11f255 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/ViewEditHistoryEpoxyController.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/ViewEditHistoryEpoxyController.kt @@ -75,9 +75,7 @@ class ViewEditHistoryEpoxyController(private val context: Context, } } else { var lastDate: Calendar? = null - sourceEvents.sortedByDescending { - it.originServerTs ?: 0 - }.forEachIndexed { index, timelineEvent -> + sourceEvents.forEachIndexed { index, timelineEvent -> val evDate = Calendar.getInstance().apply { timeInMillis = timelineEvent.originServerTs diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/ViewEditHistoryViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/ViewEditHistoryViewModel.kt index 576ef5e9..6ad17210 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/ViewEditHistoryViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/ViewEditHistoryViewModel.kt @@ -20,12 +20,16 @@ import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.session.Session +import im.vector.matrix.android.api.session.crypto.MXCryptoError import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.room.model.message.MessageContent import im.vector.matrix.android.api.session.room.model.message.isReply +import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.features.home.room.detail.timeline.helper.TimelineDateFormatter +import timber.log.Timber +import java.util.* data class ViewEditHistoryViewState( @@ -79,16 +83,36 @@ class ViewEditHistoryViewModel @AssistedInject constructor(@Assisted } override fun onSuccess(data: List) { - //TODO until supported by API Add original event manually - val withOriginal = data.toMutableList() var originalIsReply = false - room.getTimeLineEvent(eventId)?.let { - withOriginal.add(it.root) - originalIsReply = it.root.getClearContent().toModel().isReply() + + val events = data.map { event -> + val timelineID = event.roomId + UUID.randomUUID().toString() + event.also { + //We need to check encryption + if (it.isEncrypted() && it.mxDecryptionResult == null) { + //for now decrypt sync + try { + val result = session.decryptEvent(it, timelineID) + it.mxDecryptionResult = OlmDecryptionResult( + payload = result.clearEvent, + senderKey = result.senderCurve25519Key, + keysClaimed = result.claimedEd25519Key?.let { k -> mapOf("ed25519" to k) }, + forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain + ) + } catch (e: MXCryptoError) { + Timber.w("Failed to decrypt event in history") + } + } + + if (event.eventId == it.eventId) { + originalIsReply = it.getClearContent().toModel().isReply() + } + } + } setState { copy( - editList = Success(withOriginal), + editList = Success(events), isOriginalAReply = originalIsReply ) } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MessageItemFactory.kt index 42050482..3a0d2d1d 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MessageItemFactory.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MessageItemFactory.kt @@ -27,12 +27,14 @@ import dagger.Lazy import im.vector.matrix.android.api.permalinks.MatrixLinkify import im.vector.matrix.android.api.permalinks.MatrixPermalinkSpan import im.vector.matrix.android.api.session.events.model.RelationType +import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.room.model.EditAggregatedSummary import im.vector.matrix.android.api.session.room.model.message.* import im.vector.matrix.android.api.session.room.send.SendState import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.api.session.room.timeline.getLastMessageContent import im.vector.matrix.android.internal.crypto.attachments.toElementToDecrypt +import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent import im.vector.riotx.EmojiCompatFontProvider import im.vector.riotx.R import im.vector.riotx.core.epoxy.VectorEpoxyModel @@ -83,7 +85,9 @@ class MessageItemFactory @Inject constructor( ?: //Malformed content, we should echo something on screen return DefaultItem_().text(stringProvider.getString(R.string.malformed_message)) - if (messageContent.relatesTo?.type == RelationType.REPLACE) { + if (messageContent.relatesTo?.type == RelationType.REPLACE + || event.isEncrypted() && event.root.content.toModel()?.relatesTo?.type == RelationType.REPLACE + ) { // ignore replace event, the targeted id is already edited return BlankItem_() } @@ -229,7 +233,8 @@ class MessageItemFactory @Inject constructor( val (maxWidth, maxHeight) = timelineMediaSizeProvider.getMaxSize() val thumbnailData = ImageContentRenderer.Data( filename = messageContent.body, - url = messageContent.videoInfo?.thumbnailFile?.url ?: messageContent.videoInfo?.thumbnailUrl, + url = messageContent.videoInfo?.thumbnailFile?.url + ?: messageContent.videoInfo?.thumbnailUrl, elementToDecrypt = messageContent.videoInfo?.thumbnailFile?.toElementToDecrypt(), height = messageContent.videoInfo?.height, maxHeight = maxHeight,