diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/messages/reply/InReplyToMetadata.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/messages/reply/InReplyToMetadata.kt index 9e5e468cd9..2da5184389 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/messages/reply/InReplyToMetadata.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/messages/reply/InReplyToMetadata.kt @@ -10,12 +10,15 @@ package io.element.android.libraries.matrix.ui.messages.reply import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable +import androidx.compose.ui.res.pluralStringResource import androidx.compose.ui.res.stringResource import io.element.android.libraries.matrix.api.timeline.item.event.AudioMessageType import io.element.android.libraries.matrix.api.timeline.item.event.CallNotifyContent import io.element.android.libraries.matrix.api.timeline.item.event.FailedToParseMessageLikeContent import io.element.android.libraries.matrix.api.timeline.item.event.FailedToParseStateContent 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.LegacyCallInviteContent import io.element.android.libraries.matrix.api.timeline.item.event.LiveLocationContent @@ -35,6 +38,7 @@ import io.element.android.libraries.matrix.ui.components.AttachmentThumbnailInfo import io.element.android.libraries.matrix.ui.components.AttachmentThumbnailType import io.element.android.libraries.matrix.ui.messages.reply.InReplyToMetadata.Text import io.element.android.libraries.matrix.ui.messages.reply.InReplyToMetadata.Thumbnail +import io.element.android.libraries.ui.strings.CommonPlurals import io.element.android.libraries.ui.strings.CommonStrings @Immutable @@ -104,6 +108,44 @@ internal fun InReplyToDetails.Ready.metadata(hideImage: Boolean): InReplyToMetad type = AttachmentThumbnailType.Voice, ) ) + is GalleryMessageType -> { + val caption = textContent?.takeIf { it.isNotBlank() } + val isMediaGallery = type.items.all { it is GalleryItemType.Image || it is GalleryItemType.Video } + val countPlural = if (isMediaGallery) { + CommonPlurals.common_gallery_reply_media_items + } else { + CommonPlurals.common_gallery_reply_attachments + } + val text = caption ?: pluralStringResource(countPlural, type.items.size, type.items.size) + if (isMediaGallery) { + val firstMediaItem = type.items.firstOrNull { it is GalleryItemType.Image || it is GalleryItemType.Video } + val thumbnailSource = when (firstMediaItem) { + is GalleryItemType.Image -> (firstMediaItem.content.info?.thumbnailSource ?: firstMediaItem.content.source).takeUnless { hideImage } + is GalleryItemType.Video -> firstMediaItem.content.info?.thumbnailSource?.takeUnless { hideImage } + else -> null + } + val blurHash = when (firstMediaItem) { + is GalleryItemType.Image -> firstMediaItem.content.info?.blurhash + is GalleryItemType.Video -> firstMediaItem.content.info?.blurhash + else -> null + } + Thumbnail( + AttachmentThumbnailInfo( + thumbnailSource = thumbnailSource, + textContent = text, + type = AttachmentThumbnailType.Image, + blurHash = blurHash, + ) + ) + } else { + Thumbnail( + AttachmentThumbnailInfo( + textContent = text, + type = AttachmentThumbnailType.File, + ) + ) + } + } else -> Text(textContent ?: eventContent.body) } is StickerContent -> Thumbnail( diff --git a/libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrix/ui/messages/reply/InReplyToMetadataKtTest.kt b/libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrix/ui/messages/reply/InReplyToMetadataKtTest.kt index 0847315867..5b6df661f3 100644 --- a/libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrix/ui/messages/reply/InReplyToMetadataKtTest.kt +++ b/libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrix/ui/messages/reply/InReplyToMetadataKtTest.kt @@ -24,6 +24,8 @@ import io.element.android.libraries.matrix.api.timeline.item.event.EventContent import io.element.android.libraries.matrix.api.timeline.item.event.FailedToParseMessageLikeContent import io.element.android.libraries.matrix.api.timeline.item.event.FailedToParseStateContent 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.OtherState @@ -407,6 +409,226 @@ class InReplyToMetadataKtTest : RobolectricTest() { } } + @Test + fun `a gallery message with all media items shows media count`() = runTest { + moleculeFlow(RecompositionMode.Immediate) { + withConfigurationAndContext { + anInReplyToDetailsReady( + eventContent = aMessageContent( + messageType = GalleryMessageType( + body = "", + formatted = null, + items = listOf( + GalleryItemType.Image( + ImageMessageType( + filename = "image1.jpg", + caption = null, + formattedCaption = null, + source = aMediaSource(), + info = anImageInfo(), + ) + ), + GalleryItemType.Image( + ImageMessageType( + filename = "image2.jpg", + caption = null, + formattedCaption = null, + source = aMediaSource(), + info = anImageInfo(), + ) + ), + ), + ) + ), + textContent = "", + ).metadata(hideImage = false) + } + }.test { + awaitItem().let { + assertThat(it).isEqualTo( + InReplyToMetadata.Thumbnail( + attachmentThumbnailInfo = AttachmentThumbnailInfo( + thumbnailSource = aMediaSource(), + textContent = "2 media items…", + type = AttachmentThumbnailType.Image, + blurHash = A_BLUR_HASH, + ) + ) + ) + } + } + } + + @Test + fun `a gallery message with attachment items shows attachment count`() = runTest { + moleculeFlow(RecompositionMode.Immediate) { + withConfigurationAndContext { + anInReplyToDetailsReady( + eventContent = aMessageContent( + messageType = GalleryMessageType( + body = "", + formatted = null, + items = listOf( + GalleryItemType.File( + FileMessageType( + filename = "doc1.pdf", + caption = null, + formattedCaption = null, + source = aMediaSource(), + info = FileInfo( + mimetype = null, + size = null, + thumbnailInfo = null, + thumbnailSource = null, + ), + ) + ), + GalleryItemType.File( + FileMessageType( + filename = "doc2.pdf", + caption = null, + formattedCaption = null, + source = aMediaSource(), + info = FileInfo( + mimetype = null, + size = null, + thumbnailInfo = null, + thumbnailSource = null, + ), + ) + ), + ), + ) + ), + textContent = "", + ).metadata(hideImage = false) + } + }.test { + awaitItem().let { + assertThat(it).isEqualTo( + InReplyToMetadata.Thumbnail( + attachmentThumbnailInfo = AttachmentThumbnailInfo( + thumbnailSource = null, + textContent = "2 attachments…", + type = AttachmentThumbnailType.File, + blurHash = null, + ) + ) + ) + } + } + } + + @Test + fun `a gallery message with caption shows caption instead of count`() = runTest { + moleculeFlow(RecompositionMode.Immediate) { + withConfigurationAndContext { + anInReplyToDetailsReady( + eventContent = aMessageContent( + messageType = GalleryMessageType( + body = "My vacation photos", + formatted = null, + items = listOf( + GalleryItemType.Image( + ImageMessageType( + filename = "image1.jpg", + caption = null, + formattedCaption = null, + source = aMediaSource(), + info = anImageInfo(), + ) + ), + GalleryItemType.Image( + ImageMessageType( + filename = "image2.jpg", + caption = null, + formattedCaption = null, + source = aMediaSource(), + info = anImageInfo(), + ) + ), + ), + ) + ), + textContent = "My vacation photos", + ).metadata(hideImage = false) + } + }.test { + awaitItem().let { + assertThat(it).isEqualTo( + InReplyToMetadata.Thumbnail( + attachmentThumbnailInfo = AttachmentThumbnailInfo( + thumbnailSource = aMediaSource(), + textContent = "My vacation photos", + type = AttachmentThumbnailType.Image, + blurHash = A_BLUR_HASH, + ) + ) + ) + } + } + } + + @Test + fun `a gallery message with attachment items and caption shows caption instead of count`() = runTest { + moleculeFlow(RecompositionMode.Immediate) { + withConfigurationAndContext { + anInReplyToDetailsReady( + eventContent = aMessageContent( + messageType = GalleryMessageType( + body = "My documents", + formatted = null, + items = listOf( + GalleryItemType.File( + FileMessageType( + filename = "doc1.pdf", + caption = null, + formattedCaption = null, + source = aMediaSource(), + info = FileInfo( + mimetype = null, + size = null, + thumbnailInfo = null, + thumbnailSource = null, + ), + ) + ), + GalleryItemType.File( + FileMessageType( + filename = "doc2.pdf", + caption = null, + formattedCaption = null, + source = aMediaSource(), + info = FileInfo( + mimetype = null, + size = null, + thumbnailInfo = null, + thumbnailSource = null, + ), + ) + ), + ), + ) + ), + textContent = "My documents", + ).metadata(hideImage = false) + } + }.test { + awaitItem().let { + assertThat(it).isEqualTo( + InReplyToMetadata.Thumbnail( + attachmentThumbnailInfo = AttachmentThumbnailInfo( + thumbnailSource = null, + textContent = "My documents", + type = AttachmentThumbnailType.File, + blurHash = null, + ) + ) + ) + } + } + } + @Test fun `a poll content`() = runTest { moleculeFlow(RecompositionMode.Immediate) { diff --git a/libraries/ui-strings/src/main/res/values/temporary.xml b/libraries/ui-strings/src/main/res/values/temporary.xml index fd1edb0759..47d2007517 100644 --- a/libraries/ui-strings/src/main/res/values/temporary.xml +++ b/libraries/ui-strings/src/main/res/values/temporary.xml @@ -8,4 +8,10 @@ "Mark as read" "Jump to first unread message" + + "%1$d media items…" + + + "%1$d attachments…" +