Merge pull request #7112 from element-hq/feature/bma/galleryMessages

Support gallery messages
This commit is contained in:
Benoit Marty
2026-07-03 14:09:20 +02:00
committed by GitHub
175 changed files with 4915 additions and 590 deletions
@@ -0,0 +1,49 @@
/*
* Copyright (c) 2026 Element Creations Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.matrix.impl.media
import io.element.android.libraries.matrix.api.media.GalleryItemInfo
import org.matrix.rustcomponents.sdk.GalleryItemInfo as RustGalleryItemInfo
import org.matrix.rustcomponents.sdk.UploadSource as RustUploadSource
fun GalleryItemInfo.map(): RustGalleryItemInfo = when (this) {
is GalleryItemInfo.Image -> {
RustGalleryItemInfo.Image(
imageInfo = imageInfo.map(),
source = RustUploadSource.File(file.path),
caption = null,
formattedCaption = null,
thumbnailSource = thumbnailFile?.path?.let(RustUploadSource::File),
)
}
is GalleryItemInfo.Video -> {
RustGalleryItemInfo.Video(
videoInfo = videoInfo.map(),
source = RustUploadSource.File(file.path),
caption = null,
formattedCaption = null,
thumbnailSource = thumbnailFile?.path?.let(RustUploadSource::File),
)
}
is GalleryItemInfo.Audio -> {
RustGalleryItemInfo.Audio(
audioInfo = audioInfo.map(),
source = RustUploadSource.File(file.path),
caption = null,
formattedCaption = null,
)
}
is GalleryItemInfo.MediaFile -> {
RustGalleryItemInfo.File(
fileInfo = fileInfo.map(),
source = RustUploadSource.File(file.path),
caption = null,
formattedCaption = null,
)
}
}
@@ -0,0 +1,34 @@
/*
* Copyright (c) 2026 Element Creations Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.matrix.impl.media
import io.element.android.libraries.androidutils.file.safeDelete
import io.element.android.libraries.core.extensions.runCatchingExceptions
import io.element.android.libraries.matrix.api.media.MediaUploadHandler
import org.matrix.rustcomponents.sdk.SendGalleryJoinHandle
import java.io.File
class GalleryMediaUploadHandlerImpl(
private val filesToUpload: List<File>,
private val sendGalleryJoinHandle: SendGalleryJoinHandle,
) : MediaUploadHandler {
override suspend fun await(): Result<Unit> =
runCatchingExceptions {
sendGalleryJoinHandle.join()
}
.also { cleanUpFiles() }
override fun cancel() {
sendGalleryJoinHandle.cancel()
cleanUpFiles()
}
private fun cleanUpFiles() {
filesToUpload.forEach { file -> file.safeDelete() }
}
}
@@ -97,7 +97,10 @@ private fun MessageLikeEventContent.toContent(senderId: UserId): NotificationCon
is MessageLikeEventContent.ReactionContent -> NotificationContent.MessageLike.ReactionContent(relatedEventId)
MessageLikeEventContent.RoomEncrypted -> NotificationContent.MessageLike.RoomEncrypted
is MessageLikeEventContent.RoomMessage -> {
NotificationContent.MessageLike.RoomMessage(senderId, EventMessageMapper().mapMessageType(messageType))
NotificationContent.MessageLike.RoomMessage(
senderId,
EventMessageMapper().mapMessageType(messageType)
)
}
is MessageLikeEventContent.RoomRedaction -> NotificationContent.MessageLike.RoomRedaction(
redactedEventId = redactedEventId?.let(::EventId),
@@ -219,6 +219,7 @@ class JoinedRustRoom(
RoomMessageEventMessageType.IMAGE,
RoomMessageEventMessageType.VIDEO,
RoomMessageEventMessageType.AUDIO,
RoomMessageEventMessageType.GALLERY,
)
)
is CreateTimelineParams.Focused,
@@ -15,6 +15,7 @@ import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.media.AudioInfo
import io.element.android.libraries.matrix.api.media.FileInfo
import io.element.android.libraries.matrix.api.media.GalleryItemInfo
import io.element.android.libraries.matrix.api.media.ImageInfo
import io.element.android.libraries.matrix.api.media.MediaUploadHandler
import io.element.android.libraries.matrix.api.media.VideoInfo
@@ -30,6 +31,7 @@ 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.EventOrTransactionId
import io.element.android.libraries.matrix.api.timeline.item.event.InReplyTo
import io.element.android.libraries.matrix.impl.media.GalleryMediaUploadHandlerImpl
import io.element.android.libraries.matrix.impl.media.MediaUploadHandlerImpl
import io.element.android.libraries.matrix.impl.media.map
import io.element.android.libraries.matrix.impl.poll.toInner
@@ -67,6 +69,7 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.matrix.rustcomponents.sdk.EditedContent
import org.matrix.rustcomponents.sdk.FormattedBody
import org.matrix.rustcomponents.sdk.GalleryUploadParameters
import org.matrix.rustcomponents.sdk.MessageFormat
import org.matrix.rustcomponents.sdk.PollData
import org.matrix.rustcomponents.sdk.SendAttachmentJoinHandle
@@ -535,6 +538,42 @@ class RustTimeline(
}
}
override suspend fun sendGallery(
items: List<GalleryItemInfo>,
caption: String?,
formattedCaption: String?,
inReplyToEventId: EventId?,
): Result<MediaUploadHandler> {
Timber.d("Sending gallery with ${items.size} items")
val allFiles = items.flatMap { item ->
when (item) {
is GalleryItemInfo.Image -> listOfNotNull(item.file, item.thumbnailFile)
is GalleryItemInfo.Video -> listOfNotNull(item.file, item.thumbnailFile)
is GalleryItemInfo.Audio -> listOf(item.file)
is GalleryItemInfo.MediaFile -> listOf(item.file)
}
}
return sendGalleryAttachment(allFiles) {
inner.sendGallery(
params = GalleryUploadParameters(
caption = caption,
formattedCaption = formattedCaption?.let {
FormattedBody(body = it, format = MessageFormat.Html)
},
mentions = null,
inReplyTo = inReplyToEventId?.value,
),
itemInfos = items.map { it.map() },
)
}
}
private fun sendGalleryAttachment(files: List<File>, handle: () -> org.matrix.rustcomponents.sdk.SendGalleryJoinHandle): Result<MediaUploadHandler> {
return runCatchingExceptions {
GalleryMediaUploadHandlerImpl(files, handle())
}
}
override suspend fun createPoll(
question: String,
answers: List<String>,
@@ -13,6 +13,8 @@ import io.element.android.libraries.matrix.api.timeline.item.event.AudioMessageT
import io.element.android.libraries.matrix.api.timeline.item.event.EmoteMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.FileMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.FormattedBody
import io.element.android.libraries.matrix.api.timeline.item.event.GalleryItemType
import io.element.android.libraries.matrix.api.timeline.item.event.GalleryMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.ImageMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.InReplyTo
import io.element.android.libraries.matrix.api.timeline.item.event.LocationMessageType
@@ -31,12 +33,10 @@ import org.matrix.rustcomponents.sdk.MessageType
import org.matrix.rustcomponents.sdk.MsgLikeKind
import org.matrix.rustcomponents.sdk.use
import org.matrix.rustcomponents.sdk.FormattedBody as RustFormattedBody
import org.matrix.rustcomponents.sdk.GalleryItemType as RustGalleryItemType
import org.matrix.rustcomponents.sdk.MessageFormat as RustMessageFormat
import org.matrix.rustcomponents.sdk.MessageType as RustMessageType
// https://github.com/Johennes/matrix-spec-proposals/blob/johannes/msgtype-galleries/proposals/4274-inline-media-galleries.md#unstable-prefix
private const val MSG_TYPE_GALLERY_UNSTABLE = "dm.filament.gallery"
class EventMessageMapper {
private val inReplyToMapper by lazy { InReplyToMapper(TimelineEventContentMapper()) }
@@ -124,8 +124,64 @@ class EventMessageMapper {
OtherMessageType(type.msgtype, type.body)
}
is MessageType.Gallery -> {
// TODO expose the GalleryType.
OtherMessageType(MSG_TYPE_GALLERY_UNSTABLE, type.content.body)
GalleryMessageType(
body = type.content.body,
formatted = type.content.formatted?.map(),
items = type.content.itemtypes.map { mapGalleryItemType(it) },
)
}
}
private fun mapGalleryItemType(type: RustGalleryItemType): GalleryItemType = when (type) {
is RustGalleryItemType.Image -> {
GalleryItemType.Image(
content = ImageMessageType(
filename = type.content.filename,
caption = type.content.caption,
formattedCaption = type.content.formattedCaption?.map(),
source = type.content.source.map(),
info = type.content.info?.map(),
)
)
}
is RustGalleryItemType.Audio -> {
GalleryItemType.Audio(
content = AudioMessageType(
filename = type.content.filename,
caption = type.content.caption,
formattedCaption = type.content.formattedCaption?.map(),
source = type.content.source.map(),
info = type.content.info?.map(),
)
)
}
is RustGalleryItemType.Video -> {
GalleryItemType.Video(
content = VideoMessageType(
filename = type.content.filename,
caption = type.content.caption,
formattedCaption = type.content.formattedCaption?.map(),
source = type.content.source.map(),
info = type.content.info?.map(),
)
)
}
is RustGalleryItemType.File -> {
GalleryItemType.File(
content = FileMessageType(
filename = type.content.filename,
caption = type.content.caption,
formattedCaption = type.content.formattedCaption?.map(),
source = type.content.source.map(),
info = type.content.info?.map(),
)
)
}
is RustGalleryItemType.Other -> {
GalleryItemType.Other(
itemType = type.itemtype,
body = type.body,
)
}
}
}
@@ -8,12 +8,26 @@
package io.element.android.libraries.matrix.impl.fixtures.factories
import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeFfiMediaSource
import io.element.android.libraries.matrix.test.A_MESSAGE
import org.matrix.rustcomponents.sdk.AudioInfo
import org.matrix.rustcomponents.sdk.AudioMessageContent
import org.matrix.rustcomponents.sdk.FileInfo
import org.matrix.rustcomponents.sdk.FileMessageContent
import org.matrix.rustcomponents.sdk.FormattedBody
import org.matrix.rustcomponents.sdk.GalleryItemType
import org.matrix.rustcomponents.sdk.GalleryMessageContent
import org.matrix.rustcomponents.sdk.ImageInfo
import org.matrix.rustcomponents.sdk.ImageMessageContent
import org.matrix.rustcomponents.sdk.MediaSource
import org.matrix.rustcomponents.sdk.MessageLikeEventContent
import org.matrix.rustcomponents.sdk.MessageType
import org.matrix.rustcomponents.sdk.TextMessageContent
import org.matrix.rustcomponents.sdk.TimelineEventContent
import org.matrix.rustcomponents.sdk.UnstableAudioDetailsContent
import org.matrix.rustcomponents.sdk.UnstableVoiceContent
import org.matrix.rustcomponents.sdk.VideoInfo
import org.matrix.rustcomponents.sdk.VideoMessageContent
internal fun aRustTimelineEventContentMessageLike(
content: MessageLikeEventContent = aRustMessageLikeEventContentRoomMessage(),
@@ -42,3 +56,94 @@ internal fun aRustTextMessageContent(
body = body,
formatted = formatted,
)
internal fun aRustMessageTypeGallery(
content: GalleryMessageContent = aRustGalleryMessageContent(),
) = MessageType.Gallery(content = content)
internal fun aRustGalleryMessageContent(
body: String = "A gallery",
formatted: FormattedBody? = null,
itemTypes: List<GalleryItemType> = listOf(aRustGalleryItemTypeImage()),
) = GalleryMessageContent(body = body, formatted = formatted, itemtypes = itemTypes)
internal fun aRustGalleryItemTypeImage(
content: ImageMessageContent = aRustImageMessageContent(),
) = GalleryItemType.Image(content = content)
internal fun aRustGalleryItemTypeAudio(
content: AudioMessageContent = aRustAudioMessageContent(),
) = GalleryItemType.Audio(content = content)
internal fun aRustGalleryItemTypeVideo(
content: VideoMessageContent = aRustVideoMessageContent(),
) = GalleryItemType.Video(content = content)
internal fun aRustGalleryItemTypeFile(
content: FileMessageContent = aRustFileMessageContent(),
) = GalleryItemType.File(content = content)
internal fun aRustGalleryItemTypeOther(
itemType: String = "m.unknown",
body: String = "unknown item",
) = GalleryItemType.Other(itemtype = itemType, body = body)
internal fun aRustImageMessageContent(
filename: String = "image.jpg",
caption: String? = null,
formattedCaption: FormattedBody? = null,
source: MediaSource = FakeFfiMediaSource("mxc://server/image"),
info: ImageInfo? = null,
) = ImageMessageContent(
filename = filename,
caption = caption,
formattedCaption = formattedCaption,
source = source,
info = info,
)
internal fun aRustAudioMessageContent(
filename: String = "audio.mp3",
caption: String? = null,
formattedCaption: FormattedBody? = null,
source: MediaSource = FakeFfiMediaSource("mxc://server/audio"),
info: AudioInfo? = null,
audio: UnstableAudioDetailsContent? = null,
voice: UnstableVoiceContent? = null,
) = AudioMessageContent(
filename = filename,
caption = caption,
formattedCaption = formattedCaption,
source = source,
info = info,
audio = audio,
voice = voice,
)
internal fun aRustVideoMessageContent(
filename: String = "video.mp4",
caption: String? = null,
formattedCaption: FormattedBody? = null,
source: MediaSource = FakeFfiMediaSource("mxc://server/video"),
info: VideoInfo? = null,
) = VideoMessageContent(
filename = filename,
caption = caption,
formattedCaption = formattedCaption,
source = source,
info = info,
)
internal fun aRustFileMessageContent(
filename: String = "document.pdf",
caption: String? = null,
formattedCaption: FormattedBody? = null,
source: MediaSource = FakeFfiMediaSource("mxc://server/file"),
info: FileInfo? = null,
) = FileMessageContent(
filename = filename,
caption = caption,
formattedCaption = formattedCaption,
source = source,
info = info,
)
@@ -0,0 +1,18 @@
/*
* Copyright (c) 2026 Element Creations Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.matrix.impl.fixtures.fakes
import org.matrix.rustcomponents.sdk.MediaSource
import org.matrix.rustcomponents.sdk.NoHandle
internal class FakeFfiMediaSource(
private val fakeUrl: String = "mxc://server/media",
) : MediaSource(NoHandle) {
override fun url(): String = fakeUrl
override fun toJson(): String = """{"url":"$fakeUrl"}"""
}
@@ -0,0 +1,318 @@
/*
* Copyright (c) 2026 Element Creations Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.matrix.impl.timeline.item.event
import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.matrix.api.media.MediaSource
import io.element.android.libraries.matrix.api.room.location.AssetType
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
import io.element.android.libraries.matrix.api.timeline.item.event.GalleryItemType
import io.element.android.libraries.matrix.api.timeline.item.event.GalleryMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.ImageMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.LocationMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.NoticeMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.OtherMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.TextMessageType
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.fixtures.factories.aRustAudioMessageContent
import io.element.android.libraries.matrix.impl.fixtures.factories.aRustFileMessageContent
import io.element.android.libraries.matrix.impl.fixtures.factories.aRustGalleryItemTypeAudio
import io.element.android.libraries.matrix.impl.fixtures.factories.aRustGalleryItemTypeFile
import io.element.android.libraries.matrix.impl.fixtures.factories.aRustGalleryItemTypeImage
import io.element.android.libraries.matrix.impl.fixtures.factories.aRustGalleryItemTypeOther
import io.element.android.libraries.matrix.impl.fixtures.factories.aRustGalleryItemTypeVideo
import io.element.android.libraries.matrix.impl.fixtures.factories.aRustGalleryMessageContent
import io.element.android.libraries.matrix.impl.fixtures.factories.aRustImageMessageContent
import io.element.android.libraries.matrix.impl.fixtures.factories.aRustMessageTypeGallery
import io.element.android.libraries.matrix.impl.fixtures.factories.aRustVideoMessageContent
import org.junit.Test
import org.matrix.rustcomponents.sdk.EmoteMessageContent
import org.matrix.rustcomponents.sdk.LocationContent
import org.matrix.rustcomponents.sdk.MessageType
import org.matrix.rustcomponents.sdk.NoticeMessageContent
import org.matrix.rustcomponents.sdk.TextMessageContent
import org.matrix.rustcomponents.sdk.UnstableVoiceContent
import org.matrix.rustcomponents.sdk.AssetType as RustAssetType
class EventMessageMapperTest {
private val sut = EventMessageMapper()
@Test
fun `mapMessageType with Text returns TextMessageType`() {
val result = sut.mapMessageType(
MessageType.Text(
content = TextMessageContent(body = "Hello", formatted = null)
)
)
assertThat(result).isEqualTo(TextMessageType(body = "Hello", formatted = null))
}
@Test
fun `mapMessageType with Notice returns NoticeMessageType`() {
val result = sut.mapMessageType(
MessageType.Notice(content = NoticeMessageContent(body = "A notice", formatted = null))
)
assertThat(result).isEqualTo(NoticeMessageType(body = "A notice", formatted = null))
}
@Test
fun `mapMessageType with Emote returns EmoteMessageType`() {
val result = sut.mapMessageType(
MessageType.Emote(content = EmoteMessageContent(body = "An emote", formatted = null))
)
assertThat(result).isEqualTo(EmoteMessageType(body = "An emote", formatted = null))
}
@Test
fun `mapMessageType with Other returns OtherMessageType`() {
val result = sut.mapMessageType(
MessageType.Other(msgtype = "m.custom", body = "custom body")
)
assertThat(result).isEqualTo(OtherMessageType(msgType = "m.custom", body = "custom body"))
}
@Test
fun `mapMessageType with Location returns LocationMessageType`() {
val result = sut.mapMessageType(
MessageType.Location(
content = LocationContent(
body = "Location body",
geoUri = "geo:51.5,-0.1",
description = "London",
zoomLevel = null,
asset = RustAssetType.PIN,
)
)
)
assertThat(result).isEqualTo(
LocationMessageType(
body = "Location body",
geoUri = "geo:51.5,-0.1",
description = "London",
assetType = AssetType.PIN,
)
)
}
@Test
fun `mapMessageType with Audio without voice returns AudioMessageType`() {
val result = sut.mapMessageType(
MessageType.Audio(content = aRustAudioMessageContent(filename = "audio.mp3", voice = null))
)
assertThat(result).isEqualTo(
AudioMessageType(
filename = "audio.mp3",
caption = null,
formattedCaption = null,
source = MediaSource(url = "mxc://server/audio", json = """{"url":"mxc://server/audio"}"""),
info = null,
)
)
}
@Test
fun `mapMessageType with Audio with voice returns VoiceMessageType`() {
val result = sut.mapMessageType(
MessageType.Audio(content = aRustAudioMessageContent(filename = "voice.ogg", voice = UnstableVoiceContent()))
)
assertThat(result).isEqualTo(
VoiceMessageType(
filename = "voice.ogg",
caption = null,
formattedCaption = null,
source = MediaSource(url = "mxc://server/audio", json = """{"url":"mxc://server/audio"}"""),
info = null,
details = null,
)
)
}
@Test
fun `mapMessageType with File returns FileMessageType`() {
val result = sut.mapMessageType(
MessageType.File(content = aRustFileMessageContent(filename = "document.pdf"))
)
assertThat(result).isEqualTo(
FileMessageType(
filename = "document.pdf",
caption = null,
formattedCaption = null,
source = MediaSource(url = "mxc://server/file", json = """{"url":"mxc://server/file"}"""),
info = null,
)
)
}
@Test
fun `mapMessageType with Image returns ImageMessageType`() {
val result = sut.mapMessageType(
MessageType.Image(content = aRustImageMessageContent(filename = "image.jpg"))
)
assertThat(result).isEqualTo(
ImageMessageType(
filename = "image.jpg",
caption = null,
formattedCaption = null,
source = MediaSource(url = "mxc://server/image", json = """{"url":"mxc://server/image"}"""),
info = null,
)
)
}
@Test
fun `mapMessageType with Video returns VideoMessageType`() {
val result = sut.mapMessageType(
MessageType.Video(content = aRustVideoMessageContent(filename = "video.mp4"))
)
assertThat(result).isEqualTo(
VideoMessageType(
filename = "video.mp4",
caption = null,
formattedCaption = null,
source = MediaSource(url = "mxc://server/video", json = """{"url":"mxc://server/video"}"""),
info = null,
)
)
}
@Test
fun `mapMessageType with Gallery with Image item returns GalleryMessageType with Image`() {
val result = sut.mapMessageType(
aRustMessageTypeGallery(
content = aRustGalleryMessageContent(
body = "A gallery",
itemTypes = listOf(aRustGalleryItemTypeImage(aRustImageMessageContent(filename = "image.jpg"))),
)
)
)
assertThat(result).isEqualTo(
GalleryMessageType(
body = "A gallery",
formatted = null,
items = listOf(
GalleryItemType.Image(
content = ImageMessageType(
filename = "image.jpg",
caption = null,
formattedCaption = null,
source = MediaSource(url = "mxc://server/image", json = """{"url":"mxc://server/image"}"""),
info = null,
)
)
),
)
)
}
@Test
fun `mapMessageType with Gallery with Audio item returns GalleryMessageType with Audio`() {
val result = sut.mapMessageType(
aRustMessageTypeGallery(
content = aRustGalleryMessageContent(
itemTypes = listOf(aRustGalleryItemTypeAudio(aRustAudioMessageContent(filename = "audio.mp3")))
)
)
)
assertThat(result).isEqualTo(
GalleryMessageType(
body = "A gallery",
formatted = null,
items = listOf(
GalleryItemType.Audio(
content = AudioMessageType(
filename = "audio.mp3",
caption = null,
formattedCaption = null,
source = MediaSource(url = "mxc://server/audio", json = """{"url":"mxc://server/audio"}"""),
info = null,
)
)
),
)
)
}
@Test
fun `mapMessageType with Gallery with Video item returns GalleryMessageType with Video`() {
val result = sut.mapMessageType(
aRustMessageTypeGallery(
content = aRustGalleryMessageContent(
itemTypes = listOf(aRustGalleryItemTypeVideo(aRustVideoMessageContent(filename = "video.mp4")))
)
)
)
assertThat(result).isEqualTo(
GalleryMessageType(
body = "A gallery",
formatted = null,
items = listOf(
GalleryItemType.Video(
content = VideoMessageType(
filename = "video.mp4",
caption = null,
formattedCaption = null,
source = MediaSource(url = "mxc://server/video", json = """{"url":"mxc://server/video"}"""),
info = null,
)
)
),
)
)
}
@Test
fun `mapMessageType with Gallery with File item returns GalleryMessageType with File`() {
val result = sut.mapMessageType(
aRustMessageTypeGallery(
content = aRustGalleryMessageContent(
itemTypes = listOf(aRustGalleryItemTypeFile(aRustFileMessageContent(filename = "document.pdf")))
)
)
)
assertThat(result).isEqualTo(
GalleryMessageType(
body = "A gallery",
formatted = null,
items = listOf(
GalleryItemType.File(
content = FileMessageType(
filename = "document.pdf",
caption = null,
formattedCaption = null,
source = MediaSource(url = "mxc://server/file", json = """{"url":"mxc://server/file"}"""),
info = null,
)
)
),
)
)
}
@Test
fun `mapMessageType with Gallery with Other item returns GalleryMessageType with Other`() {
val result = sut.mapMessageType(
aRustMessageTypeGallery(
content = aRustGalleryMessageContent(
itemTypes = listOf(aRustGalleryItemTypeOther(itemType = "m.custom", body = "custom item"))
)
)
)
assertThat(result).isEqualTo(
GalleryMessageType(
body = "A gallery",
formatted = null,
items = listOf(
GalleryItemType.Other(itemType = "m.custom", body = "custom item")
),
)
)
}
}