Update dependency org.matrix.rustcomponents:sdk-android to v0.2.50 (#3565)

* Adapt to changes in the SDK

* Update dependency org.matrix.rustcomponents:sdk-android to v0.2.50

* Use lambda instead of overriding the `EventDebugInfoProvider` interface

* Fix test proposal.
We may find a better way to compare data class instance if we need to do more comparison in the future...

---------

Co-authored-by: Jorge Martín <jorgem@element.io>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Benoit Marty <benoit@matrix.org>
This commit is contained in:
renovate[bot] 2024-10-01 09:44:52 +02:00 committed by GitHub
parent 6b257dcef0
commit bb11bc5494
26 changed files with 216 additions and 175 deletions

View file

@ -162,7 +162,7 @@ internal fun aTimelineItemEvent(
groupPosition = groupPosition, groupPosition = groupPosition,
localSendState = sendState, localSendState = sendState,
inReplyTo = inReplyTo, inReplyTo = inReplyTo,
debugInfo = debugInfo, debugInfoProvider = { debugInfo },
isThreaded = isThreaded, isThreaded = isThreaded,
origin = null, origin = null,
messageShield = messageShield, messageShield = messageShield,

View file

@ -85,9 +85,9 @@ class TimelineItemEventFactory @AssistedInject constructor(
localSendState = currentTimelineItem.event.localSendState, localSendState = currentTimelineItem.event.localSendState,
inReplyTo = currentTimelineItem.event.inReplyTo()?.map(permalinkParser = permalinkParser), inReplyTo = currentTimelineItem.event.inReplyTo()?.map(permalinkParser = permalinkParser),
isThreaded = currentTimelineItem.event.isThreaded(), isThreaded = currentTimelineItem.event.isThreaded(),
debugInfo = currentTimelineItem.event.debugInfo, debugInfoProvider = currentTimelineItem.event.debugInfoProvider,
origin = currentTimelineItem.event.origin, origin = currentTimelineItem.event.origin,
messageShield = currentTimelineItem.event.messageShield, messageShield = currentTimelineItem.event.messageShieldProvider.getShield(strict = false)
) )
} }

View file

@ -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.TransactionId
import io.element.android.libraries.matrix.api.core.UniqueId 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.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.LocalEventSendState
import io.element.android.libraries.matrix.api.timeline.item.event.MessageShield import io.element.android.libraries.matrix.api.timeline.item.event.MessageShield
import io.element.android.libraries.matrix.api.timeline.item.event.ProfileTimelineDetails import io.element.android.libraries.matrix.api.timeline.item.event.ProfileTimelineDetails
@ -74,7 +74,7 @@ sealed interface TimelineItem {
val localSendState: LocalEventSendState?, val localSendState: LocalEventSendState?,
val inReplyTo: InReplyToDetails?, val inReplyTo: InReplyToDetails?,
val isThreaded: Boolean, val isThreaded: Boolean,
val debugInfo: TimelineItemDebugInfo, val debugInfoProvider: EventDebugInfoProvider,
val origin: TimelineItemEventOrigin?, val origin: TimelineItemEventOrigin?,
val messageShield: MessageShield?, val messageShield: MessageShield?,
) : TimelineItem { ) : TimelineItem {
@ -89,6 +89,8 @@ sealed interface TimelineItem {
val isSticker: Boolean = content is TimelineItemStickerContent val isSticker: Boolean = content is TimelineItemStickerContent
val isRemote = eventId != null val isRemote = eventId != null
val debugInfo = debugInfoProvider.get()
} }
@Immutable @Immutable

View file

@ -493,7 +493,7 @@ class MessagesPresenterTest {
canUserPinUnpinResult = { Result.success(true) }, 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 liveTimeline.redactEventLambda = redactEventLambda
val presenter = createMessagesPresenter(matrixRoom = matrixRoom, coroutineDispatchers = coroutineDispatchers) val presenter = createMessagesPresenter(matrixRoom = matrixRoom, coroutineDispatchers = coroutineDispatchers)

View file

@ -58,7 +58,7 @@ internal fun aMessageEvent(
readReceiptState = TimelineItemReadReceipts(emptyList<ReadReceiptData>().toImmutableList()), readReceiptState = TimelineItemReadReceipts(emptyList<ReadReceiptData>().toImmutableList()),
localSendState = sendState, localSendState = sendState,
inReplyTo = inReplyTo, inReplyTo = inReplyTo,
debugInfo = debugInfo, debugInfoProvider = { debugInfo },
isThreaded = isThreaded, isThreaded = isThreaded,
origin = null, origin = null,
messageShield = messageShield, messageShield = messageShield,

View file

@ -41,7 +41,7 @@ class TimelineItemGrouperTest {
canBeRepliedTo = false, canBeRepliedTo = false,
inReplyTo = null, inReplyTo = null,
isThreaded = false, isThreaded = false,
debugInfo = aTimelineItemDebugInfo(), debugInfoProvider = { aTimelineItemDebugInfo() },
origin = null, origin = null,
messageShield = null, messageShield = null,
) )

View file

@ -88,13 +88,15 @@ fun aRedactedMatrixTimeline(eventId: EventId) = listOf<MatrixTimelineItem>(
senderProfile = ProfileTimelineDetails.Unavailable, senderProfile = ProfileTimelineDetails.Unavailable,
timestamp = 9442, timestamp = 9442,
content = RedactedContent, content = RedactedContent,
debugInfo = TimelineItemDebugInfo( debugInfoProvider = {
model = "enim", TimelineItemDebugInfo(
originalJson = null, model = "enim",
latestEditedJson = null originalJson = null,
), latestEditedJson = null
)
},
origin = null, origin = null,
messageShield = null, messageShieldProvider = { null },
), ),
) )
) )

View file

@ -59,7 +59,7 @@ class PollRepository @Inject constructor(
suspend fun deletePoll( suspend fun deletePoll(
pollStartId: EventId, pollStartId: EventId,
): Result<Boolean> = ): Result<Unit> =
timelineProvider timelineProvider
.getActiveTimeline() .getActiveTimeline()
.redactEvent( .redactEvent(

View file

@ -466,7 +466,7 @@ class CreatePollPresenterTest {
@Test @Test
fun `delete confirms`() = runTest { fun `delete confirms`() = runTest {
val presenter = createCreatePollPresenter(mode = CreatePollMode.EditPoll(pollEventId)) 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 timeline.redactEventLambda = redactEventLambda
moleculeFlow(RecompositionMode.Immediate) { moleculeFlow(RecompositionMode.Immediate) {
presenter.present() presenter.present()
@ -481,7 +481,7 @@ class CreatePollPresenterTest {
@Test @Test
fun `delete can be cancelled`() = runTest { fun `delete can be cancelled`() = runTest {
val presenter = createCreatePollPresenter(mode = CreatePollMode.EditPoll(pollEventId)) 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 timeline.redactEventLambda = redactEventLambda
moleculeFlow(RecompositionMode.Immediate) { moleculeFlow(RecompositionMode.Immediate) {
presenter.present() presenter.present()
@ -499,7 +499,7 @@ class CreatePollPresenterTest {
@Test @Test
fun `delete can be confirmed`() = runTest { fun `delete can be confirmed`() = runTest {
val presenter = createCreatePollPresenter(mode = CreatePollMode.EditPoll(pollEventId)) 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 timeline.redactEventLambda = redactEventLambda
moleculeFlow(RecompositionMode.Immediate) { moleculeFlow(RecompositionMode.Immediate) {
presenter.present() presenter.present()

View file

@ -163,7 +163,7 @@ jsoup = "org.jsoup:jsoup:1.18.1"
appyx_core = { module = "com.bumble.appyx:core", version.ref = "appyx" } appyx_core = { module = "com.bumble.appyx:core", version.ref = "appyx" }
molecule-runtime = "app.cash.molecule:molecule-runtime:2.0.0" molecule-runtime = "app.cash.molecule:molecule-runtime:2.0.0"
timber = "com.jakewharton.timber:timber:5.0.1" 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 = { module = "io.element.android:wysiwyg", version.ref = "wysiwyg" }
matrix_richtexteditor_compose = { module = "io.element.android:wysiwyg-compose", 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" } sqldelight-driver-android = { module = "app.cash.sqldelight:android-driver", version.ref = "sqldelight" }

View file

@ -154,7 +154,7 @@ interface MatrixRoom : Closeable {
suspend fun retrySendMessage(transactionId: TransactionId): Result<Unit> suspend fun retrySendMessage(transactionId: TransactionId): Result<Unit>
suspend fun cancelSend(transactionId: TransactionId): Result<Boolean> suspend fun cancelSend(transactionId: TransactionId): Result<Unit>
suspend fun leave(): Result<Unit> suspend fun leave(): Result<Unit>

View file

@ -89,7 +89,7 @@ interface Timeline : AutoCloseable {
progressCallback: ProgressCallback? progressCallback: ProgressCallback?
): Result<MediaUploadHandler> ): Result<MediaUploadHandler>
suspend fun redactEvent(eventId: EventId?, transactionId: TransactionId?, reason: String?): Result<Boolean> suspend fun redactEvent(eventId: EventId?, transactionId: TransactionId?, reason: String?): Result<Unit>
suspend fun sendAudio(file: File, audioInfo: AudioInfo, progressCallback: ProgressCallback?): Result<MediaUploadHandler> suspend fun sendAudio(file: File, audioInfo: AudioInfo, progressCallback: ProgressCallback?): Result<MediaUploadHandler>
@ -99,7 +99,7 @@ interface Timeline : AutoCloseable {
suspend fun forwardEvent(eventId: EventId, roomIds: List<RoomId>): Result<Unit> suspend fun forwardEvent(eventId: EventId, roomIds: List<RoomId>): Result<Unit>
suspend fun cancelSend(transactionId: TransactionId): Result<Boolean> suspend fun cancelSend(transactionId: TransactionId): Result<Unit>
/** /**
* Share a location message in the room. * Share a location message in the room.

View file

@ -28,9 +28,9 @@ data class EventTimelineItem(
val senderProfile: ProfileTimelineDetails, val senderProfile: ProfileTimelineDetails,
val timestamp: Long, val timestamp: Long,
val content: EventContent, val content: EventContent,
val debugInfo: TimelineItemDebugInfo, val debugInfoProvider: EventDebugInfoProvider,
val origin: TimelineItemEventOrigin?, val origin: TimelineItemEventOrigin?,
val messageShield: MessageShield?, val messageShieldProvider: EventShieldsProvider,
) { ) {
fun inReplyTo(): InReplyTo? { fun inReplyTo(): InReplyTo? {
return (content as? MessageContent)?.inReplyTo return (content as? MessageContent)?.inReplyTo
@ -45,3 +45,11 @@ data class EventTimelineItem(
return details is InReplyTo.NotLoaded return details is InReplyTo.NotLoaded
} }
} }
fun interface EventDebugInfoProvider {
fun get(): TimelineItemDebugInfo
}
fun interface EventShieldsProvider {
fun getShield(strict: Boolean): MessageShield?
}

View file

@ -114,7 +114,8 @@ internal class RustEncryptionService(
override fun onUpdate(status: RustEnableRecoveryProgress) { override fun onUpdate(status: RustEnableRecoveryProgress) {
enableRecoveryProgressStateFlow.value = enableRecoveryProgressMapper.map(status) enableRecoveryProgressStateFlow.value = enableRecoveryProgressMapper.map(status)
} }
} },
passphrase = null,
) )
// enableRecovery returns the encryption key, but we read it from the state flow // enableRecovery returns the encryption key, but we read it from the state flow
.let { } .let { }

View file

@ -18,6 +18,8 @@ import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.withTimeout import kotlinx.coroutines.withTimeout
import org.matrix.rustcomponents.sdk.RoomListService import org.matrix.rustcomponents.sdk.RoomListService
import org.matrix.rustcomponents.sdk.Timeline import org.matrix.rustcomponents.sdk.Timeline
import org.matrix.rustcomponents.sdk.TimelineItemContent
import org.matrix.rustcomponents.sdk.contentWithoutRelationFromMessage
import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.milliseconds
/** /**
@ -40,11 +42,7 @@ class RoomContentForwarder(
toRoomIds: List<RoomId>, toRoomIds: List<RoomId>,
timeoutMs: Long = 5000L timeoutMs: Long = 5000L
) { ) {
val content = fromTimeline val content = (fromTimeline.getEventTimelineItemByEventId(eventId.value).content as? TimelineItemContent.Message)?.content
.getEventTimelineItemByEventId(eventId.value)
.content()
.asMessage()
?.content()
?: throw ForwardEventException(toRoomIds) ?: throw ForwardEventException(toRoomIds)
val targetSlidingSyncRooms = toRoomIds.mapNotNull { roomId -> roomListService.roomOrNull(roomId.value) } val targetSlidingSyncRooms = toRoomIds.mapNotNull { roomId -> roomListService.roomOrNull(roomId.value) }
@ -58,7 +56,7 @@ class RoomContentForwarder(
// Sending a message requires a registered timeline listener // Sending a message requires a registered timeline listener
targetRoom.timeline().runWithTimelineListenerRegistered { targetRoom.timeline().runWithTimelineListenerRegistered {
withTimeout(timeoutMs.milliseconds) { withTimeout(timeoutMs.milliseconds) {
targetRoom.timeline().send(content) targetRoom.timeline().send(contentWithoutRelationFromMessage(content))
} }
} }
} }

View file

@ -462,7 +462,7 @@ class RustMatrixRoom(
innerRoom.tryResend(transactionId.value) innerRoom.tryResend(transactionId.value)
} }
override suspend fun cancelSend(transactionId: TransactionId): Result<Boolean> { override suspend fun cancelSend(transactionId: TransactionId): Result<Unit> {
return liveTimeline.cancelSend(transactionId) return liveTimeline.cancelSend(transactionId)
} }

View file

@ -64,6 +64,7 @@ import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.matrix.rustcomponents.sdk.EditedContent import org.matrix.rustcomponents.sdk.EditedContent
import org.matrix.rustcomponents.sdk.EventOrTransactionId
import org.matrix.rustcomponents.sdk.EventTimelineItem import org.matrix.rustcomponents.sdk.EventTimelineItem
import org.matrix.rustcomponents.sdk.FormattedBody import org.matrix.rustcomponents.sdk.FormattedBody
import org.matrix.rustcomponents.sdk.MessageFormat import org.matrix.rustcomponents.sdk.MessageFormat
@ -274,11 +275,14 @@ class RustTimeline(
} }
} }
override suspend fun redactEvent(eventId: EventId?, transactionId: TransactionId?, reason: String?): Result<Boolean> = withContext(dispatcher) { override suspend fun redactEvent(eventId: EventId?, transactionId: TransactionId?, reason: String?): Result<Unit> = withContext(dispatcher) {
runCatching { runCatching {
getEventTimelineItem(eventId, transactionId).use { item -> val eventOrTransactionId = if (eventId != null) {
inner.redactEvent(item = item, reason = reason) EventOrTransactionId.EventId(eventId.value)
} else {
EventOrTransactionId.TransactionId(transactionId!!.value)
} }
inner.redactEvent(eventOrTransactionId = eventOrTransactionId, reason = reason)
} }
} }
@ -291,19 +295,22 @@ class RustTimeline(
): Result<Unit> = ): Result<Unit> =
withContext(dispatcher) { withContext(dispatcher) {
runCatching<Unit> { runCatching<Unit> {
getEventTimelineItem(originalEventId, transactionId).use { item -> val eventOrTransactionId = if (originalEventId != null) {
val editedContent = EditedContent.RoomMessage( EventOrTransactionId.EventId(originalEventId.value)
content = MessageEventContent.from( } else {
body = body, EventOrTransactionId.TransactionId(transactionId!!.value)
htmlBody = htmlBody,
intentionalMentions = intentionalMentions
),
)
inner.edit(
newContent = editedContent,
item = item,
)
} }
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 @Throws
@Suppress("UnusedPrivateMember")
private suspend fun getEventTimelineItem(eventId: EventId?, transactionId: TransactionId?): EventTimelineItem { private suspend fun getEventTimelineItem(eventId: EventId?, transactionId: TransactionId?): EventTimelineItem {
return try { return try {
when { when {
@ -411,7 +419,8 @@ class RustTimeline(
} }
} }
override suspend fun cancelSend(transactionId: TransactionId): Result<Boolean> = redactEvent(eventId = null, transactionId = transactionId, reason = null) override suspend fun cancelSend(transactionId: TransactionId): Result<Unit> =
redactEvent(eventId = null, transactionId = transactionId, reason = null)
override suspend fun sendLocation( override suspend fun sendLocation(
body: String, body: String,
@ -455,10 +464,6 @@ class RustTimeline(
pollKind: PollKind, pollKind: PollKind,
): Result<Unit> = withContext(dispatcher) { ): Result<Unit> = withContext(dispatcher) {
runCatching { runCatching {
val pollStartEvent =
inner.getEventTimelineItemByEventId(
eventId = pollStartId.value
)
val editedContent = EditedContent.PollStart( val editedContent = EditedContent.PollStart(
pollData = PollData( pollData = PollData(
question = question, question = question,
@ -467,12 +472,10 @@ class RustTimeline(
pollKind = pollKind.toInner(), pollKind = pollKind.toInner(),
), ),
) )
pollStartEvent.use { inner.edit(
inner.edit( newContent = editedContent,
newContent = editedContent, eventOrTransactionId = EventOrTransactionId.EventId(pollStartId.value),
item = it, )
)
}
}.map { } }.map { }
} }
@ -482,7 +485,7 @@ class RustTimeline(
): Result<Unit> = withContext(dispatcher) { ): Result<Unit> = withContext(dispatcher) {
runCatching { runCatching {
inner.sendPollResponse( inner.sendPollResponse(
pollStartId = pollStartId.value, pollStartEventId = pollStartId.value,
answers = answers, answers = answers,
) )
} }
@ -494,7 +497,7 @@ class RustTimeline(
): Result<Unit> = withContext(dispatcher) { ): Result<Unit> = withContext(dispatcher) {
runCatching { runCatching {
inner.endPoll( inner.endPoll(
pollStartId = pollStartId.value, pollStartEventId = pollStartId.value,
text = text, text = text,
) )
} }

View file

@ -41,5 +41,5 @@ internal fun TimelineDiff.eventOrigin(): EventItemOrigin? {
} }
private fun TimelineItem.eventOrigin(): EventItemOrigin? { private fun TimelineItem.eventOrigin(): EventItemOrigin? {
return asEvent()?.origin() return asEvent()?.origin
} }

View file

@ -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.api.timeline.item.event.VoiceMessageType
import io.element.android.libraries.matrix.impl.media.map import io.element.android.libraries.matrix.impl.media.map
import io.element.android.libraries.matrix.impl.timeline.reply.InReplyToMapper 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.MessageType
import org.matrix.rustcomponents.sdk.use import org.matrix.rustcomponents.sdk.use
import org.matrix.rustcomponents.sdk.FormattedBody as RustFormattedBody 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.MessageFormat as RustMessageFormat
import org.matrix.rustcomponents.sdk.MessageType as RustMessageType import org.matrix.rustcomponents.sdk.MessageType as RustMessageType
@ -34,13 +34,13 @@ class EventMessageMapper {
private val inReplyToMapper by lazy { InReplyToMapper(TimelineEventContentMapper()) } private val inReplyToMapper by lazy { InReplyToMapper(TimelineEventContentMapper()) }
fun map(message: Message): MessageContent = message.use { fun map(message: Message): MessageContent = message.use {
val type = it.msgtype().use(this::mapMessageType) val type = it.msgType.use(this::mapMessageType)
val inReplyToEvent: InReplyTo? = it.inReplyTo()?.use(inReplyToMapper::map) val inReplyToEvent: InReplyTo? = it.inReplyTo?.use(inReplyToMapper::map)
MessageContent( MessageContent(
body = it.body(), body = it.body,
inReplyTo = inReplyToEvent, inReplyTo = inReplyToEvent,
isEdited = it.isEdited(), isEdited = it.isEdited,
isThreaded = it.isThreaded(), isThreaded = it.threadRoot != null,
type = type type = type
) )
} }

View file

@ -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.TransactionId
import io.element.android.libraries.matrix.api.core.UserId 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.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.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.EventTimelineItem
import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState 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.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.ImmutableList
import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableList
import org.matrix.rustcomponents.sdk.EventOrTransactionId
import org.matrix.rustcomponents.sdk.EventSendState import org.matrix.rustcomponents.sdk.EventSendState
import org.matrix.rustcomponents.sdk.EventTimelineItemDebugInfoProvider
import org.matrix.rustcomponents.sdk.Reaction import org.matrix.rustcomponents.sdk.Reaction
import org.matrix.rustcomponents.sdk.ShieldState import org.matrix.rustcomponents.sdk.ShieldState
import uniffi.matrix_sdk_common.ShieldStateCode import uniffi.matrix_sdk_common.ShieldStateCode
import org.matrix.rustcomponents.sdk.EventSendState as RustEventSendState 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.EventTimelineItem as RustEventTimelineItem
import org.matrix.rustcomponents.sdk.EventTimelineItemDebugInfo as RustEventTimelineItemDebugInfo import org.matrix.rustcomponents.sdk.EventTimelineItemDebugInfo as RustEventTimelineItemDebugInfo
import org.matrix.rustcomponents.sdk.ProfileDetails as RustProfileDetails import org.matrix.rustcomponents.sdk.ProfileDetails as RustProfileDetails
@ -37,25 +42,25 @@ import uniffi.matrix_sdk_ui.EventItemOrigin as RustEventItemOrigin
class EventTimelineItemMapper( class EventTimelineItemMapper(
private val contentMapper: TimelineEventContentMapper = TimelineEventContentMapper(), private val contentMapper: TimelineEventContentMapper = TimelineEventContentMapper(),
) { ) {
fun map(eventTimelineItem: RustEventTimelineItem): EventTimelineItem = eventTimelineItem.use { fun map(eventTimelineItem: RustEventTimelineItem): EventTimelineItem = eventTimelineItem.run {
EventTimelineItem( EventTimelineItem(
eventId = it.eventId()?.let(::EventId), eventId = eventOrTransactionId.eventId(),
transactionId = it.transactionId()?.let(::TransactionId), transactionId = eventOrTransactionId.transactionId(),
isEditable = it.isEditable(), isEditable = isEditable,
canBeRepliedTo = it.canBeRepliedTo(), canBeRepliedTo = canBeRepliedTo,
isLocal = it.isLocal(), isLocal = isLocal,
isOwn = it.isOwn(), isOwn = isOwn,
isRemote = it.isRemote(), isRemote = isRemote,
localSendState = it.localSendState()?.map(), localSendState = localSendState?.map(),
reactions = it.reactions().map(), reactions = reactions.map(),
receipts = it.readReceipts().map(), receipts = readReceipts.map(),
sender = UserId(it.sender()), sender = UserId(sender),
senderProfile = it.senderProfile().map(), senderProfile = senderProfile.map(),
timestamp = it.timestamp().toLong(), timestamp = timestamp.toLong(),
content = contentMapper.map(it.content()), content = contentMapper.map(content),
debugInfo = it.debugInfo().map(), debugInfoProvider = RustEventDebugInfoProvider(debugInfoProvider),
origin = it.origin()?.map(), origin = origin?.map(),
messageShield = it.getShield(false)?.map(), messageShieldProvider = RustEventShieldsProvider(shieldsProvider)
) )
} }
} }
@ -162,3 +167,23 @@ private fun ShieldState?.map(): MessageShield? {
ShieldStateCode.PREVIOUSLY_VERIFIED -> MessageShield.PreviouslyVerified(isCritical) 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) }
}

View file

@ -29,7 +29,6 @@ import io.element.android.libraries.matrix.impl.poll.map
import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableList
import kotlinx.collections.immutable.toImmutableMap import kotlinx.collections.immutable.toImmutableMap
import org.matrix.rustcomponents.sdk.TimelineItemContent import org.matrix.rustcomponents.sdk.TimelineItemContent
import org.matrix.rustcomponents.sdk.TimelineItemContentKind
import org.matrix.rustcomponents.sdk.use import org.matrix.rustcomponents.sdk.use
import uniffi.matrix_sdk_ui.RoomPinnedEventsChange import uniffi.matrix_sdk_ui.RoomPinnedEventsChange
import org.matrix.rustcomponents.sdk.EncryptedMessage as RustEncryptedMessage import org.matrix.rustcomponents.sdk.EncryptedMessage as RustEncryptedMessage
@ -42,87 +41,78 @@ class TimelineEventContentMapper(
) { ) {
fun map(content: TimelineItemContent): EventContent { fun map(content: TimelineItemContent): EventContent {
return content.use { return content.use {
content.kind().use { kind -> when (it) {
map(content, kind) 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 { private fun RustMembershipChange.map(): MembershipChange {

View file

@ -19,8 +19,8 @@ class InReplyToMapper(
private val timelineEventContentMapper: TimelineEventContentMapper, private val timelineEventContentMapper: TimelineEventContentMapper,
) { ) {
fun map(inReplyToDetails: InReplyToDetails): InReplyTo { fun map(inReplyToDetails: InReplyToDetails): InReplyTo {
val inReplyToId = EventId(inReplyToDetails.eventId) val inReplyToId = EventId(inReplyToDetails.eventId())
return when (val event = inReplyToDetails.event) { return when (val event = inReplyToDetails.event()) {
is RepliedToEventDetails.Ready -> { is RepliedToEventDetails.Ready -> {
InReplyTo.Ready( InReplyTo.Ready(
eventId = inReplyToId, eventId = inReplyToId,

View file

@ -103,7 +103,7 @@ class FakeMatrixRoom(
private val updateUserRoleResult: () -> Result<Unit> = { lambdaError() }, private val updateUserRoleResult: () -> Result<Unit> = { lambdaError() },
private val toggleReactionResult: (String, UniqueId) -> Result<Unit> = { _, _ -> lambdaError() }, private val toggleReactionResult: (String, UniqueId) -> Result<Unit> = { _, _ -> lambdaError() },
private val retrySendMessageResult: (TransactionId) -> Result<Unit> = { lambdaError() }, private val retrySendMessageResult: (TransactionId) -> Result<Unit> = { lambdaError() },
private val cancelSendResult: (TransactionId) -> Result<Boolean> = { lambdaError() }, private val cancelSendResult: (TransactionId) -> Result<Unit> = { lambdaError() },
private val forwardEventResult: (EventId, List<RoomId>) -> Result<Unit> = { _, _ -> lambdaError() }, private val forwardEventResult: (EventId, List<RoomId>) -> Result<Unit> = { _, _ -> lambdaError() },
private val reportContentResult: (EventId, String, UserId?) -> Result<Unit> = { _, _, _ -> lambdaError() }, private val reportContentResult: (EventId, String, UserId?) -> Result<Unit> = { _, _, _ -> lambdaError() },
private val kickUserResult: (UserId, String?) -> Result<Unit> = { _, _ -> lambdaError() }, private val kickUserResult: (UserId, String?) -> Result<Unit> = { _, _ -> lambdaError() },
@ -233,7 +233,7 @@ class FakeMatrixRoom(
return retrySendMessageResult(transactionId) return retrySendMessageResult(transactionId)
} }
override suspend fun cancelSend(transactionId: TransactionId): Result<Boolean> { override suspend fun cancelSend(transactionId: TransactionId): Result<Unit> {
return cancelSendResult(transactionId) return cancelSendResult(transactionId)
} }

View file

@ -63,15 +63,15 @@ class FakeTimeline(
intentionalMentions: List<IntentionalMention>, intentionalMentions: List<IntentionalMention>,
): Result<Unit> = sendMessageLambda(body, htmlBody, intentionalMentions) ): Result<Unit> = sendMessageLambda(body, htmlBody, intentionalMentions)
var redactEventLambda: (eventId: EventId?, transactionId: TransactionId?, reason: String?) -> Result<Boolean> = { _, _, _ -> var redactEventLambda: (eventId: EventId?, transactionId: TransactionId?, reason: String?) -> Result<Unit> = { _, _, _ ->
Result.success(true) Result.success(Unit)
} }
override suspend fun redactEvent( override suspend fun redactEvent(
eventId: EventId?, eventId: EventId?,
transactionId: TransactionId?, transactionId: TransactionId?,
reason: String? reason: String?
): Result<Boolean> = redactEventLambda(eventId, transactionId, reason) ): Result<Unit> = redactEventLambda(eventId, transactionId, reason)
var editMessageLambda: ( var editMessageLambda: (
originalEventId: EventId?, originalEventId: EventId?,
@ -217,7 +217,7 @@ class FakeTimeline(
var forwardEventLambda: (eventId: EventId, roomIds: List<RoomId>) -> Result<Unit> = { _, _ -> Result.success(Unit) } var forwardEventLambda: (eventId: EventId, roomIds: List<RoomId>) -> Result<Unit> = { _, _ -> Result.success(Unit) }
override suspend fun forwardEvent(eventId: EventId, roomIds: List<RoomId>): Result<Unit> = forwardEventLambda(eventId, roomIds) override suspend fun forwardEvent(eventId: EventId, roomIds: List<RoomId>): Result<Unit> = forwardEventLambda(eventId, roomIds)
override suspend fun cancelSend(transactionId: TransactionId): Result<Boolean> = redactEvent(null, transactionId, null) override suspend fun cancelSend(transactionId: TransactionId): Result<Unit> = redactEvent(null, transactionId, null)
var sendLocationLambda: ( var sendLocationLambda: (
body: String, body: String,

View file

@ -66,9 +66,9 @@ fun anEventTimelineItem(
senderProfile = senderProfile, senderProfile = senderProfile,
timestamp = timestamp, timestamp = timestamp,
content = content, content = content,
debugInfo = debugInfo, debugInfoProvider = { debugInfo },
origin = null, origin = null,
messageShield = messageShield, messageShieldProvider = { messageShield },
) )
fun aProfileTimelineDetails( fun aProfileTimelineDetails(

View file

@ -68,7 +68,19 @@ class RoomSelectPresenterTest {
presenter.present() presenter.present()
}.test { }.test {
val initialState = awaitItem() 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) initialState.eventSink(RoomSelectEvents.ToggleSearchActive)
skipItems(1) skipItems(1)
initialState.eventSink(RoomSelectEvents.UpdateQuery("string not contained")) initialState.eventSink(RoomSelectEvents.UpdateQuery("string not contained"))