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 ee745ea59d..8e022828d2 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 @@ -28,6 +28,7 @@ import io.element.android.libraries.matrix.api.media.VideoInfo import io.element.android.libraries.matrix.api.poll.PollKind import io.element.android.libraries.matrix.api.room.Mention import io.element.android.libraries.matrix.api.room.location.AssetType +import io.element.android.libraries.matrix.api.timeline.item.event.InReplyTo import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow import java.io.File @@ -168,4 +169,6 @@ interface Timeline : AutoCloseable { waveform: List, progressCallback: ProgressCallback? ): Result + + suspend fun loadReplyDetails(eventId: EventId): Result } 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 75b59e8c65..c19e4b7b93 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 @@ -33,6 +33,7 @@ import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem import io.element.android.libraries.matrix.api.timeline.ReceiptType import io.element.android.libraries.matrix.api.timeline.Timeline import io.element.android.libraries.matrix.api.timeline.TimelineException +import io.element.android.libraries.matrix.api.timeline.item.event.InReplyTo import io.element.android.libraries.matrix.impl.core.toProgressWatcher import io.element.android.libraries.matrix.impl.media.MediaUploadHandlerImpl import io.element.android.libraries.matrix.impl.media.map @@ -41,7 +42,6 @@ import io.element.android.libraries.matrix.impl.poll.toInner import io.element.android.libraries.matrix.impl.room.RoomContentForwarder import io.element.android.libraries.matrix.impl.room.location.toInner import io.element.android.libraries.matrix.impl.room.map -import io.element.android.libraries.matrix.impl.timeline.item.event.EventMessageMapper import io.element.android.libraries.matrix.impl.timeline.item.event.EventTimelineItemMapper import io.element.android.libraries.matrix.impl.timeline.item.event.TimelineEventContentMapper import io.element.android.libraries.matrix.impl.timeline.item.virtual.VirtualTimelineItemMapper @@ -49,6 +49,7 @@ import io.element.android.libraries.matrix.impl.timeline.postprocessor.LastForwa import io.element.android.libraries.matrix.impl.timeline.postprocessor.LoadingIndicatorsPostProcessor import io.element.android.libraries.matrix.impl.timeline.postprocessor.RoomBeginningPostProcessor import io.element.android.libraries.matrix.impl.timeline.postprocessor.TimelineEncryptedHistoryPostProcessor +import io.element.android.libraries.matrix.impl.timeline.reply.InReplyToMapper import io.element.android.services.toolbox.api.systemclock.SystemClock import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CoroutineDispatcher @@ -118,14 +119,14 @@ class RustTimeline( private val loadingIndicatorsPostProcessor = LoadingIndicatorsPostProcessor(systemClock) private val lastForwardIndicatorsPostProcessor = LastForwardIndicatorsPostProcessor(isLive) + private val timelineEventContentMapper = TimelineEventContentMapper() + private val inReplyToMapper = InReplyToMapper(timelineEventContentMapper) private val timelineItemFactory = MatrixTimelineItemMapper( fetchDetailsForEvent = this::fetchDetailsForEvent, roomCoroutineScope = roomCoroutineScope, virtualTimelineItemMapper = VirtualTimelineItemMapper(), eventTimelineItemMapper = EventTimelineItemMapper( - contentMapper = TimelineEventContentMapper( - eventMessageMapper = EventMessageMapper() - ) + contentMapper = timelineEventContentMapper ) ) @@ -580,4 +581,10 @@ class RustTimeline( inner.fetchDetailsForEvent(eventId.value) } } + + override suspend fun loadReplyDetails(eventId: EventId): Result { + return runCatching { + inner.loadReplyDetails(eventId.value).use(inReplyToMapper::map) + } + } } 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 09a66fa656..70f1f45a85 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 @@ -16,8 +16,6 @@ package io.element.android.libraries.matrix.impl.timeline.item.event -import io.element.android.libraries.matrix.api.core.EventId -import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.timeline.item.event.AudioMessageType import io.element.android.libraries.matrix.api.timeline.item.event.EmoteMessageType import io.element.android.libraries.matrix.api.timeline.item.event.FileMessageType @@ -33,42 +31,20 @@ import io.element.android.libraries.matrix.api.timeline.item.event.TextMessageTy import io.element.android.libraries.matrix.api.timeline.item.event.VideoMessageType 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.RepliedToEventDetails import org.matrix.rustcomponents.sdk.use import org.matrix.rustcomponents.sdk.FormattedBody as RustFormattedBody import org.matrix.rustcomponents.sdk.MessageFormat as RustMessageFormat import org.matrix.rustcomponents.sdk.MessageType as RustMessageType class EventMessageMapper { - private val timelineEventContentMapper by lazy { TimelineEventContentMapper() } + 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 { details -> - val inReplyToId = EventId(details.eventId) - when (val event = details.event) { - is RepliedToEventDetails.Ready -> { - InReplyTo.Ready( - eventId = inReplyToId, - content = timelineEventContentMapper.map(event.content), - senderId = UserId(event.sender), - senderProfile = event.senderProfile.map(), - ) - } - is RepliedToEventDetails.Error -> InReplyTo.Error( - eventId = inReplyToId, - message = event.message, - ) - RepliedToEventDetails.Pending -> InReplyTo.Pending( - eventId = inReplyToId, - ) - is RepliedToEventDetails.Unavailable -> InReplyTo.NotLoaded( - eventId = inReplyToId - ) - } - } + val inReplyToEvent: InReplyTo? = it.inReplyTo()?.use(inReplyToMapper::map) MessageContent( body = it.body(), inReplyTo = inReplyToEvent, 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 new file mode 100644 index 0000000000..57eebddfb1 --- /dev/null +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/reply/InReplyToMapper.kt @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.matrix.impl.timeline.reply + +import io.element.android.libraries.matrix.api.core.EventId +import io.element.android.libraries.matrix.api.core.UserId +import io.element.android.libraries.matrix.api.timeline.item.event.InReplyTo +import io.element.android.libraries.matrix.impl.timeline.item.event.TimelineEventContentMapper +import io.element.android.libraries.matrix.impl.timeline.item.event.map +import org.matrix.rustcomponents.sdk.InReplyToDetails +import org.matrix.rustcomponents.sdk.RepliedToEventDetails + +class InReplyToMapper( + private val timelineEventContentMapper: TimelineEventContentMapper, +) { + + fun map(inReplyToDetails: InReplyToDetails): InReplyTo { + val inReplyToId = EventId(inReplyToDetails.eventId) + return when (val event = inReplyToDetails.event) { + is RepliedToEventDetails.Ready -> { + InReplyTo.Ready( + eventId = inReplyToId, + content = timelineEventContentMapper.map(event.content), + senderId = UserId(event.sender), + senderProfile = event.senderProfile.map(), + ) + } + is RepliedToEventDetails.Error -> InReplyTo.Error( + eventId = inReplyToId, + message = event.message, + ) + RepliedToEventDetails.Pending -> InReplyTo.Pending( + eventId = inReplyToId, + ) + is RepliedToEventDetails.Unavailable -> InReplyTo.NotLoaded( + eventId = inReplyToId + ) + } + } +} 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 9f62df2ca6..d1da0581da 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 @@ -31,6 +31,7 @@ import io.element.android.libraries.matrix.api.room.location.AssetType import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem import io.element.android.libraries.matrix.api.timeline.ReceiptType import io.element.android.libraries.matrix.api.timeline.Timeline +import io.element.android.libraries.matrix.api.timeline.item.event.InReplyTo import io.element.android.libraries.matrix.test.media.FakeMediaUploadHandler import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow @@ -370,6 +371,12 @@ class FakeTimeline( } } + var loadReplyDetailsLambda: (eventId: EventId) -> Result = { + Result.success(InReplyTo.NotLoaded(it)) + } + + override suspend fun loadReplyDetails(eventId: EventId) = loadReplyDetailsLambda(eventId) + var closeCounter = 0 private set