diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineStateProvider.kt index 426f92eac1..e0a9d660ca 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineStateProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineStateProvider.kt @@ -162,7 +162,7 @@ internal fun aTimelineItemEvent( groupPosition = groupPosition, localSendState = sendState, inReplyTo = inReplyTo, - debugInfo = debugInfo, + debugInfoProvider = { debugInfo }, isThreaded = isThreaded, origin = null, messageShield = messageShield, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemEventFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemEventFactory.kt index c71e9f6a26..e71d0cf93e 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemEventFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemEventFactory.kt @@ -85,9 +85,9 @@ class TimelineItemEventFactory @AssistedInject constructor( localSendState = currentTimelineItem.event.localSendState, inReplyTo = currentTimelineItem.event.inReplyTo()?.map(permalinkParser = permalinkParser), isThreaded = currentTimelineItem.event.isThreaded(), - debugInfo = currentTimelineItem.event.debugInfo, + debugInfoProvider = currentTimelineItem.event.debugInfoProvider, origin = currentTimelineItem.event.origin, - messageShield = currentTimelineItem.event.messageShield, + messageShield = currentTimelineItem.event.messageShieldProvider.getShield(strict = false) ) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItem.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItem.kt index 12fb7830fd..038f5eb9fa 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItem.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItem.kt @@ -17,7 +17,7 @@ import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.TransactionId import io.element.android.libraries.matrix.api.core.UniqueId import io.element.android.libraries.matrix.api.core.UserId -import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo +import io.element.android.libraries.matrix.api.timeline.item.event.EventDebugInfoProvider import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState import io.element.android.libraries.matrix.api.timeline.item.event.MessageShield import io.element.android.libraries.matrix.api.timeline.item.event.ProfileTimelineDetails @@ -74,7 +74,7 @@ sealed interface TimelineItem { val localSendState: LocalEventSendState?, val inReplyTo: InReplyToDetails?, val isThreaded: Boolean, - val debugInfo: TimelineItemDebugInfo, + val debugInfoProvider: EventDebugInfoProvider, val origin: TimelineItemEventOrigin?, val messageShield: MessageShield?, ) : TimelineItem { @@ -89,6 +89,8 @@ sealed interface TimelineItem { val isSticker: Boolean = content is TimelineItemStickerContent val isRemote = eventId != null + + val debugInfo = debugInfoProvider.get() } @Immutable diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt index 0676efd258..be1b4aa7a8 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt @@ -493,7 +493,7 @@ class MessagesPresenterTest { canUserPinUnpinResult = { Result.success(true) }, ) - val redactEventLambda = lambdaRecorder { _: EventId?, _: TransactionId?, _: String? -> Result.success(true) } + val redactEventLambda = lambdaRecorder { _: EventId?, _: TransactionId?, _: String? -> Result.success(Unit) } liveTimeline.redactEventLambda = redactEventLambda val presenter = createMessagesPresenter(matrixRoom = matrixRoom, coroutineDispatchers = coroutineDispatchers) diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/fixtures/MessageEventFixtures.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/fixtures/MessageEventFixtures.kt index 7d3587a264..758557923f 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/fixtures/MessageEventFixtures.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/fixtures/MessageEventFixtures.kt @@ -58,7 +58,7 @@ internal fun aMessageEvent( readReceiptState = TimelineItemReadReceipts(emptyList().toImmutableList()), localSendState = sendState, inReplyTo = inReplyTo, - debugInfo = debugInfo, + debugInfoProvider = { debugInfo }, isThreaded = isThreaded, origin = null, messageShield = messageShield, diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/groups/TimelineItemGrouperTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/groups/TimelineItemGrouperTest.kt index 45eaa992c2..2e37145ff8 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/groups/TimelineItemGrouperTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/groups/TimelineItemGrouperTest.kt @@ -41,7 +41,7 @@ class TimelineItemGrouperTest { canBeRepliedTo = false, inReplyTo = null, isThreaded = false, - debugInfo = aTimelineItemDebugInfo(), + debugInfoProvider = { aTimelineItemDebugInfo() }, origin = null, messageShield = null, ) diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/RedactedVoiceMessageManagerTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/RedactedVoiceMessageManagerTest.kt index 1693b360c3..b9b08ab6d4 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/RedactedVoiceMessageManagerTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/RedactedVoiceMessageManagerTest.kt @@ -88,13 +88,15 @@ fun aRedactedMatrixTimeline(eventId: EventId) = listOf( senderProfile = ProfileTimelineDetails.Unavailable, timestamp = 9442, content = RedactedContent, - debugInfo = TimelineItemDebugInfo( - model = "enim", - originalJson = null, - latestEditedJson = null - ), + debugInfoProvider = { + TimelineItemDebugInfo( + model = "enim", + originalJson = null, + latestEditedJson = null + ) + }, origin = null, - messageShield = null, + messageShieldProvider = { null }, ), ) ) diff --git a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/data/PollRepository.kt b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/data/PollRepository.kt index a201e74452..839adc492f 100644 --- a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/data/PollRepository.kt +++ b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/data/PollRepository.kt @@ -59,7 +59,7 @@ class PollRepository @Inject constructor( suspend fun deletePoll( pollStartId: EventId, - ): Result = + ): Result = timelineProvider .getActiveTimeline() .redactEvent( diff --git a/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/create/CreatePollPresenterTest.kt b/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/create/CreatePollPresenterTest.kt index 8938c28ae7..d3abb3d10d 100644 --- a/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/create/CreatePollPresenterTest.kt +++ b/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/create/CreatePollPresenterTest.kt @@ -466,7 +466,7 @@ class CreatePollPresenterTest { @Test fun `delete confirms`() = runTest { val presenter = createCreatePollPresenter(mode = CreatePollMode.EditPoll(pollEventId)) - val redactEventLambda = lambdaRecorder { _: EventId?, _: TransactionId?, _: String? -> Result.success(true) } + val redactEventLambda = lambdaRecorder { _: EventId?, _: TransactionId?, _: String? -> Result.success(Unit) } timeline.redactEventLambda = redactEventLambda moleculeFlow(RecompositionMode.Immediate) { presenter.present() @@ -481,7 +481,7 @@ class CreatePollPresenterTest { @Test fun `delete can be cancelled`() = runTest { val presenter = createCreatePollPresenter(mode = CreatePollMode.EditPoll(pollEventId)) - val redactEventLambda = lambdaRecorder { _: EventId?, _: TransactionId?, _: String? -> Result.success(true) } + val redactEventLambda = lambdaRecorder { _: EventId?, _: TransactionId?, _: String? -> Result.success(Unit) } timeline.redactEventLambda = redactEventLambda moleculeFlow(RecompositionMode.Immediate) { presenter.present() @@ -499,7 +499,7 @@ class CreatePollPresenterTest { @Test fun `delete can be confirmed`() = runTest { val presenter = createCreatePollPresenter(mode = CreatePollMode.EditPoll(pollEventId)) - val redactEventLambda = lambdaRecorder { _: EventId?, _: TransactionId?, _: String? -> Result.success(true) } + val redactEventLambda = lambdaRecorder { _: EventId?, _: TransactionId?, _: String? -> Result.success(Unit) } timeline.redactEventLambda = redactEventLambda moleculeFlow(RecompositionMode.Immediate) { presenter.present() diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 510c8c0295..0cc72e19fe 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -163,7 +163,7 @@ jsoup = "org.jsoup:jsoup:1.18.1" appyx_core = { module = "com.bumble.appyx:core", version.ref = "appyx" } molecule-runtime = "app.cash.molecule:molecule-runtime:2.0.0" timber = "com.jakewharton.timber:timber:5.0.1" -matrix_sdk = "org.matrix.rustcomponents:sdk-android:0.2.49" +matrix_sdk = "org.matrix.rustcomponents:sdk-android:0.2.50" matrix_richtexteditor = { module = "io.element.android:wysiwyg", version.ref = "wysiwyg" } matrix_richtexteditor_compose = { module = "io.element.android:wysiwyg-compose", version.ref = "wysiwyg" } sqldelight-driver-android = { module = "app.cash.sqldelight:android-driver", version.ref = "sqldelight" } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt index d266b45d35..98ebc531a5 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt @@ -154,7 +154,7 @@ interface MatrixRoom : Closeable { suspend fun retrySendMessage(transactionId: TransactionId): Result - suspend fun cancelSend(transactionId: TransactionId): Result + suspend fun cancelSend(transactionId: TransactionId): Result suspend fun leave(): Result diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/Timeline.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/Timeline.kt index 5b91c6f971..70d5a5ab57 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/Timeline.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/Timeline.kt @@ -89,7 +89,7 @@ interface Timeline : AutoCloseable { progressCallback: ProgressCallback? ): Result - suspend fun redactEvent(eventId: EventId?, transactionId: TransactionId?, reason: String?): Result + suspend fun redactEvent(eventId: EventId?, transactionId: TransactionId?, reason: String?): Result suspend fun sendAudio(file: File, audioInfo: AudioInfo, progressCallback: ProgressCallback?): Result @@ -99,7 +99,7 @@ interface Timeline : AutoCloseable { suspend fun forwardEvent(eventId: EventId, roomIds: List): Result - suspend fun cancelSend(transactionId: TransactionId): Result + suspend fun cancelSend(transactionId: TransactionId): Result /** * Share a location message in the room. diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventTimelineItem.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventTimelineItem.kt index a76e8ecfd6..b311b4e6bf 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventTimelineItem.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventTimelineItem.kt @@ -28,9 +28,9 @@ data class EventTimelineItem( val senderProfile: ProfileTimelineDetails, val timestamp: Long, val content: EventContent, - val debugInfo: TimelineItemDebugInfo, + val debugInfoProvider: EventDebugInfoProvider, val origin: TimelineItemEventOrigin?, - val messageShield: MessageShield?, + val messageShieldProvider: EventShieldsProvider, ) { fun inReplyTo(): InReplyTo? { return (content as? MessageContent)?.inReplyTo @@ -45,3 +45,11 @@ data class EventTimelineItem( return details is InReplyTo.NotLoaded } } + +fun interface EventDebugInfoProvider { + fun get(): TimelineItemDebugInfo +} + +fun interface EventShieldsProvider { + fun getShield(strict: Boolean): MessageShield? +} diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/encryption/RustEncryptionService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/encryption/RustEncryptionService.kt index 1ef0866e8a..f4e4af7b4f 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/encryption/RustEncryptionService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/encryption/RustEncryptionService.kt @@ -114,7 +114,8 @@ internal class RustEncryptionService( override fun onUpdate(status: RustEnableRecoveryProgress) { enableRecoveryProgressStateFlow.value = enableRecoveryProgressMapper.map(status) } - } + }, + passphrase = null, ) // enableRecovery returns the encryption key, but we read it from the state flow .let { } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomContentForwarder.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomContentForwarder.kt index da9afb3605..9c2ca0d74a 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomContentForwarder.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomContentForwarder.kt @@ -18,6 +18,8 @@ import kotlinx.coroutines.CancellationException import kotlinx.coroutines.withTimeout import org.matrix.rustcomponents.sdk.RoomListService import org.matrix.rustcomponents.sdk.Timeline +import org.matrix.rustcomponents.sdk.TimelineItemContent +import org.matrix.rustcomponents.sdk.contentWithoutRelationFromMessage import kotlin.time.Duration.Companion.milliseconds /** @@ -40,11 +42,7 @@ class RoomContentForwarder( toRoomIds: List, timeoutMs: Long = 5000L ) { - val content = fromTimeline - .getEventTimelineItemByEventId(eventId.value) - .content() - .asMessage() - ?.content() + val content = (fromTimeline.getEventTimelineItemByEventId(eventId.value).content as? TimelineItemContent.Message)?.content ?: throw ForwardEventException(toRoomIds) val targetSlidingSyncRooms = toRoomIds.mapNotNull { roomId -> roomListService.roomOrNull(roomId.value) } @@ -58,7 +56,7 @@ class RoomContentForwarder( // Sending a message requires a registered timeline listener targetRoom.timeline().runWithTimelineListenerRegistered { withTimeout(timeoutMs.milliseconds) { - targetRoom.timeline().send(content) + targetRoom.timeline().send(contentWithoutRelationFromMessage(content)) } } } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt index 5f9b00da35..c3521ecf99 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt @@ -462,7 +462,7 @@ class RustMatrixRoom( innerRoom.tryResend(transactionId.value) } - override suspend fun cancelSend(transactionId: TransactionId): Result { + override suspend fun cancelSend(transactionId: TransactionId): Result { return liveTimeline.cancelSend(transactionId) } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustTimeline.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustTimeline.kt index ea6b93b7fa..e0fd82bfcb 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustTimeline.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustTimeline.kt @@ -64,6 +64,7 @@ import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.matrix.rustcomponents.sdk.EditedContent +import org.matrix.rustcomponents.sdk.EventOrTransactionId import org.matrix.rustcomponents.sdk.EventTimelineItem import org.matrix.rustcomponents.sdk.FormattedBody import org.matrix.rustcomponents.sdk.MessageFormat @@ -274,11 +275,14 @@ class RustTimeline( } } - override suspend fun redactEvent(eventId: EventId?, transactionId: TransactionId?, reason: String?): Result = withContext(dispatcher) { + override suspend fun redactEvent(eventId: EventId?, transactionId: TransactionId?, reason: String?): Result = withContext(dispatcher) { runCatching { - getEventTimelineItem(eventId, transactionId).use { item -> - inner.redactEvent(item = item, reason = reason) + val eventOrTransactionId = if (eventId != null) { + EventOrTransactionId.EventId(eventId.value) + } else { + EventOrTransactionId.TransactionId(transactionId!!.value) } + inner.redactEvent(eventOrTransactionId = eventOrTransactionId, reason = reason) } } @@ -291,19 +295,22 @@ class RustTimeline( ): Result = withContext(dispatcher) { runCatching { - getEventTimelineItem(originalEventId, transactionId).use { item -> - val editedContent = EditedContent.RoomMessage( - content = MessageEventContent.from( - body = body, - htmlBody = htmlBody, - intentionalMentions = intentionalMentions - ), - ) - inner.edit( - newContent = editedContent, - item = item, - ) + val eventOrTransactionId = if (originalEventId != null) { + EventOrTransactionId.EventId(originalEventId.value) + } else { + EventOrTransactionId.TransactionId(transactionId!!.value) } + val editedContent = EditedContent.RoomMessage( + content = MessageEventContent.from( + body = body, + htmlBody = htmlBody, + intentionalMentions = intentionalMentions + ), + ) + inner.edit( + newContent = editedContent, + eventOrTransactionId = eventOrTransactionId, + ) } } @@ -343,6 +350,7 @@ class RustTimeline( } @Throws + @Suppress("UnusedPrivateMember") private suspend fun getEventTimelineItem(eventId: EventId?, transactionId: TransactionId?): EventTimelineItem { return try { when { @@ -411,7 +419,8 @@ class RustTimeline( } } - override suspend fun cancelSend(transactionId: TransactionId): Result = redactEvent(eventId = null, transactionId = transactionId, reason = null) + override suspend fun cancelSend(transactionId: TransactionId): Result = + redactEvent(eventId = null, transactionId = transactionId, reason = null) override suspend fun sendLocation( body: String, @@ -455,10 +464,6 @@ class RustTimeline( pollKind: PollKind, ): Result = withContext(dispatcher) { runCatching { - val pollStartEvent = - inner.getEventTimelineItemByEventId( - eventId = pollStartId.value - ) val editedContent = EditedContent.PollStart( pollData = PollData( question = question, @@ -467,12 +472,10 @@ class RustTimeline( pollKind = pollKind.toInner(), ), ) - pollStartEvent.use { - inner.edit( - newContent = editedContent, - item = it, - ) - } + inner.edit( + newContent = editedContent, + eventOrTransactionId = EventOrTransactionId.EventId(pollStartId.value), + ) }.map { } } @@ -482,7 +485,7 @@ class RustTimeline( ): Result = withContext(dispatcher) { runCatching { inner.sendPollResponse( - pollStartId = pollStartId.value, + pollStartEventId = pollStartId.value, answers = answers, ) } @@ -494,7 +497,7 @@ class RustTimeline( ): Result = withContext(dispatcher) { runCatching { inner.endPoll( - pollStartId = pollStartId.value, + pollStartEventId = pollStartId.value, text = text, ) } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/TimelineDiffExt.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/TimelineDiffExt.kt index c2b8957734..5c024698b4 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/TimelineDiffExt.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/TimelineDiffExt.kt @@ -41,5 +41,5 @@ internal fun TimelineDiff.eventOrigin(): EventItemOrigin? { } private fun TimelineItem.eventOrigin(): EventItemOrigin? { - return asEvent()?.origin() + return asEvent()?.origin } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventMessageMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventMessageMapper.kt index a7098d4be1..3e67bd8595 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventMessageMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventMessageMapper.kt @@ -23,10 +23,10 @@ import io.element.android.libraries.matrix.api.timeline.item.event.VideoMessageT import io.element.android.libraries.matrix.api.timeline.item.event.VoiceMessageType import io.element.android.libraries.matrix.impl.media.map import io.element.android.libraries.matrix.impl.timeline.reply.InReplyToMapper -import org.matrix.rustcomponents.sdk.Message import org.matrix.rustcomponents.sdk.MessageType import org.matrix.rustcomponents.sdk.use import org.matrix.rustcomponents.sdk.FormattedBody as RustFormattedBody +import org.matrix.rustcomponents.sdk.MessageContent as Message import org.matrix.rustcomponents.sdk.MessageFormat as RustMessageFormat import org.matrix.rustcomponents.sdk.MessageType as RustMessageType @@ -34,13 +34,13 @@ class EventMessageMapper { private val inReplyToMapper by lazy { InReplyToMapper(TimelineEventContentMapper()) } fun map(message: Message): MessageContent = message.use { - val type = it.msgtype().use(this::mapMessageType) - val inReplyToEvent: InReplyTo? = it.inReplyTo()?.use(inReplyToMapper::map) + val type = it.msgType.use(this::mapMessageType) + val inReplyToEvent: InReplyTo? = it.inReplyTo?.use(inReplyToMapper::map) MessageContent( - body = it.body(), + body = it.body, inReplyTo = inReplyToEvent, - isEdited = it.isEdited(), - isThreaded = it.isThreaded(), + isEdited = it.isEdited, + isThreaded = it.threadRoot != null, type = type ) } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventTimelineItemMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventTimelineItemMapper.kt index 17a33ff93b..9fae19bc1d 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventTimelineItemMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventTimelineItemMapper.kt @@ -12,7 +12,9 @@ import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.TransactionId import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo +import io.element.android.libraries.matrix.api.timeline.item.event.EventDebugInfoProvider import io.element.android.libraries.matrix.api.timeline.item.event.EventReaction +import io.element.android.libraries.matrix.api.timeline.item.event.EventShieldsProvider import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState import io.element.android.libraries.matrix.api.timeline.item.event.MessageShield @@ -23,11 +25,14 @@ import io.element.android.libraries.matrix.api.timeline.item.event.TimelineItemE import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList +import org.matrix.rustcomponents.sdk.EventOrTransactionId import org.matrix.rustcomponents.sdk.EventSendState +import org.matrix.rustcomponents.sdk.EventTimelineItemDebugInfoProvider import org.matrix.rustcomponents.sdk.Reaction import org.matrix.rustcomponents.sdk.ShieldState import uniffi.matrix_sdk_common.ShieldStateCode import org.matrix.rustcomponents.sdk.EventSendState as RustEventSendState +import org.matrix.rustcomponents.sdk.EventShieldsProvider as RustEventShieldsProvider import org.matrix.rustcomponents.sdk.EventTimelineItem as RustEventTimelineItem import org.matrix.rustcomponents.sdk.EventTimelineItemDebugInfo as RustEventTimelineItemDebugInfo import org.matrix.rustcomponents.sdk.ProfileDetails as RustProfileDetails @@ -37,25 +42,25 @@ import uniffi.matrix_sdk_ui.EventItemOrigin as RustEventItemOrigin class EventTimelineItemMapper( private val contentMapper: TimelineEventContentMapper = TimelineEventContentMapper(), ) { - fun map(eventTimelineItem: RustEventTimelineItem): EventTimelineItem = eventTimelineItem.use { + fun map(eventTimelineItem: RustEventTimelineItem): EventTimelineItem = eventTimelineItem.run { EventTimelineItem( - eventId = it.eventId()?.let(::EventId), - transactionId = it.transactionId()?.let(::TransactionId), - isEditable = it.isEditable(), - canBeRepliedTo = it.canBeRepliedTo(), - isLocal = it.isLocal(), - isOwn = it.isOwn(), - isRemote = it.isRemote(), - localSendState = it.localSendState()?.map(), - reactions = it.reactions().map(), - receipts = it.readReceipts().map(), - sender = UserId(it.sender()), - senderProfile = it.senderProfile().map(), - timestamp = it.timestamp().toLong(), - content = contentMapper.map(it.content()), - debugInfo = it.debugInfo().map(), - origin = it.origin()?.map(), - messageShield = it.getShield(false)?.map(), + eventId = eventOrTransactionId.eventId(), + transactionId = eventOrTransactionId.transactionId(), + isEditable = isEditable, + canBeRepliedTo = canBeRepliedTo, + isLocal = isLocal, + isOwn = isOwn, + isRemote = isRemote, + localSendState = localSendState?.map(), + reactions = reactions.map(), + receipts = readReceipts.map(), + sender = UserId(sender), + senderProfile = senderProfile.map(), + timestamp = timestamp.toLong(), + content = contentMapper.map(content), + debugInfoProvider = RustEventDebugInfoProvider(debugInfoProvider), + origin = origin?.map(), + messageShieldProvider = RustEventShieldsProvider(shieldsProvider) ) } } @@ -162,3 +167,23 @@ private fun ShieldState?.map(): MessageShield? { ShieldStateCode.PREVIOUSLY_VERIFIED -> MessageShield.PreviouslyVerified(isCritical) } } + +class RustEventDebugInfoProvider(private val debugInfoProvider: EventTimelineItemDebugInfoProvider) : EventDebugInfoProvider { + override fun get(): TimelineItemDebugInfo { + return debugInfoProvider.get().map() + } +} + +class RustEventShieldsProvider(private val shieldsProvider: RustEventShieldsProvider) : EventShieldsProvider { + override fun getShield(strict: Boolean): MessageShield? { + return shieldsProvider.getShields(strict)?.map() + } +} + +private fun EventOrTransactionId.eventId(): EventId? { + return (this as? EventOrTransactionId.EventId)?.let { EventId(it.eventId) } +} + +private fun EventOrTransactionId.transactionId(): TransactionId? { + return (this as? EventOrTransactionId.TransactionId)?.let { TransactionId(it.transactionId) } +} diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/TimelineEventContentMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/TimelineEventContentMapper.kt index 474d57a7e1..ed075c508f 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/TimelineEventContentMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/TimelineEventContentMapper.kt @@ -29,7 +29,6 @@ import io.element.android.libraries.matrix.impl.poll.map import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableMap import org.matrix.rustcomponents.sdk.TimelineItemContent -import org.matrix.rustcomponents.sdk.TimelineItemContentKind import org.matrix.rustcomponents.sdk.use import uniffi.matrix_sdk_ui.RoomPinnedEventsChange import org.matrix.rustcomponents.sdk.EncryptedMessage as RustEncryptedMessage @@ -42,87 +41,78 @@ class TimelineEventContentMapper( ) { fun map(content: TimelineItemContent): EventContent { return content.use { - content.kind().use { kind -> - map(content, kind) + when (it) { + is TimelineItemContent.FailedToParseMessageLike -> { + FailedToParseMessageLikeContent( + eventType = it.eventType, + error = it.error + ) + } + is TimelineItemContent.FailedToParseState -> { + FailedToParseStateContent( + eventType = it.eventType, + stateKey = it.stateKey, + error = it.error + ) + } + is TimelineItemContent.Message -> { + eventMessageMapper.map(it.content) + } + is TimelineItemContent.ProfileChange -> { + ProfileChangeContent( + displayName = it.displayName, + prevDisplayName = it.prevDisplayName, + avatarUrl = it.avatarUrl, + prevAvatarUrl = it.prevAvatarUrl + ) + } + TimelineItemContent.RedactedMessage -> { + RedactedContent + } + is TimelineItemContent.RoomMembership -> { + RoomMembershipContent( + userId = UserId(it.userId), + userDisplayName = it.userDisplayName, + change = it.change?.map() + ) + } + is TimelineItemContent.State -> { + StateContent( + stateKey = it.stateKey, + content = it.content.map() + ) + } + is TimelineItemContent.Sticker -> { + StickerContent( + body = it.body, + info = it.info.map(), + source = it.source.map(), + ) + } + is TimelineItemContent.Poll -> { + PollContent( + question = it.question, + kind = it.kind.map(), + maxSelections = it.maxSelections, + answers = it.answers.map { answer -> answer.map() }.toImmutableList(), + votes = it.votes.mapValues { vote -> + vote.value.map { userId -> UserId(userId) }.toImmutableList() + }.toImmutableMap(), + endTime = it.endTime, + isEdited = it.hasBeenEdited, + ) + } + is TimelineItemContent.UnableToDecrypt -> { + UnableToDecryptContent( + data = it.msg.map() + ) + } + is TimelineItemContent.CallInvite -> LegacyCallInviteContent + is TimelineItemContent.CallNotify -> CallNotifyContent + else -> UnknownContent } } } - - private fun map(content: TimelineItemContent, kind: TimelineItemContentKind) = when (kind) { - is TimelineItemContentKind.FailedToParseMessageLike -> { - FailedToParseMessageLikeContent( - eventType = kind.eventType, - error = kind.error - ) - } - is TimelineItemContentKind.FailedToParseState -> { - FailedToParseStateContent( - eventType = kind.eventType, - stateKey = kind.stateKey, - error = kind.error - ) - } - TimelineItemContentKind.Message -> { - val message = content.asMessage() - if (message == null) { - UnknownContent - } else { - eventMessageMapper.map(message) - } - } - is TimelineItemContentKind.ProfileChange -> { - ProfileChangeContent( - displayName = kind.displayName, - prevDisplayName = kind.prevDisplayName, - avatarUrl = kind.avatarUrl, - prevAvatarUrl = kind.prevAvatarUrl - ) - } - TimelineItemContentKind.RedactedMessage -> { - RedactedContent - } - is TimelineItemContentKind.RoomMembership -> { - RoomMembershipContent( - userId = UserId(kind.userId), - userDisplayName = kind.userDisplayName, - change = kind.change?.map() - ) - } - is TimelineItemContentKind.State -> { - StateContent( - stateKey = kind.stateKey, - content = kind.content.map() - ) - } - is TimelineItemContentKind.Sticker -> { - StickerContent( - body = kind.body, - info = kind.info.map(), - source = kind.source.map(), - ) - } - is TimelineItemContentKind.Poll -> { - PollContent( - question = kind.question, - kind = kind.kind.map(), - maxSelections = kind.maxSelections, - answers = kind.answers.map { answer -> answer.map() }.toImmutableList(), - votes = kind.votes.mapValues { vote -> - vote.value.map { userId -> UserId(userId) }.toImmutableList() - }.toImmutableMap(), - endTime = kind.endTime, - isEdited = kind.hasBeenEdited, - ) - } - is TimelineItemContentKind.UnableToDecrypt -> { - UnableToDecryptContent( - data = kind.msg.map() - ) - } - is TimelineItemContentKind.CallInvite -> LegacyCallInviteContent - is TimelineItemContentKind.CallNotify -> CallNotifyContent - else -> UnknownContent - } } private fun RustMembershipChange.map(): MembershipChange { diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/reply/InReplyToMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/reply/InReplyToMapper.kt index dd0c553104..352dfce894 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/reply/InReplyToMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/reply/InReplyToMapper.kt @@ -19,8 +19,8 @@ class InReplyToMapper( private val timelineEventContentMapper: TimelineEventContentMapper, ) { fun map(inReplyToDetails: InReplyToDetails): InReplyTo { - val inReplyToId = EventId(inReplyToDetails.eventId) - return when (val event = inReplyToDetails.event) { + val inReplyToId = EventId(inReplyToDetails.eventId()) + return when (val event = inReplyToDetails.event()) { is RepliedToEventDetails.Ready -> { InReplyTo.Ready( eventId = inReplyToId, diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt index 076baf5f2b..1d78e87369 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt @@ -103,7 +103,7 @@ class FakeMatrixRoom( private val updateUserRoleResult: () -> Result = { lambdaError() }, private val toggleReactionResult: (String, UniqueId) -> Result = { _, _ -> lambdaError() }, private val retrySendMessageResult: (TransactionId) -> Result = { lambdaError() }, - private val cancelSendResult: (TransactionId) -> Result = { lambdaError() }, + private val cancelSendResult: (TransactionId) -> Result = { lambdaError() }, private val forwardEventResult: (EventId, List) -> Result = { _, _ -> lambdaError() }, private val reportContentResult: (EventId, String, UserId?) -> Result = { _, _, _ -> lambdaError() }, private val kickUserResult: (UserId, String?) -> Result = { _, _ -> lambdaError() }, @@ -233,7 +233,7 @@ class FakeMatrixRoom( return retrySendMessageResult(transactionId) } - override suspend fun cancelSend(transactionId: TransactionId): Result { + override suspend fun cancelSend(transactionId: TransactionId): Result { return cancelSendResult(transactionId) } diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/FakeTimeline.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/FakeTimeline.kt index f94128bcb7..d2a99df97b 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/FakeTimeline.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/FakeTimeline.kt @@ -63,15 +63,15 @@ class FakeTimeline( intentionalMentions: List, ): Result = sendMessageLambda(body, htmlBody, intentionalMentions) - var redactEventLambda: (eventId: EventId?, transactionId: TransactionId?, reason: String?) -> Result = { _, _, _ -> - Result.success(true) + var redactEventLambda: (eventId: EventId?, transactionId: TransactionId?, reason: String?) -> Result = { _, _, _ -> + Result.success(Unit) } override suspend fun redactEvent( eventId: EventId?, transactionId: TransactionId?, reason: String? - ): Result = redactEventLambda(eventId, transactionId, reason) + ): Result = redactEventLambda(eventId, transactionId, reason) var editMessageLambda: ( originalEventId: EventId?, @@ -217,7 +217,7 @@ class FakeTimeline( var forwardEventLambda: (eventId: EventId, roomIds: List) -> Result = { _, _ -> Result.success(Unit) } override suspend fun forwardEvent(eventId: EventId, roomIds: List): Result = forwardEventLambda(eventId, roomIds) - override suspend fun cancelSend(transactionId: TransactionId): Result = redactEvent(null, transactionId, null) + override suspend fun cancelSend(transactionId: TransactionId): Result = redactEvent(null, transactionId, null) var sendLocationLambda: ( body: String, diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/TimelineFixture.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/TimelineFixture.kt index 17d4eee54c..b8bc798b28 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/TimelineFixture.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/TimelineFixture.kt @@ -66,9 +66,9 @@ fun anEventTimelineItem( senderProfile = senderProfile, timestamp = timestamp, content = content, - debugInfo = debugInfo, + debugInfoProvider = { debugInfo }, origin = null, - messageShield = messageShield, + messageShieldProvider = { messageShield }, ) fun aProfileTimelineDetails( diff --git a/libraries/roomselect/impl/src/test/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectPresenterTest.kt b/libraries/roomselect/impl/src/test/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectPresenterTest.kt index 4d1a3b85a5..ff94e32a5f 100644 --- a/libraries/roomselect/impl/src/test/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectPresenterTest.kt +++ b/libraries/roomselect/impl/src/test/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectPresenterTest.kt @@ -68,7 +68,19 @@ class RoomSelectPresenterTest { presenter.present() }.test { val initialState = awaitItem() - assertThat(awaitItem().resultState as? SearchBarResultState.Results).isEqualTo(SearchBarResultState.Results(listOf(aRoomSummary()))) + val expectedRoomSummary = aRoomSummary() + // Do not compare the lambda because they will be different. So copy the lambda from expectedRoomSummary to result + val result = (awaitItem().resultState as SearchBarResultState.Results).results.map { roomSummary -> + roomSummary.copy( + lastMessage = roomSummary.lastMessage!!.copy( + event = roomSummary.lastMessage!!.event.copy( + debugInfoProvider = expectedRoomSummary.lastMessage!!.event.debugInfoProvider, + messageShieldProvider = expectedRoomSummary.lastMessage!!.event.messageShieldProvider, + ) + ), + ) + } + assertThat(result).isEqualTo(listOf(expectedRoomSummary)) initialState.eventSink(RoomSelectEvents.ToggleSearchActive) skipItems(1) initialState.eventSink(RoomSelectEvents.UpdateQuery("string not contained"))