From 2de51bc768b2d8ae3a393ed64b6123aaec1675b2 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 1 Oct 2024 14:40:53 +0200 Subject: [PATCH] Clarify model for Event with attachment. They must have a `filename: String` and they may have a `body :String?` (caption). If filename is missing from the SDK model, the body is used instead and in this case the body is not mapped to our model. --- .../messages/impl/MessagesFlowNode.kt | 15 ++-- .../impl/actionlist/ActionListView.kt | 10 +-- .../components/event/TimelineItemAudioView.kt | 2 +- .../components/event/TimelineItemFileView.kt | 2 +- .../components/event/TimelineItemImageView.kt | 3 +- .../event/TimelineItemStickerView.kt | 9 ++- .../components/event/TimelineItemVideoView.kt | 3 +- .../TimelineItemContentMessageFactory.kt | 35 ++++++---- .../TimelineItemContentStickerFactory.kt | 3 +- .../model/event/TimelineItemAudioContent.kt | 5 +- .../event/TimelineItemAudioContentProvider.kt | 3 +- .../model/event/TimelineItemEventContent.kt | 9 +++ .../model/event/TimelineItemFileContent.kt | 5 +- .../event/TimelineItemFileContentProvider.kt | 3 +- .../model/event/TimelineItemImageContent.kt | 10 +-- .../event/TimelineItemImageContentProvider.kt | 8 ++- .../model/event/TimelineItemStickerContent.kt | 5 +- .../TimelineItemStickerContentProvider.kt | 1 + .../model/event/TimelineItemVideoContent.kt | 10 +-- .../event/TimelineItemVideoContentProvider.kt | 4 +- .../model/event/TimelineItemVoiceContent.kt | 5 +- .../event/TimelineItemVoiceContentProvider.kt | 4 +- .../messages/impl/MessagesPresenterTest.kt | 11 +-- .../pinned/list/PinnedMessagesListViewTest.kt | 4 +- .../impl/timeline/TimelineViewTest.kt | 2 +- .../TimelineItemContentMessageFactoryTest.kt | 70 +++++++++++-------- .../roomdetails/impl/RoomDetailsFlowNode.kt | 1 + .../userprofile/impl/UserProfileFlowNode.kt | 1 + .../DefaultPinnedMessagesBannerFormatter.kt | 15 ++-- .../impl/DefaultRoomLastMessageFormatter.kt | 2 +- ...efaultPinnedMessagesBannerFormatterTest.kt | 12 ++-- .../DefaultRoomLastMessageFormatterTest.kt | 12 ++-- .../api/timeline/item/event/EventContent.kt | 8 ++- .../api/timeline/item/event/MessageType.kt | 42 +++++++---- .../timeline/item/event/EventMessageMapper.kt | 29 ++++++-- .../item/event/TimelineEventContentMapper.kt | 3 +- .../matrix/test/timeline/TimelineFixture.kt | 15 ++++ .../matrix/ui/media/MediaRequestData.kt | 8 ++- .../reply/InReplyToDetailsProvider.kt | 8 +-- .../messages/reply/InReplyToMetadataKtTest.kt | 12 ++-- .../mediaviewer/api/local/MediaInfo.kt | 46 ++++++------ .../mediaviewer/api/viewer/MediaViewerView.kt | 2 +- .../impl/local/AndroidLocalMediaFactory.kt | 34 ++++++--- .../local/AndroidLocalMediaFactoryTest.kt | 1 + .../mediaviewer/test/FakeLocalMediaFactory.kt | 1 + .../DefaultNotifiableEventResolver.kt | 10 +-- .../DefaultNotifiableEventResolverTest.kt | 10 +-- 47 files changed, 329 insertions(+), 184 deletions(-) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesFlowNode.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesFlowNode.kt index 08b485b1e7..0e0a477021 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesFlowNode.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesFlowNode.kt @@ -324,7 +324,8 @@ class MessagesFlowNode @AssistedInject constructor( is TimelineItemImageContent -> { val navTarget = NavTarget.MediaViewer( mediaInfo = MediaInfo( - name = event.content.filename ?: event.content.body, + name = event.content.filename, + body = event.content.body, mimeType = event.content.mimeType, formattedFileSize = event.content.formattedFileSize, fileExtension = event.content.fileExtension @@ -341,7 +342,8 @@ class MessagesFlowNode @AssistedInject constructor( if (event.content.preferredMediaSource != null) { val navTarget = NavTarget.MediaViewer( mediaInfo = MediaInfo( - name = event.content.body, + name = event.content.filename, + body = event.content.body, mimeType = event.content.mimeType, formattedFileSize = event.content.formattedFileSize, fileExtension = event.content.fileExtension @@ -358,7 +360,8 @@ class MessagesFlowNode @AssistedInject constructor( is TimelineItemVideoContent -> { val navTarget = NavTarget.MediaViewer( mediaInfo = MediaInfo( - name = event.content.filename ?: event.content.body, + name = event.content.filename, + body = event.content.body, mimeType = event.content.mimeType, formattedFileSize = event.content.formattedFileSize, fileExtension = event.content.fileExtension @@ -372,7 +375,8 @@ class MessagesFlowNode @AssistedInject constructor( is TimelineItemFileContent -> { val navTarget = NavTarget.MediaViewer( mediaInfo = MediaInfo( - name = event.content.body, + name = event.content.filename, + body = event.content.body, mimeType = event.content.mimeType, formattedFileSize = event.content.formattedFileSize, fileExtension = event.content.fileExtension @@ -386,7 +390,8 @@ class MessagesFlowNode @AssistedInject constructor( is TimelineItemAudioContent -> { val navTarget = NavTarget.MediaViewer( mediaInfo = MediaInfo( - name = event.content.body, + name = event.content.filename, + body = event.content.body, mimeType = event.content.mimeType, formattedFileSize = event.content.formattedFileSize, fileExtension = event.content.fileExtension diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListView.kt index cccfe89409..0b9377ae90 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListView.kt @@ -269,19 +269,19 @@ private fun MessageSummary(event: TimelineItem.Event, modifier: Modifier = Modif content = { ContentForBody(stringResource(CommonStrings.common_shared_location)) } } is TimelineItemImageContent -> { - content = { ContentForBody(event.content.body) } + content = { ContentForBody(event.content.bestDescription) } } is TimelineItemStickerContent -> { - content = { ContentForBody(event.content.body) } + content = { ContentForBody(event.content.bestDescription) } } is TimelineItemVideoContent -> { - content = { ContentForBody(event.content.body) } + content = { ContentForBody(event.content.bestDescription) } } is TimelineItemFileContent -> { - content = { ContentForBody(event.content.body) } + content = { ContentForBody(event.content.bestDescription) } } is TimelineItemAudioContent -> { - content = { ContentForBody(event.content.body) } + content = { ContentForBody(event.content.bestDescription) } } is TimelineItemVoiceContent -> { content = { ContentForBody(textContent) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemAudioView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemAudioView.kt index 3ea3f33135..23069a1fac 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemAudioView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemAudioView.kt @@ -63,7 +63,7 @@ fun TimelineItemAudioView( Spacer(Modifier.width(spacing)) Column { Text( - text = content.body, + text = content.bestDescription, color = ElementTheme.materialColors.primary, maxLines = 2, style = ElementTheme.typography.fontBodyLgRegular, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemFileView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemFileView.kt index 354bfaf2c7..dadfadc299 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemFileView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemFileView.kt @@ -64,7 +64,7 @@ fun TimelineItemFileView( Spacer(Modifier.width(spacing)) Column { Text( - text = content.body, + text = content.bestDescription, color = ElementTheme.materialColors.primary, maxLines = 2, style = ElementTheme.typography.fontBodyLgRegular, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemImageView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemImageView.kt index 9f9222f458..de7ac033e1 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemImageView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemImageView.kt @@ -84,7 +84,8 @@ fun TimelineItemImageView( model = MediaRequestData( source = content.preferredMediaSource, kind = MediaRequestData.Kind.File( - body = content.filename ?: content.body, + fileName = content.filename, + body = content.body, mimeType = content.mimeType, ), ), diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemStickerView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemStickerView.kt index f895eddc85..a2226fb563 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemStickerView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemStickerView.kt @@ -37,7 +37,14 @@ fun TimelineItemStickerView( contentAlignment = Alignment.TopStart, ) { BlurHashAsyncImage( - model = MediaRequestData(content.preferredMediaSource, MediaRequestData.Kind.File(content.body, content.mimeType)), + model = MediaRequestData( + source = content.preferredMediaSource, + kind = MediaRequestData.Kind.File( + fileName = content.filename, + body = content.body, + mimeType = content.mimeType, + ) + ), blurHash = content.blurhash, ) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemVideoView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemVideoView.kt index 7324d3368b..750e458fdc 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemVideoView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemVideoView.kt @@ -91,7 +91,8 @@ fun TimelineItemVideoView( model = MediaRequestData( source = content.thumbnailSource, kind = MediaRequestData.Kind.File( - body = content.filename ?: content.body, + fileName = content.filename, + body = content.body, mimeType = content.mimeType ) ), diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactory.kt index d171c3dd0f..fed47e22b2 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactory.kt @@ -84,9 +84,9 @@ class TimelineItemContentMessageFactory @Inject constructor( is ImageMessageType -> { val aspectRatio = aspectRatioOf(messageType.info?.width, messageType.info?.height) TimelineItemImageContent( - body = messageType.body.trimEnd(), - formatted = messageType.formatted, filename = messageType.filename, + body = messageType.body?.trimEnd(), + formatted = messageType.formatted, mediaSource = messageType.source, thumbnailSource = messageType.info?.thumbnailSource, mimeType = messageType.info?.mimetype ?: MimeTypes.OctetStream, @@ -95,13 +95,14 @@ class TimelineItemContentMessageFactory @Inject constructor( height = messageType.info?.height?.toInt(), aspectRatio = aspectRatio, formattedFileSize = fileSizeFormatter.format(messageType.info?.size ?: 0), - fileExtension = messageType.filename?.let { fileExtensionExtractor.extractFromName(it) }.orEmpty() + fileExtension = fileExtensionExtractor.extractFromName(messageType.filename) ) } is StickerMessageType -> { val aspectRatio = aspectRatioOf(messageType.info?.width, messageType.info?.height) TimelineItemStickerContent( - body = messageType.body.trimEnd(), + filename = messageType.filename, + body = messageType.body?.trimEnd(), mediaSource = messageType.source, thumbnailSource = messageType.info?.thumbnailSource, mimeType = messageType.info?.mimetype ?: MimeTypes.OctetStream, @@ -110,7 +111,7 @@ class TimelineItemContentMessageFactory @Inject constructor( height = messageType.info?.height?.toInt(), aspectRatio = aspectRatio, formattedFileSize = fileSizeFormatter.format(messageType.info?.size ?: 0), - fileExtension = fileExtensionExtractor.extractFromName(messageType.body) + fileExtension = fileExtensionExtractor.extractFromName(messageType.filename) ) } is LocationMessageType -> { @@ -136,9 +137,9 @@ class TimelineItemContentMessageFactory @Inject constructor( is VideoMessageType -> { val aspectRatio = aspectRatioOf(messageType.info?.width, messageType.info?.height) TimelineItemVideoContent( - body = messageType.body.trimEnd(), - formatted = messageType.formatted, filename = messageType.filename, + body = messageType.body?.trimEnd(), + formatted = messageType.formatted, thumbnailSource = messageType.info?.thumbnailSource, videoSource = messageType.source, mimeType = messageType.info?.mimetype ?: MimeTypes.OctetStream, @@ -148,17 +149,18 @@ class TimelineItemContentMessageFactory @Inject constructor( blurHash = messageType.info?.blurhash, aspectRatio = aspectRatio, formattedFileSize = fileSizeFormatter.format(messageType.info?.size ?: 0), - fileExtension = messageType.filename?.let { fileExtensionExtractor.extractFromName(it) }.orEmpty(), + fileExtension = fileExtensionExtractor.extractFromName(messageType.filename), ) } is AudioMessageType -> { TimelineItemAudioContent( - body = messageType.body.trimEnd(), + filename = messageType.filename, + body = messageType.body?.trimEnd(), mediaSource = messageType.source, duration = messageType.info?.duration ?: Duration.ZERO, mimeType = messageType.info?.mimetype ?: MimeTypes.OctetStream, formattedFileSize = fileSizeFormatter.format(messageType.info?.size ?: 0), - fileExtension = fileExtensionExtractor.extractFromName(messageType.body), + fileExtension = fileExtensionExtractor.extractFromName(messageType.filename), ) } is VoiceMessageType -> { @@ -166,7 +168,8 @@ class TimelineItemContentMessageFactory @Inject constructor( true -> { TimelineItemVoiceContent( eventId = eventId, - body = messageType.body.trimEnd(), + filename = messageType.filename, + body = messageType.body?.trimEnd(), mediaSource = messageType.source, duration = messageType.info?.duration ?: Duration.ZERO, mimeType = messageType.info?.mimetype ?: MimeTypes.OctetStream, @@ -175,20 +178,22 @@ class TimelineItemContentMessageFactory @Inject constructor( } false -> { TimelineItemAudioContent( - body = messageType.body.trimEnd(), + filename = messageType.filename, + body = messageType.body?.trimEnd(), mediaSource = messageType.source, duration = messageType.info?.duration ?: Duration.ZERO, mimeType = messageType.info?.mimetype ?: MimeTypes.OctetStream, formattedFileSize = fileSizeFormatter.format(messageType.info?.size ?: 0), - fileExtension = fileExtensionExtractor.extractFromName(messageType.body), + fileExtension = fileExtensionExtractor.extractFromName(messageType.filename), ) } } } is FileMessageType -> { - val fileExtension = fileExtensionExtractor.extractFromName(messageType.body) + val fileExtension = fileExtensionExtractor.extractFromName(messageType.filename) TimelineItemFileContent( - body = messageType.body.trimEnd(), + filename = messageType.filename, + body = messageType.body?.trimEnd(), thumbnailSource = messageType.info?.thumbnailSource, fileSource = messageType.source, mimeType = messageType.info?.mimetype ?: MimeTypes.fromFileExtension(fileExtension), diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentStickerFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentStickerFactory.kt index 787e4d4be6..cf9cfb8461 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentStickerFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentStickerFactory.kt @@ -33,6 +33,7 @@ class TimelineItemContentStickerFactory @Inject constructor( val aspectRatio = aspectRatioOf(content.info.width, content.info.height) return TimelineItemStickerContent( + filename = content.filename, body = content.body, mediaSource = content.source, thumbnailSource = content.info.thumbnailSource, @@ -42,7 +43,7 @@ class TimelineItemContentStickerFactory @Inject constructor( height = content.info.height?.toInt(), aspectRatio = aspectRatio, formattedFileSize = fileSizeFormatter.format(content.info.size ?: 0), - fileExtension = fileExtensionExtractor.extractFromName(content.body) + fileExtension = fileExtensionExtractor.extractFromName(content.filename) ) } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemAudioContent.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemAudioContent.kt index aa33804d8e..62624a65d1 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemAudioContent.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemAudioContent.kt @@ -12,13 +12,14 @@ import io.element.android.libraries.mediaviewer.api.helper.formatFileExtensionAn import kotlin.time.Duration data class TimelineItemAudioContent( - val body: String, + override val filename: String, + override val body: String?, val duration: Duration, val mediaSource: MediaSource, val mimeType: String, val formattedFileSize: String, val fileExtension: String, -) : TimelineItemEventContent { +) : TimelineItemEventContentWithAttachment { val fileExtensionAndSize = formatFileExtensionAndSize( fileExtension, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemAudioContentProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemAudioContentProvider.kt index 25933e9ecd..0154543371 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemAudioContentProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemAudioContentProvider.kt @@ -22,7 +22,8 @@ open class TimelineItemAudioContentProvider : PreviewParameterProvider, -) : TimelineItemEventContent { +) : TimelineItemEventContentWithAttachment { override val type: String = "TimelineItemAudioContent" } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemVoiceContentProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemVoiceContentProvider.kt index 84354008f5..2ac342f144 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemVoiceContentProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemVoiceContentProvider.kt @@ -36,13 +36,15 @@ open class TimelineItemVoiceContentProvider : PreviewParameterProvider = listOf(0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, 8f, 9f, 8f, 7f, 6f, 5f, 4f, 3f, 2f, 1f, 0f), ) = TimelineItemVoiceContent( eventId = eventId?.let { EventId(it) }, + filename = filename, body = body, duration = duration, mediaSource = MediaSource(contentUri), diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt index be1b4aa7a8..35c5de5d07 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt @@ -353,9 +353,9 @@ class MessagesPresenterTest { val initialState = awaitFirstItem() val mediaMessage = aMessageEvent( content = TimelineItemImageContent( - body = "image.jpg", + filename = "image.jpg", formatted = null, - filename = null, + body = null, mediaSource = MediaSource(AN_AVATAR_URL), thumbnailSource = null, mimeType = MimeTypes.Jpeg, @@ -385,9 +385,9 @@ class MessagesPresenterTest { val initialState = awaitFirstItem() val mediaMessage = aMessageEvent( content = TimelineItemVideoContent( - body = "video.mp4", + filename = "video.mp4", formatted = null, - filename = null, + body = null, duration = 10.milliseconds, videoSource = MediaSource(AN_AVATAR_URL), thumbnailSource = MediaSource(AN_AVATAR_URL), @@ -418,7 +418,8 @@ class MessagesPresenterTest { val initialState = awaitFirstItem() val mediaMessage = aMessageEvent( content = TimelineItemFileContent( - body = "file.pdf", + filename = "file.pdf", + body = null, fileSource = MediaSource(AN_AVATAR_URL), thumbnailSource = MediaSource(AN_AVATAR_URL), formattedFileSize = "10 MB", diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListViewTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListViewTest.kt index a7c8e1c5aa..fb161f3a23 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListViewTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListViewTest.kt @@ -68,7 +68,7 @@ class PinnedMessagesListViewTest { state = state, onEventClick = callback ) - rule.onAllNodesWithText(content.body).onFirst().performClick() + rule.onAllNodesWithText(content.filename).onFirst().performClick() } } @@ -84,7 +84,7 @@ class PinnedMessagesListViewTest { rule.setPinnedMessagesListView( state = state, ) - rule.onAllNodesWithText(content.body).onFirst() + rule.onAllNodesWithText(content.filename).onFirst() .performTouchInput { longClick() } diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelineViewTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelineViewTest.kt index 4292e6f226..4650b68bb6 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelineViewTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelineViewTest.kt @@ -102,7 +102,7 @@ class TimelineViewTest { timelineItems = persistentListOf( aTimelineItemEvent( // Do not use a Text because EditorStyledText cannot be used in UI test. - content = aTimelineItemImageContent(), + content = aTimelineItemImageContent(body = null), messageShield = MessageShield.UnverifiedIdentity(true), ), ), diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactoryTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactoryTest.kt index c627ff3fdc..11f6736ccb 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactoryTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactoryTest.kt @@ -62,6 +62,7 @@ import io.element.android.libraries.matrix.api.timeline.item.event.VoiceMessageT import io.element.android.libraries.matrix.test.AN_EVENT_ID import io.element.android.libraries.matrix.test.media.aMediaSource import io.element.android.libraries.matrix.test.permalink.FakePermalinkParser +import io.element.android.libraries.matrix.test.timeline.aStickerContent import io.element.android.libraries.matrix.ui.components.A_BLUR_HASH import io.element.android.libraries.mediaviewer.api.util.FileExtensionExtractorWithoutValidation import kotlinx.collections.immutable.persistentListOf @@ -228,14 +229,14 @@ class TimelineItemContentMessageFactoryTest { fun `test create VideoMessageType`() = runTest { val sut = createTimelineItemContentMessageFactory() val result = sut.create( - content = createMessageContent(type = VideoMessageType("body", null, null, MediaSource("url"), null)), + content = createMessageContent(type = VideoMessageType("filename", null, null, MediaSource("url"), null)), senderDisambiguatedDisplayName = "Bob", eventId = AN_EVENT_ID, ) val expected = TimelineItemVideoContent( - body = "body", + filename = "filename", formatted = null, - filename = null, + body = null, duration = Duration.ZERO, videoSource = MediaSource(url = "url", json = null), thumbnailSource = null, @@ -302,12 +303,13 @@ class TimelineItemContentMessageFactoryTest { fun `test create AudioMessageType`() = runTest { val sut = createTimelineItemContentMessageFactory() val result = sut.create( - content = createMessageContent(type = AudioMessageType("body", MediaSource("url"), null)), + content = createMessageContent(type = AudioMessageType("filename", null, MediaSource("url"), null)), senderDisambiguatedDisplayName = "Bob", eventId = AN_EVENT_ID, ) val expected = TimelineItemAudioContent( - body = "body", + filename = "filename", + body = null, duration = Duration.ZERO, mediaSource = MediaSource(url = "url", json = null), mimeType = MimeTypes.OctetStream, @@ -323,7 +325,8 @@ class TimelineItemContentMessageFactoryTest { val result = sut.create( content = createMessageContent( type = AudioMessageType( - body = "body.mp3", + filename = "body.mp3", + body = null, source = MediaSource("url"), info = AudioInfo( duration = 1.minutes, @@ -336,7 +339,8 @@ class TimelineItemContentMessageFactoryTest { eventId = AN_EVENT_ID, ) val expected = TimelineItemAudioContent( - body = "body.mp3", + filename = "body.mp3", + body = null, duration = 1.minutes, mediaSource = MediaSource(url = "url", json = null), mimeType = MimeTypes.Mp3, @@ -350,13 +354,14 @@ class TimelineItemContentMessageFactoryTest { fun `test create VoiceMessageType`() = runTest { val sut = createTimelineItemContentMessageFactory() val result = sut.create( - content = createMessageContent(type = VoiceMessageType("body", MediaSource("url"), null, null)), + content = createMessageContent(type = VoiceMessageType("filename", null, MediaSource("url"), null, null)), senderDisambiguatedDisplayName = "Bob", eventId = AN_EVENT_ID, ) val expected = TimelineItemVoiceContent( + filename = "filename", eventId = AN_EVENT_ID, - body = "body", + body = null, duration = Duration.ZERO, mediaSource = MediaSource(url = "url", json = null), mimeType = MimeTypes.OctetStream, @@ -371,7 +376,8 @@ class TimelineItemContentMessageFactoryTest { val result = sut.create( content = createMessageContent( type = VoiceMessageType( - body = "body.ogg", + filename = "body.ogg", + body = null, source = MediaSource("url"), info = AudioInfo( duration = 1.minutes, @@ -389,7 +395,8 @@ class TimelineItemContentMessageFactoryTest { ) val expected = TimelineItemVoiceContent( eventId = AN_EVENT_ID, - body = "body.ogg", + filename = "body.ogg", + body = null, duration = 1.minutes, mediaSource = MediaSource(url = "url", json = null), mimeType = MimeTypes.Ogg, @@ -408,12 +415,13 @@ class TimelineItemContentMessageFactoryTest { ) ) val result = sut.create( - content = createMessageContent(type = VoiceMessageType("body", MediaSource("url"), null, null)), + content = createMessageContent(type = VoiceMessageType("filename", null, MediaSource("url"), null, null)), senderDisambiguatedDisplayName = "Bob", eventId = AN_EVENT_ID, ) val expected = TimelineItemAudioContent( - body = "body", + filename = "filename", + body = null, duration = Duration.ZERO, mediaSource = MediaSource(url = "url", json = null), mimeType = MimeTypes.OctetStream, @@ -427,14 +435,14 @@ class TimelineItemContentMessageFactoryTest { fun `test create ImageMessageType`() = runTest { val sut = createTimelineItemContentMessageFactory() val result = sut.create( - content = createMessageContent(type = ImageMessageType("body", null, null, MediaSource("url"), null)), + content = createMessageContent(type = ImageMessageType("filename", "body", null, MediaSource("url"), null)), senderDisambiguatedDisplayName = "Bob", eventId = AN_EVENT_ID, ) val expected = TimelineItemImageContent( - body = "body", + filename = "filename", formatted = null, - filename = null, + body = "body", mediaSource = MediaSource(url = "url", json = null), thumbnailSource = null, formattedFileSize = "0 Bytes", @@ -453,13 +461,14 @@ class TimelineItemContentMessageFactoryTest { val sut = createTimelineItemContentStickerFactory() val result = sut.create( content = createStickerContent( - "body", - ImageInfo(32, 32, "image/webp", 8192, null, MediaSource("thumbnail://url"), null), - "url" + filename = "filename", + inImageInfo = ImageInfo(32, 32, "image/webp", 8192, null, MediaSource("thumbnail://url"), null), + inUrl = "url" ) ) val expected = TimelineItemStickerContent( - body = "body", + filename = "filename", + body = null, mediaSource = MediaSource(url = "url", json = null), thumbnailSource = MediaSource(url = "thumbnail://url", json = null), formattedFileSize = "8192 Bytes", @@ -523,12 +532,13 @@ class TimelineItemContentMessageFactoryTest { fun `test create FileMessageType`() = runTest { val sut = createTimelineItemContentMessageFactory() val result = sut.create( - content = createMessageContent(type = FileMessageType("body", MediaSource("url"), null)), + content = createMessageContent(type = FileMessageType("filename", null, MediaSource("url"), null)), senderDisambiguatedDisplayName = "Bob", eventId = AN_EVENT_ID, ) val expected = TimelineItemFileContent( - body = "body", + filename = "filename", + body = null, fileSource = MediaSource(url = "url", json = null), thumbnailSource = null, formattedFileSize = "0 Bytes", @@ -544,7 +554,8 @@ class TimelineItemContentMessageFactoryTest { val result = sut.create( content = createMessageContent( type = FileMessageType( - body = "body.pdf", + filename = "body.pdf", + body = null, source = MediaSource("url"), info = FileInfo( mimetype = MimeTypes.Pdf, @@ -563,7 +574,8 @@ class TimelineItemContentMessageFactoryTest { eventId = AN_EVENT_ID, ) val expected = TimelineItemFileContent( - body = "body.pdf", + filename = "body.pdf", + body = null, fileSource = MediaSource(url = "url", json = null), thumbnailSource = MediaSource("url_thumbnail"), formattedFileSize = "123 Bytes", @@ -749,14 +761,16 @@ class TimelineItemContentMessageFactoryTest { ) private fun createStickerContent( - body: String = "Body", + filename: String = "filename", inImageInfo: ImageInfo, - inUrl: String + inUrl: String, + body: String? = null, ): StickerContent { - return StickerContent( + return aStickerContent( + filename = filename, body = body, info = inImageInfo, - source = aMediaSource(url = inUrl), + mediaSource = aMediaSource(url = inUrl), ) } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt index c501c6cf38..dad26e19a2 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt @@ -207,6 +207,7 @@ class RoomDetailsFlowNode @AssistedInject constructor( val input = MediaViewerNode.Inputs( mediaInfo = MediaInfo( name = navTarget.name, + body = null, mimeType = mimeType, formattedFileSize = "", fileExtension = "" diff --git a/features/userprofile/impl/src/main/kotlin/io/element/android/features/userprofile/impl/UserProfileFlowNode.kt b/features/userprofile/impl/src/main/kotlin/io/element/android/features/userprofile/impl/UserProfileFlowNode.kt index 26b578a39d..fb9c4dfebd 100644 --- a/features/userprofile/impl/src/main/kotlin/io/element/android/features/userprofile/impl/UserProfileFlowNode.kt +++ b/features/userprofile/impl/src/main/kotlin/io/element/android/features/userprofile/impl/UserProfileFlowNode.kt @@ -85,6 +85,7 @@ class UserProfileFlowNode @AssistedInject constructor( val input = MediaViewerNode.Inputs( mediaInfo = MediaInfo( name = navTarget.name, + body = null, mimeType = mimeType, formattedFileSize = "", fileExtension = "" diff --git a/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/DefaultPinnedMessagesBannerFormatter.kt b/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/DefaultPinnedMessagesBannerFormatter.kt index 8fb18e1c3e..ab7a19a9c7 100644 --- a/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/DefaultPinnedMessagesBannerFormatter.kt +++ b/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/DefaultPinnedMessagesBannerFormatter.kt @@ -46,7 +46,8 @@ class DefaultPinnedMessagesBannerFormatter @Inject constructor( return when (val content = event.content) { is MessageContent -> processMessageContents(event, content) is StickerContent -> { - content.body.prefixWith(CommonStrings.common_sticker) + val text = content.body ?: content.filename + text.prefixWith(CommonStrings.common_sticker) } is UnableToDecryptContent -> { sp.getString(CommonStrings.common_waiting_for_decryption_key) @@ -76,25 +77,25 @@ class DefaultPinnedMessagesBannerFormatter @Inject constructor( messageType.toPlainText(permalinkParser) } is VideoMessageType -> { - messageType.body.prefixWith(CommonStrings.common_video) + messageType.bestDescription.prefixWith(CommonStrings.common_video) } is ImageMessageType -> { - messageType.body.prefixWith(CommonStrings.common_image) + messageType.bestDescription.prefixWith(CommonStrings.common_image) } is StickerMessageType -> { - messageType.body.prefixWith(CommonStrings.common_sticker) + messageType.bestDescription.prefixWith(CommonStrings.common_sticker) } is LocationMessageType -> { messageType.body.prefixWith(CommonStrings.common_shared_location) } is FileMessageType -> { - messageType.body.prefixWith(CommonStrings.common_file) + messageType.bestDescription.prefixWith(CommonStrings.common_file) } is AudioMessageType -> { - messageType.body.prefixWith(CommonStrings.common_audio) + messageType.bestDescription.prefixWith(CommonStrings.common_audio) } is VoiceMessageType -> { - messageType.body.prefixWith(CommonStrings.common_voice_message) + messageType.bestDescription.prefixWith(CommonStrings.common_voice_message) } is OtherMessageType -> { messageType.body diff --git a/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLastMessageFormatter.kt b/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLastMessageFormatter.kt index 200b93343d..6b43fc3607 100644 --- a/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLastMessageFormatter.kt +++ b/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLastMessageFormatter.kt @@ -67,7 +67,7 @@ class DefaultRoomLastMessageFormatter @Inject constructor( message.prefixIfNeeded(senderDisambiguatedDisplayName, isDmRoom, isOutgoing) } is StickerContent -> { - val message = sp.getString(CommonStrings.common_sticker) + " (" + content.body + ")" + val message = sp.getString(CommonStrings.common_sticker) + " (" + content.bestDescription + ")" message.prefixIfNeeded(senderDisambiguatedDisplayName, isDmRoom, isOutgoing) } is UnableToDecryptContent -> { diff --git a/libraries/eventformatter/impl/src/test/kotlin/io/element/android/libraries/eventformatter/impl/DefaultPinnedMessagesBannerFormatterTest.kt b/libraries/eventformatter/impl/src/test/kotlin/io/element/android/libraries/eventformatter/impl/DefaultPinnedMessagesBannerFormatterTest.kt index 6247065e37..9da67244d0 100644 --- a/libraries/eventformatter/impl/src/test/kotlin/io/element/android/libraries/eventformatter/impl/DefaultPinnedMessagesBannerFormatterTest.kt +++ b/libraries/eventformatter/impl/src/test/kotlin/io/element/android/libraries/eventformatter/impl/DefaultPinnedMessagesBannerFormatterTest.kt @@ -32,7 +32,6 @@ import io.element.android.libraries.matrix.api.timeline.item.event.OtherState import io.element.android.libraries.matrix.api.timeline.item.event.RedactedContent import io.element.android.libraries.matrix.api.timeline.item.event.RoomMembershipContent import io.element.android.libraries.matrix.api.timeline.item.event.StateContent -import io.element.android.libraries.matrix.api.timeline.item.event.StickerContent import io.element.android.libraries.matrix.api.timeline.item.event.StickerMessageType import io.element.android.libraries.matrix.api.timeline.item.event.TextMessageType import io.element.android.libraries.matrix.api.timeline.item.event.UnableToDecryptContent @@ -46,6 +45,7 @@ import io.element.android.libraries.matrix.test.permalink.FakePermalinkParser import io.element.android.libraries.matrix.test.timeline.aPollContent import io.element.android.libraries.matrix.test.timeline.aProfileChangeMessageContent import io.element.android.libraries.matrix.test.timeline.aProfileTimelineDetails +import io.element.android.libraries.matrix.test.timeline.aStickerContent import io.element.android.libraries.matrix.test.timeline.anEventTimelineItem import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.services.toolbox.impl.strings.AndroidStringProvider @@ -91,7 +91,7 @@ class DefaultPinnedMessagesBannerFormatterTest { fun `Sticker content`() { val body = "a sticker body" val info = ImageInfo(null, null, null, null, null, null, null) - val message = createRoomEvent(false, null, StickerContent(body, info, aMediaSource(url = "url"))) + val message = createRoomEvent(false, null, aStickerContent(body, info, aMediaSource(url = "url"))) val result = formatter.format(message) val expectedBody = "Sticker: a sticker body" assertThat(result.toString()).isEqualTo(expectedBody) @@ -135,11 +135,11 @@ class DefaultPinnedMessagesBannerFormatterTest { val sharedContentMessagesTypes = arrayOf( TextMessageType(body, null), VideoMessageType(body, null, null, MediaSource("url"), null), - AudioMessageType(body, MediaSource("url"), null), - VoiceMessageType(body, MediaSource("url"), null, null), + AudioMessageType(body, null, MediaSource("url"), null), + VoiceMessageType(body, null, MediaSource("url"), null, null), ImageMessageType(body, null, null, MediaSource("url"), null), - StickerMessageType(body, MediaSource("url"), null), - FileMessageType(body, MediaSource("url"), null), + StickerMessageType(body, null, MediaSource("url"), null), + FileMessageType(body, null, MediaSource("url"), null), LocationMessageType(body, "geo:1,2", null), NoticeMessageType(body, null), EmoteMessageType(body, null), diff --git a/libraries/eventformatter/impl/src/test/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLastMessageFormatterTest.kt b/libraries/eventformatter/impl/src/test/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLastMessageFormatterTest.kt index db3377c5e8..ead6064df7 100644 --- a/libraries/eventformatter/impl/src/test/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLastMessageFormatterTest.kt +++ b/libraries/eventformatter/impl/src/test/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLastMessageFormatterTest.kt @@ -32,7 +32,6 @@ import io.element.android.libraries.matrix.api.timeline.item.event.OtherState import io.element.android.libraries.matrix.api.timeline.item.event.RedactedContent import io.element.android.libraries.matrix.api.timeline.item.event.RoomMembershipContent import io.element.android.libraries.matrix.api.timeline.item.event.StateContent -import io.element.android.libraries.matrix.api.timeline.item.event.StickerContent import io.element.android.libraries.matrix.api.timeline.item.event.StickerMessageType import io.element.android.libraries.matrix.api.timeline.item.event.TextMessageType import io.element.android.libraries.matrix.api.timeline.item.event.UnableToDecryptContent @@ -46,6 +45,7 @@ import io.element.android.libraries.matrix.test.permalink.FakePermalinkParser import io.element.android.libraries.matrix.test.timeline.aPollContent import io.element.android.libraries.matrix.test.timeline.aProfileChangeMessageContent import io.element.android.libraries.matrix.test.timeline.aProfileTimelineDetails +import io.element.android.libraries.matrix.test.timeline.aStickerContent import io.element.android.libraries.matrix.test.timeline.anEventTimelineItem import io.element.android.services.toolbox.impl.strings.AndroidStringProvider import org.junit.Before @@ -98,7 +98,7 @@ class DefaultRoomLastMessageFormatterTest { fun `Sticker content`() { val body = "a sticker body" val info = ImageInfo(null, null, null, null, null, null, null) - val message = createRoomEvent(false, null, StickerContent(body, info, aMediaSource(url = "url"))) + val message = createRoomEvent(false, null, aStickerContent(body, info, aMediaSource(url = "url"))) val result = formatter.format(message, false) val expectedBody = someoneElseId.toString() + ": Sticker (a sticker body)" assertThat(result.toString()).isEqualTo(expectedBody) @@ -179,11 +179,11 @@ class DefaultRoomLastMessageFormatterTest { val sharedContentMessagesTypes = arrayOf( TextMessageType(body, null), VideoMessageType(body, null, null, MediaSource("url"), null), - AudioMessageType(body, MediaSource("url"), null), - VoiceMessageType(body, MediaSource("url"), null, null), + AudioMessageType(body, null, MediaSource("url"), null), + VoiceMessageType(body, null, MediaSource("url"), null, null), ImageMessageType(body, null, null, MediaSource("url"), null), - StickerMessageType(body, MediaSource("url"), null), - FileMessageType(body, MediaSource("url"), null), + StickerMessageType(body, null, MediaSource("url"), null), + FileMessageType(body, null, MediaSource("url"), null), LocationMessageType(body, "geo:1,2", null), NoticeMessageType(body, null), EmoteMessageType(body, null), diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventContent.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventContent.kt index fc65d30955..ebe8a10bdc 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventContent.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventContent.kt @@ -30,10 +30,14 @@ data class MessageContent( data object RedactedContent : EventContent data class StickerContent( - val body: String, + val filename: String, + val body: String?, val info: ImageInfo, val source: MediaSource, -) : EventContent +) : EventContent { + val bestDescription: String + get() = body ?: filename +} data class PollContent( val question: String, diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/MessageType.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/MessageType.kt index b398e056bc..9775a6802d 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/MessageType.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/MessageType.kt @@ -18,24 +18,35 @@ import io.element.android.libraries.matrix.api.media.VideoInfo @Immutable sealed interface MessageType +@Immutable +sealed interface MessageTypeWithAttachment : MessageType { + val filename: String + val body: String? + + val bestDescription: String + get() = body ?: filename +} + data class EmoteMessageType( val body: String, val formatted: FormattedBody? ) : MessageType data class ImageMessageType( - val body: String, + override val filename: String, + override val body: String?, val formatted: FormattedBody?, - val filename: String?, val source: MediaSource, val info: ImageInfo? -) : MessageType +) : MessageTypeWithAttachment +// FIXME This is never used in production code. data class StickerMessageType( - val body: String, + override val filename: String, + override val body: String?, val source: MediaSource, val info: ImageInfo? -) : MessageType +) : MessageTypeWithAttachment data class LocationMessageType( val body: String, @@ -44,31 +55,34 @@ data class LocationMessageType( ) : MessageType data class AudioMessageType( - val body: String, + override val filename: String, + override val body: String?, val source: MediaSource, val info: AudioInfo?, -) : MessageType +) : MessageTypeWithAttachment data class VoiceMessageType( - val body: String, + override val filename: String, + override val body: String?, val source: MediaSource, val info: AudioInfo?, val details: AudioDetails?, -) : MessageType +) : MessageTypeWithAttachment data class VideoMessageType( - val body: String, + override val filename: String, + override val body: String?, val formatted: FormattedBody?, - val filename: String?, val source: MediaSource, val info: VideoInfo? -) : MessageType +) : MessageTypeWithAttachment data class FileMessageType( - val body: String, + override val filename: String, + override val body: String?, val source: MediaSource, val info: FileInfo? -) : MessageType +) : MessageTypeWithAttachment data class NoticeMessageType( val body: String, 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 3e67bd8595..a9543046eb 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 @@ -50,14 +50,16 @@ class EventMessageMapper { when (type.content.voice) { null -> { AudioMessageType( - body = type.content.body, + filename = type.content.filename ?: type.content.body, + body = type.content.body.takeIf { type.content.filename != null }, source = type.content.source.map(), info = type.content.info?.map(), ) } else -> { VoiceMessageType( - body = type.content.body, + filename = type.content.filename ?: type.content.body, + body = type.content.body.takeIf { type.content.filename != null }, source = type.content.source.map(), info = type.content.info?.map(), details = type.content.audio?.map(), @@ -66,10 +68,21 @@ class EventMessageMapper { } } is RustMessageType.File -> { - FileMessageType(type.content.body, type.content.source.map(), type.content.info?.map()) + FileMessageType( + filename = type.content.filename ?: type.content.body, + body = type.content.body.takeIf { type.content.filename != null }, + source = type.content.source.map(), + info = type.content.info?.map(), + ) } is RustMessageType.Image -> { - ImageMessageType(type.content.body, type.content.formatted?.map(), type.content.filename, type.content.source.map(), type.content.info?.map()) + ImageMessageType( + filename = type.content.filename ?: type.content.body, + body = type.content.body.takeIf { type.content.filename != null }, + formatted = type.content.formatted?.map(), + source = type.content.source.map(), + info = type.content.info?.map(), + ) } is RustMessageType.Notice -> { NoticeMessageType(type.content.body, type.content.formatted?.map()) @@ -81,7 +94,13 @@ class EventMessageMapper { EmoteMessageType(type.content.body, type.content.formatted?.map()) } is RustMessageType.Video -> { - VideoMessageType(type.content.body, type.content.formatted?.map(), type.content.filename, type.content.source.map(), type.content.info?.map()) + VideoMessageType( + filename = type.content.filename ?: type.content.body, + body = type.content.body.takeIf { type.content.filename != null }, + formatted = type.content.formatted?.map(), + source = type.content.source.map(), + info = type.content.info?.map(), + ) } is RustMessageType.Location -> { LocationMessageType(type.content.body, type.content.geoUri, type.content.description) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/TimelineEventContentMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/TimelineEventContentMapper.kt index ed075c508f..3c0ec16f65 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/TimelineEventContentMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/TimelineEventContentMapper.kt @@ -84,7 +84,8 @@ class TimelineEventContentMapper( } is TimelineItemContent.Sticker -> { StickerContent( - body = it.body, + filename = it.body, + body = null, info = it.info.map(), source = it.source.map(), ) diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/TimelineFixture.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/TimelineFixture.kt index b8bc798b28..85833f183a 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/TimelineFixture.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/TimelineFixture.kt @@ -10,6 +10,8 @@ package io.element.android.libraries.matrix.test.timeline 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.UserId +import io.element.android.libraries.matrix.api.media.ImageInfo +import io.element.android.libraries.matrix.api.media.MediaSource import io.element.android.libraries.matrix.api.poll.PollAnswer import io.element.android.libraries.matrix.api.poll.PollKind import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo @@ -25,6 +27,7 @@ import io.element.android.libraries.matrix.api.timeline.item.event.PollContent import io.element.android.libraries.matrix.api.timeline.item.event.ProfileChangeContent import io.element.android.libraries.matrix.api.timeline.item.event.ProfileTimelineDetails import io.element.android.libraries.matrix.api.timeline.item.event.Receipt +import io.element.android.libraries.matrix.api.timeline.item.event.StickerContent import io.element.android.libraries.matrix.api.timeline.item.event.TextMessageType import io.element.android.libraries.matrix.test.AN_EVENT_ID import io.element.android.libraries.matrix.test.A_USER_ID @@ -110,6 +113,18 @@ fun aMessageContent( type = messageType ) +fun aStickerContent( + filename: String = "filename", + info: ImageInfo, + mediaSource: MediaSource, + body: String? = null, +) = StickerContent( + filename = filename, + body = body, + info = info, + source = mediaSource, +) + fun aTimelineItemDebugInfo( model: String = "Rust(Model())", originalJson: String? = null, diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/MediaRequestData.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/MediaRequestData.kt index ac073d3840..ff67e1d2b2 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/MediaRequestData.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/MediaRequestData.kt @@ -26,7 +26,13 @@ data class MediaRequestData( ) { sealed interface Kind { data object Content : Kind - data class File(val body: String?, val mimeType: String) : Kind + + data class File( + val fileName: String, + val body: String?, + val mimeType: String, + ) : Kind + data class Thumbnail(val width: Long, val height: Long) : Kind { constructor(size: Long) : this(size, size) } diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/messages/reply/InReplyToDetailsProvider.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/messages/reply/InReplyToDetailsProvider.kt index 301966566a..587af7c1d0 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/messages/reply/InReplyToDetailsProvider.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/messages/reply/InReplyToDetailsProvider.kt @@ -49,11 +49,11 @@ open class InReplyToDetailsProvider : PreviewParameterProvider ), aMessageContent( body = "Audio", - type = AudioMessageType("Audio", MediaSource("url"), null), + type = AudioMessageType("Audio", null, MediaSource("url"), null), ), aMessageContent( body = "Voice", - type = VoiceMessageType("Voice", MediaSource("url"), null, null), + type = VoiceMessageType("Voice", null, MediaSource("url"), null, null), ), aMessageContent( body = "Image", @@ -61,11 +61,11 @@ open class InReplyToDetailsProvider : PreviewParameterProvider ), aMessageContent( body = "Sticker", - type = StickerMessageType("Image", MediaSource("url"), null), + type = StickerMessageType("Image", null, MediaSource("url"), null), ), aMessageContent( body = "File", - type = FileMessageType("File", MediaSource("url"), null), + type = FileMessageType("File", null, MediaSource("url"), null), ), aMessageContent( body = "Location", 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 26e7df9631..ed828aedbe 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 @@ -75,9 +75,9 @@ class InReplyToMetadataKtTest { anInReplyToDetailsReady( eventContent = aMessageContent( messageType = ImageMessageType( - body = "body", + filename = "filename", + body = null, formatted = null, - filename = null, source = aMediaSource(), info = anImageInfo(), ) @@ -104,6 +104,7 @@ class InReplyToMetadataKtTest { moleculeFlow(RecompositionMode.Immediate) { anInReplyToDetailsReady( eventContent = StickerContent( + filename = "filename", body = "body", info = anImageInfo(), source = aMediaSource(url = "url") @@ -131,9 +132,9 @@ class InReplyToMetadataKtTest { anInReplyToDetailsReady( eventContent = aMessageContent( messageType = VideoMessageType( - body = "body", + filename = "filename", + body = null, formatted = null, - filename = null, source = aMediaSource(), info = aVideoInfo(), ) @@ -161,6 +162,7 @@ class InReplyToMetadataKtTest { anInReplyToDetailsReady( eventContent = aMessageContent( messageType = FileMessageType( + filename = "filename", body = "body", source = aMediaSource(), info = FileInfo( @@ -194,6 +196,7 @@ class InReplyToMetadataKtTest { anInReplyToDetailsReady( eventContent = aMessageContent( messageType = AudioMessageType( + filename = "filename", body = "body", source = aMediaSource(), info = AudioInfo( @@ -256,6 +259,7 @@ class InReplyToMetadataKtTest { anInReplyToDetailsReady( eventContent = aMessageContent( messageType = VoiceMessageType( + filename = "filename", body = "body", source = aMediaSource(), info = null, diff --git a/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/local/MediaInfo.kt b/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/local/MediaInfo.kt index c836a25a85..f485b41b92 100644 --- a/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/local/MediaInfo.kt +++ b/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/local/MediaInfo.kt @@ -14,42 +14,48 @@ import kotlinx.parcelize.Parcelize @Parcelize data class MediaInfo( val name: String, + val body: String?, val mimeType: String, val formattedFileSize: String, val fileExtension: String, ) : Parcelable fun anImageMediaInfo(): MediaInfo = MediaInfo( - "an image file.jpg", - MimeTypes.Jpeg, - "4MB", - "jpg" + name = "an image file.jpg", + body = null, + mimeType = MimeTypes.Jpeg, + formattedFileSize = "4MB", + fileExtension = "jpg", ) fun aVideoMediaInfo(): MediaInfo = MediaInfo( - "a video file.mp4", - MimeTypes.Mp4, - "14MB", - "mp4" + name = "a video file.mp4", + body = null, + mimeType = MimeTypes.Mp4, + formattedFileSize = "14MB", + fileExtension = "mp4", ) fun aPdfMediaInfo(): MediaInfo = MediaInfo( - "a pdf file.pdf", - MimeTypes.Pdf, - "23MB", - "pdf" + name = "a pdf file.pdf", + body = null, + mimeType = MimeTypes.Pdf, + formattedFileSize = "23MB", + fileExtension = "pdf", ) fun anApkMediaInfo(): MediaInfo = MediaInfo( - "an apk file.apk", - MimeTypes.Apk, - "50MB", - "apk" + name = "an apk file.apk", + body = null, + mimeType = MimeTypes.Apk, + formattedFileSize = "50MB", + fileExtension = "apk", ) fun anAudioMediaInfo(): MediaInfo = MediaInfo( - "an audio file.mp3", - MimeTypes.Mp3, - "7MB", - "mp3" + name = "an audio file.mp3", + body = null, + mimeType = MimeTypes.Mp3, + formattedFileSize = "7MB", + fileExtension = "mp3", ) diff --git a/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/viewer/MediaViewerView.kt b/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/viewer/MediaViewerView.kt index f76bf51073..f0aecbbf4b 100644 --- a/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/viewer/MediaViewerView.kt +++ b/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/viewer/MediaViewerView.kt @@ -322,7 +322,7 @@ private fun ThumbnailView( if (isVisible) { val mediaRequestData = MediaRequestData( source = thumbnailSource, - kind = MediaRequestData.Kind.File(mediaInfo.name, mediaInfo.mimeType) + kind = MediaRequestData.Kind.File(mediaInfo.name, mediaInfo.body, mediaInfo.mimeType) ) AsyncImage( modifier = Modifier.fillMaxSize(), diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaFactory.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaFactory.kt index d8e5af8233..4a26ee6818 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaFactory.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaFactory.kt @@ -32,21 +32,36 @@ class AndroidLocalMediaFactory @Inject constructor( private val fileSizeFormatter: FileSizeFormatter, private val fileExtensionExtractor: FileExtensionExtractor, ) : LocalMediaFactory { - override fun createFromMediaFile(mediaFile: MediaFile, mediaInfo: MediaInfo): LocalMedia { - val uri = mediaFile.toFile().toUri() - return createFromUri( - uri = uri, - mimeType = mediaInfo.mimeType, - name = mediaInfo.name, - formattedFileSize = mediaInfo.formattedFileSize, - ) - } + override fun createFromMediaFile( + mediaFile: MediaFile, + mediaInfo: MediaInfo, + ): LocalMedia = createFromUri( + uri = mediaFile.toFile().toUri(), + mimeType = mediaInfo.mimeType, + name = mediaInfo.name, + body = mediaInfo.body, + formattedFileSize = mediaInfo.formattedFileSize, + ) override fun createFromUri( uri: Uri, mimeType: String?, name: String?, formattedFileSize: String? + ): LocalMedia = createFromUri( + uri = uri, + mimeType = mimeType, + name = name, + body = null, + formattedFileSize = formattedFileSize, + ) + + private fun createFromUri( + uri: Uri, + mimeType: String?, + name: String?, + body: String?, + formattedFileSize: String? ): LocalMedia { val resolvedMimeType = mimeType ?: context.getMimeType(uri) ?: MimeTypes.OctetStream val fileName = name ?: context.getFileName(uri) ?: "" @@ -57,6 +72,7 @@ class AndroidLocalMediaFactory @Inject constructor( info = MediaInfo( mimeType = resolvedMimeType, name = fileName, + body = body, formattedFileSize = fileSize, fileExtension = fileExtension ) diff --git a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaFactoryTest.kt b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaFactoryTest.kt index af0a1e9b88..28d1aff9fc 100644 --- a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaFactoryTest.kt +++ b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaFactoryTest.kt @@ -30,6 +30,7 @@ class AndroidLocalMediaFactoryTest { assertThat(result.info).isEqualTo( MediaInfo( name = "an image file.jpg", + body = null, mimeType = MimeTypes.Jpeg, formattedFileSize = "4MB", fileExtension = "jpg", diff --git a/libraries/mediaviewer/test/src/main/kotlin/io/element/android/libraries/mediaviewer/test/FakeLocalMediaFactory.kt b/libraries/mediaviewer/test/src/main/kotlin/io/element/android/libraries/mediaviewer/test/FakeLocalMediaFactory.kt index cc8b29da2b..7b0cdfc5b2 100644 --- a/libraries/mediaviewer/test/src/main/kotlin/io/element/android/libraries/mediaviewer/test/FakeLocalMediaFactory.kt +++ b/libraries/mediaviewer/test/src/main/kotlin/io/element/android/libraries/mediaviewer/test/FakeLocalMediaFactory.kt @@ -33,6 +33,7 @@ class FakeLocalMediaFactory( val safeName = name ?: fallbackName val mediaInfo = MediaInfo( name = safeName, + body = null, mimeType = mimeType ?: fallbackMimeType, formattedFileSize = formattedFileSize ?: fallbackFileSize, fileExtension = fileExtensionExtractor.extractFromName(safeName) diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotifiableEventResolver.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotifiableEventResolver.kt index 283b28ab31..ec77d88335 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotifiableEventResolver.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotifiableEventResolver.kt @@ -263,15 +263,15 @@ class DefaultNotifiableEventResolver @Inject constructor( senderDisambiguatedDisplayName: String, ): String { return when (val messageType = content.messageType) { - is AudioMessageType -> messageType.body + is AudioMessageType -> messageType.bestDescription is VoiceMessageType -> stringProvider.getString(CommonStrings.common_voice_message) is EmoteMessageType -> "* $senderDisambiguatedDisplayName ${messageType.body}" - is FileMessageType -> messageType.body - is ImageMessageType -> messageType.body - is StickerMessageType -> messageType.body + is FileMessageType -> messageType.bestDescription + is ImageMessageType -> messageType.bestDescription + is StickerMessageType -> messageType.bestDescription is NoticeMessageType -> messageType.body is TextMessageType -> messageType.toPlainText(permalinkParser = permalinkParser) - is VideoMessageType -> messageType.body + is VideoMessageType -> messageType.bestDescription is LocationMessageType -> messageType.body is OtherMessageType -> messageType.body } diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotifiableEventResolverTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotifiableEventResolverTest.kt index de74d1d7a9..50088844b4 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotifiableEventResolverTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotifiableEventResolverTest.kt @@ -185,7 +185,7 @@ class DefaultNotifiableEventResolverTest { aNotificationData( content = NotificationContent.MessageLike.RoomMessage( senderId = A_USER_ID_2, - messageType = AudioMessageType(body = "Audio", MediaSource("url"), null) + messageType = AudioMessageType("Audio", null, MediaSource("url"), null) ), ) ) @@ -204,7 +204,7 @@ class DefaultNotifiableEventResolverTest { aNotificationData( content = NotificationContent.MessageLike.RoomMessage( senderId = A_USER_ID_2, - messageType = VideoMessageType(body = "Video", null, null, MediaSource("url"), null) + messageType = VideoMessageType("Video", null, null, MediaSource("url"), null) ), ) ) @@ -223,7 +223,7 @@ class DefaultNotifiableEventResolverTest { aNotificationData( content = NotificationContent.MessageLike.RoomMessage( senderId = A_USER_ID_2, - messageType = VoiceMessageType(body = "Voice", MediaSource("url"), null, null) + messageType = VoiceMessageType("Voice", null, MediaSource("url"), null, null) ), ) ) @@ -261,7 +261,7 @@ class DefaultNotifiableEventResolverTest { aNotificationData( content = NotificationContent.MessageLike.RoomMessage( senderId = A_USER_ID_2, - messageType = StickerMessageType("Sticker", MediaSource("url"), null), + messageType = StickerMessageType("Sticker", null, MediaSource("url"), null), ), ) ) @@ -280,7 +280,7 @@ class DefaultNotifiableEventResolverTest { aNotificationData( content = NotificationContent.MessageLike.RoomMessage( senderId = A_USER_ID_2, - messageType = FileMessageType("File", MediaSource("url"), null), + messageType = FileMessageType("File", null, MediaSource("url"), null), ), ) )