Support gallery messages (#6519)

* Support gallery messages

* Address review

* Remove unused function

* Fix indent

* Add "Gallery" message prefix

* Support galleries for image editing

* Fix tapping on a tile opens the last item

* Fix overflow count

* Add caption editing to gallery messages

* Use the new gallery string for prefix

* Lock sending galleries behind a feature flag

* Fix detekts

* Fix

* Ensure image edition is saved when navigating

* Fix sending media broken on edited gallery.

* Fix tests

* Order imports

* Remove unused parameters.

* Fix copyright header of new files.

* Fix Konsist test

* Extract new previews to a dedicated file.

* Sync strings

* Remove unused import

* Update screenshots

* Trigger CI

* Remove parameters with default value.

* More cleanup

* Restore sendAsFile behavior.

* Improve Preview.

* Improve Preview.

* Improve Preview.

* Fix gallery sending cancel and retry issue

* Ensure any previous job is cancelled.

* Fix issue in summary message

* Gallery feature is disabled by default.

* Kotlin convention

* Remove useless parenthesis

* Update screenshots

* Fix test

* List -> ImmutableList

* Remove useless code.

* Render formatted caption for attachment list.

* Replace set of Booleans by an enum

* Remove unused model for individual caption in a gallery Event.

* Fix tests

* Fix tests

* Rework MediaViewer entry point.

And ensure that the clicked image from the gallery is displayed first.

* Ensure gallery item can be click in the pinned message list
Improve the gallery item click handling code.

* Improve code and fix separator color
Closes #7101

* React on attachment item click
Improve code

* Improve code and support 0 items in gallery.

* Fix click on attachment item not rendering anything.

---------

Co-authored-by: Benoit Marty <benoitm@element.io>
Co-authored-by: Benoit Marty <benoit@matrix.org>
Co-authored-by: ElementBot <android@element.io>
This commit is contained in:
bxdxnn
2026-06-26 17:00:56 +03:00
committed by GitHub
parent 9e7ae34728
commit 87aff40b26
180 changed files with 3470 additions and 637 deletions
@@ -61,5 +61,12 @@ interface MediaSender {
inReplyToEventId: EventId? = null,
): Result<Unit>
suspend fun sendGallery(
mediaUploadInfos: List<MediaUploadInfo>,
caption: String?,
formattedCaption: String?,
inReplyToEventId: EventId?,
): Result<Unit>
fun cleanUp()
}
@@ -10,6 +10,7 @@ package io.element.android.libraries.mediaupload.api
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.VideoInfo
import java.io.File
@@ -31,3 +32,30 @@ fun MediaUploadInfo.allFiles(): List<File> {
(this@allFiles as? MediaUploadInfo.Video)?.thumbnailFile,
)
}
fun MediaUploadInfo.toGalleryItemInfo(): GalleryItemInfo {
return when (this) {
is MediaUploadInfo.Image -> GalleryItemInfo.Image(
file = file,
imageInfo = imageInfo,
thumbnailFile = thumbnailFile,
)
is MediaUploadInfo.Video -> GalleryItemInfo.Video(
file = file,
videoInfo = videoInfo,
thumbnailFile = thumbnailFile,
)
is MediaUploadInfo.Audio -> GalleryItemInfo.Audio(
file = file,
audioInfo = audioInfo,
)
is MediaUploadInfo.VoiceMessage -> GalleryItemInfo.Audio(
file = file,
audioInfo = audioInfo,
)
is MediaUploadInfo.AnyFile -> GalleryItemInfo.MediaFile(
file = file,
fileInfo = fileInfo,
)
}
}
@@ -27,6 +27,7 @@ import io.element.android.libraries.mediaupload.api.MediaSender
import io.element.android.libraries.mediaupload.api.MediaSenderFactory
import io.element.android.libraries.mediaupload.api.MediaSenderRoomFactory
import io.element.android.libraries.mediaupload.api.MediaUploadInfo
import io.element.android.libraries.mediaupload.api.toGalleryItemInfo
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Job
import timber.log.Timber
@@ -167,6 +168,29 @@ class DefaultMediaSender(
.handleSendResult(mediaId(uri))
}
override suspend fun sendGallery(
mediaUploadInfos: List<MediaUploadInfo>,
caption: String?,
formattedCaption: String?,
inReplyToEventId: EventId?,
): Result<Unit> {
val galleryLogId = "gallery[${mediaUploadInfos.size} items]"
Timber.d("Sending $galleryLogId")
return getTimeline().flatMap { timeline ->
val galleryItems = mediaUploadInfos.map { it.toGalleryItemInfo() }
timeline.sendGallery(
items = galleryItems,
caption = caption,
formattedCaption = formattedCaption,
inReplyToEventId = inReplyToEventId,
)
}
.flatMapCatching { uploadHandler ->
uploadHandler.await()
}
.handleSendResult(galleryLogId)
}
private fun Result<Unit>.handleSendResult(mediaId: String) = this
.onFailure { error ->
val job = ongoingUploadJobs.remove(Job)
@@ -19,6 +19,7 @@ class FakeMediaSender(
private val sendPreProcessedMediaResult: () -> Result<Unit> = { lambdaError() },
private val sendMediaResult: () -> Result<Unit> = { lambdaError() },
private val sendVoiceMessageResult: () -> Result<Unit> = { lambdaError() },
private val sendGalleryResult: () -> Result<Unit> = { lambdaError() },
private val cleanUpResult: () -> Unit = { lambdaError() },
) : MediaSender {
override suspend fun preProcessMedia(
@@ -58,6 +59,15 @@ class FakeMediaSender(
return sendVoiceMessageResult()
}
override suspend fun sendGallery(
mediaUploadInfos: List<MediaUploadInfo>,
caption: String?,
formattedCaption: String?,
inReplyToEventId: EventId?,
): Result<Unit> {
return sendGalleryResult()
}
override fun cleanUp() {
cleanUpResult()
}