From 4236b697050253b29aaca3d7163d8afd5f85e8c8 Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 5 May 2023 19:47:10 +0200 Subject: [PATCH] Introduce MatrixMediaSource --- .../event/TimelineItemContentView.kt | 5 ++ .../components/event/TimelineItemImageView.kt | 17 ++-- .../components/event/TimelineItemVideoView.kt | 81 +++++++++++++++++++ .../TimelineItemContentMessageFactory.kt | 30 +++++-- .../model/event/TimelineItemImageContent.kt | 8 +- .../event/TimelineItemImageContentProvider.kt | 7 +- .../model/event/TimelineItemVideoContent.kt | 33 ++++++++ .../event/TimelineItemVideoContentProvider.kt | 42 ++++++++++ .../DefaultRoomLastMessageFormatterTests.kt | 10 ++- .../libraries/matrix/api/media/FileInfo.kt | 2 +- .../libraries/matrix/api/media/ImageInfo.kt | 2 +- .../matrix/api/media/MatrixMediaLoader.kt | 10 +-- .../matrix/api/media/MatrixMediaSource.kt | 25 ++++++ .../libraries/matrix/api/media/VideoInfo.kt | 2 +- .../api/timeline/item/event/EventContent.kt | 13 +-- .../libraries/matrix/impl/media/FileInfo.kt | 4 +- .../libraries/matrix/impl/media/ImageInfo.kt | 2 +- .../matrix/impl/media/MediaSource.kt | 5 +- .../matrix/impl/media/RustMediaLoader.kt | 20 ++--- .../libraries/matrix/impl/media/VideoInfo.kt | 2 +- .../timeline/item/event/EventMessageMapper.kt | 9 +-- .../item/event/TimelineEventContentMapper.kt | 4 +- .../matrix/test/media/FakeMediaLoader.kt | 13 +-- .../matrix/ui/media/AvatatarDataExt.kt | 13 ++- .../matrix/ui/media/CoilMediaFetcher.kt | 12 +-- .../matrix/ui/media/MediaRequestData.kt | 4 +- .../matrix/ui/media/MediaRequestDataKeyer.kt | 7 +- 27 files changed, 298 insertions(+), 84 deletions(-) create mode 100644 features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemVideoView.kt create mode 100644 features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemVideoContent.kt create mode 100644 features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemVideoContentProvider.kt create mode 100644 libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/media/MatrixMediaSource.kt diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemContentView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemContentView.kt index 5171c92c40..6ef3965c5c 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemContentView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemContentView.kt @@ -27,6 +27,7 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRedactedContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextBasedContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemUnknownContent +import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVideoContent private fun Modifier.defaultContentPadding(): Modifier = padding( horizontal = 12.dp, vertical = 6.dp @@ -64,5 +65,9 @@ fun TimelineItemEventContentView( content = content, modifier = modifier, ) + is TimelineItemVideoContent -> TimelineItemVideoView( + content = content, + modifier = modifier + ) } } 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 fe0e4abba0..7fa2076574 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 @@ -18,39 +18,38 @@ package io.element.android.features.messages.impl.timeline.components.event import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.aspectRatio -import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.heightIn import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.unit.dp import io.element.android.features.messages.impl.timeline.model.event.TimelineItemImageContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemImageContentProvider import io.element.android.libraries.designsystem.components.blurhash.BlurHashAsyncImage import io.element.android.libraries.designsystem.preview.ElementPreviewDark import io.element.android.libraries.designsystem.preview.ElementPreviewLight +import io.element.android.libraries.matrix.ui.media.MediaRequestData +import kotlin.math.min @Composable fun TimelineItemImageView( content: TimelineItemImageContent, modifier: Modifier = Modifier, ) { - val widthPercent = if (content.aspectRatio > 1f) { - 1f - } else { - 0.7f - } + val maxHeight = min(300, content.height ?: Int.MAX_VALUE) Box( modifier = modifier - .fillMaxWidth(widthPercent) + .heightIn(max = maxHeight.dp) .aspectRatio(content.aspectRatio), contentAlignment = Alignment.Center, ) { BlurHashAsyncImage( blurHash = content.blurhash, - model = content.mediaRequestData, - contentScale = ContentScale.Crop, + model = MediaRequestData(content.mediaSource, MediaRequestData.Kind.Content), + contentScale = ContentScale.Fit, ) } } 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 new file mode 100644 index 0000000000..5dec3717c8 --- /dev/null +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemVideoView.kt @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.messages.impl.timeline.components.event + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.heightIn +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.unit.dp +import io.element.android.features.messages.impl.timeline.model.event.TimelineItemImageContent +import io.element.android.features.messages.impl.timeline.model.event.TimelineItemImageContentProvider +import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVideoContent +import io.element.android.libraries.designsystem.components.blurhash.BlurHashAsyncImage +import io.element.android.libraries.designsystem.preview.ElementPreviewDark +import io.element.android.libraries.designsystem.preview.ElementPreviewLight +import io.element.android.libraries.matrix.ui.media.MediaRequestData +import kotlin.math.min + +@Composable +fun TimelineItemVideoView( + content: TimelineItemVideoContent, + modifier: Modifier = Modifier, +) { + val maxHeight = min(300, content.height ?: Int.MAX_VALUE) + Box( + modifier = modifier + .heightIn(max = maxHeight.dp) + .aspectRatio(content.aspectRatio), + contentAlignment = Alignment.Center, + ) { + BlurHashAsyncImage( + blurHash = content.blurhash, + model = MediaRequestData(content.thumbnailSource, MediaRequestData.Kind.Content), + contentScale = ContentScale.Fit, + ) + Image( + painterResource(id = androidx.media3.ui.R.drawable.exo_ic_play_circle_filled), + contentDescription = null, + colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onBackground) + ) + + } +} + +@Preview +@Composable +internal fun TimelineItemVideoViewLightPreview(@PreviewParameter(TimelineItemImageContentProvider::class) content: TimelineItemImageContent) = + ElementPreviewLight { ContentToPreview(content) } + +@Preview +@Composable +internal fun TimelineItemVideoViewDarkPreview(@PreviewParameter(TimelineItemImageContentProvider::class) content: TimelineItemImageContent) = + ElementPreviewDark { ContentToPreview(content) } + +@Composable +private fun ContentToPreview(content: TimelineItemImageContent) { + TimelineItemImageView(content) +} 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 57d6489592..857049d1be 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 @@ -22,13 +22,14 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt import io.element.android.features.messages.impl.timeline.model.event.TimelineItemNoticeContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemUnknownContent +import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVideoContent import io.element.android.features.messages.impl.timeline.util.toHtmlDocument import io.element.android.libraries.matrix.api.timeline.item.event.EmoteMessageType import io.element.android.libraries.matrix.api.timeline.item.event.ImageMessageType import io.element.android.libraries.matrix.api.timeline.item.event.MessageContent import io.element.android.libraries.matrix.api.timeline.item.event.NoticeMessageType import io.element.android.libraries.matrix.api.timeline.item.event.TextMessageType -import io.element.android.libraries.matrix.ui.media.MediaRequestData +import io.element.android.libraries.matrix.api.timeline.item.event.VideoMessageType import javax.inject.Inject class TimelineItemContentMessageFactory @Inject constructor() { @@ -49,10 +50,29 @@ class TimelineItemContentMessageFactory @Inject constructor() { } TimelineItemImageContent( body = messageType.body, - mediaRequestData = MediaRequestData( - url = messageType.url, - kind = MediaRequestData.Kind.Content - ), + height = messageType.info?.height?.toInt(), + width = messageType.info?.width?.toInt(), + mediaSource = messageType.source, + blurhash = messageType.info?.blurhash, + aspectRatio = aspectRatio + ) + } + is VideoMessageType -> { + val height = messageType.info?.height?.toFloat() + val width = messageType.info?.width?.toFloat() + val aspectRatio = if (height != null && width != null) { + width / height + } else { + 0.7f + } + TimelineItemVideoContent( + body = messageType.body, + thumbnailSource = messageType.info?.thumbnailSource, + videoSource = messageType.source, + mimetype = messageType.info?.mimetype, + width = messageType.info?.width?.toInt(), + height = messageType.info?.height?.toInt(), + duration = messageType.info?.duration ?: 0L, blurhash = messageType.info?.blurhash, aspectRatio = aspectRatio ) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemImageContent.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemImageContent.kt index d6069e2020..9f70d16de4 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemImageContent.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemImageContent.kt @@ -16,13 +16,15 @@ package io.element.android.features.messages.impl.timeline.model.event -import io.element.android.libraries.matrix.ui.media.MediaRequestData +import io.element.android.libraries.matrix.api.media.MatrixMediaSource data class TimelineItemImageContent( val body: String, - val mediaRequestData: MediaRequestData, + val mediaSource: MatrixMediaSource, val blurhash: String?, + val width: Int?, + val height: Int?, val aspectRatio: Float -) : TimelineItemEventContent{ +) : TimelineItemEventContent { override val type: String = "TimelineItemImageContent" } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemImageContentProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemImageContentProvider.kt index e1fb9d06e6..a43551cd2d 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemImageContentProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemImageContentProvider.kt @@ -17,6 +17,7 @@ package io.element.android.features.messages.impl.timeline.model.event import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import io.element.android.libraries.matrix.api.media.MatrixMediaSource import io.element.android.libraries.matrix.ui.media.MediaRequestData open class TimelineItemImageContentProvider : PreviewParameterProvider { @@ -30,7 +31,9 @@ open class TimelineItemImageContentProvider : PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf( + aTimelineItemVideoContent(), + aTimelineItemVideoContent().copy(aspectRatio = 1.0f), + aTimelineItemVideoContent().copy(aspectRatio = 1.5f), + ) +} + +fun aTimelineItemVideoContent() = TimelineItemVideoContent( + body = "a video", + thumbnailSource = MatrixMediaSource(url = ""), + blurhash = "TQF5:I_NtRE4kXt7Z#MwkCIARPjr", + aspectRatio = 0.5f, + duration = 0, + videoSource = MatrixMediaSource(""), + height = null, + width = null, + mimetype = null +) diff --git a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/DefaultRoomLastMessageFormatterTests.kt b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/DefaultRoomLastMessageFormatterTests.kt index d4c2ca7dd2..641753db76 100644 --- a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/DefaultRoomLastMessageFormatterTests.kt +++ b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/DefaultRoomLastMessageFormatterTests.kt @@ -21,6 +21,7 @@ import androidx.compose.ui.text.AnnotatedString import com.google.common.truth.Truth 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.MatrixMediaSource 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.EventContent @@ -145,12 +146,13 @@ class DefaultRoomLastMessageFormatterTests { fun createMessageContent(type: MessageType): MessageContent { return MessageContent(body, null, false, type) } + val sharedContentMessagesTypes = arrayOf( TextMessageType(body, null), - VideoMessageType(body, "url", null), - AudioMessageType(body, "url", null), - ImageMessageType(body, "url", null), - FileMessageType(body, "url", null), + VideoMessageType(body, MatrixMediaSource("url"), null), + AudioMessageType(body, MatrixMediaSource("url"), null), + ImageMessageType(body, MatrixMediaSource("url"), null), + FileMessageType(body, MatrixMediaSource("url"), null), NoticeMessageType(body, null), EmoteMessageType(body, null), ) diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/media/FileInfo.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/media/FileInfo.kt index fc591a5078..b989a621fd 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/media/FileInfo.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/media/FileInfo.kt @@ -20,5 +20,5 @@ data class FileInfo( val mimetype: String?, val size: Long?, val thumbnailInfo: ThumbnailInfo?, - val thumbnailUrl: String? + val thumbnailSource: MatrixMediaSource? ) diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/media/ImageInfo.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/media/ImageInfo.kt index 540627470e..a1361082c8 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/media/ImageInfo.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/media/ImageInfo.kt @@ -22,6 +22,6 @@ data class ImageInfo( val mimetype: String?, val size: Long?, val thumbnailInfo: ThumbnailInfo?, - val thumbnailUrl: String?, + val thumbnailSource: MatrixMediaSource?, val blurhash: String? ) diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/media/MatrixMediaLoader.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/media/MatrixMediaLoader.kt index 4c068ab744..43ab98c3b9 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/media/MatrixMediaLoader.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/media/MatrixMediaLoader.kt @@ -16,14 +16,14 @@ package io.element.android.libraries.matrix.api.media -import java.nio.file.Path +import android.net.Uri interface MatrixMediaLoader { /** * @param url to fetch the content for. * @return a [Result] of ByteArray. It contains the binary data for the media. */ - suspend fun loadMediaContent(url: String): Result + suspend fun loadMediaContent(source: MatrixMediaSource): Result /** * @param url to fetch the data for. @@ -31,12 +31,12 @@ interface MatrixMediaLoader { * @param height: the desired height for rescaling the media as thumbnail * @return a [Result] of ByteArray. It contains the binary data for the media. */ - suspend fun loadMediaThumbnail(url: String, width: Long, height: Long): Result + suspend fun loadMediaThumbnail(source: MatrixMediaSource, width: Long, height: Long): Result /** * @param url to fetch the data for. * @param mimeType: optional mime type - * @return a [Result] of [Path]. It's the path to the downloaded file. + * @return a [Result] of [Uri]. It's the uri of the downloaded file. */ - suspend fun loadMediaFile(url: String, mimeType: String?): Result + suspend fun loadMediaFile(source: MatrixMediaSource, mimeType: String?): Result } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/media/MatrixMediaSource.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/media/MatrixMediaSource.kt new file mode 100644 index 0000000000..22092c1afd --- /dev/null +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/media/MatrixMediaSource.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.matrix.api.media + +import android.os.Parcelable +import kotlinx.parcelize.Parcelize + +@Parcelize +data class MatrixMediaSource( + val url: String +) : Parcelable diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/media/VideoInfo.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/media/VideoInfo.kt index c2d74fc2f6..ca24a3303f 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/media/VideoInfo.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/media/VideoInfo.kt @@ -23,6 +23,6 @@ data class VideoInfo( val mimetype: String?, val size: Long?, val thumbnailInfo: ThumbnailInfo?, - val thumbnailUrl: String?, + val thumbnailSource: MatrixMediaSource?, val blurhash: String? ) 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 63d3ce3911..31bdfee4fa 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 @@ -21,6 +21,7 @@ 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.ImageInfo +import io.element.android.libraries.matrix.api.media.MatrixMediaSource import io.element.android.libraries.matrix.api.media.VideoInfo sealed interface EventContent @@ -106,25 +107,25 @@ data class EmoteMessageType( data class ImageMessageType( val body: String, - val url: String, + val source: MatrixMediaSource, val info: ImageInfo? ) : MessageType data class AudioMessageType( - var body: String, - var url: String, - var info: AudioInfo? + val body: String, + val source: MatrixMediaSource, + val info: AudioInfo? ) : MessageType data class VideoMessageType( val body: String, - val url: String, + val source: MatrixMediaSource, val info: VideoInfo? ) : MessageType data class FileMessageType( val body: String, - val url: String, + val source: MatrixMediaSource, val info: FileInfo? ) : MessageType diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/FileInfo.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/FileInfo.kt index 98c96c4d9a..d9f8f74df1 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/FileInfo.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/FileInfo.kt @@ -17,13 +17,11 @@ package io.element.android.libraries.matrix.impl.media import io.element.android.libraries.matrix.api.media.FileInfo -import io.element.android.libraries.matrix.api.media.ThumbnailInfo import org.matrix.rustcomponents.sdk.FileInfo as RustFileInfo -import org.matrix.rustcomponents.sdk.ThumbnailInfo as RustThumbnailInfo fun RustFileInfo.map(): FileInfo = FileInfo( mimetype = mimetype, size = size?.toLong(), thumbnailInfo = thumbnailInfo?.map(), - thumbnailUrl = thumbnailSource?.useUrl() + thumbnailSource = thumbnailSource?.map() ) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/ImageInfo.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/ImageInfo.kt index 27ab6d656a..3716912c79 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/ImageInfo.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/ImageInfo.kt @@ -25,6 +25,6 @@ fun RustImageInfo.map(): ImageInfo = ImageInfo( mimetype = mimetype, size = size?.toLong(), thumbnailInfo = thumbnailInfo?.map(), - thumbnailUrl = thumbnailSource?.useUrl(), + thumbnailSource = thumbnailSource?.map(), blurhash = blurhash ) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/MediaSource.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/MediaSource.kt index 2fc50611e8..4d3dc6603f 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/MediaSource.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/MediaSource.kt @@ -16,7 +16,8 @@ package io.element.android.libraries.matrix.impl.media -import org.matrix.rustcomponents.sdk.MediaSource +import io.element.android.libraries.matrix.api.media.MatrixMediaSource import org.matrix.rustcomponents.sdk.use +import org.matrix.rustcomponents.sdk.MediaSource as RustMediaSource -fun MediaSource.useUrl(): String = use { it.url() } +fun RustMediaSource.map(): MatrixMediaSource = use { MatrixMediaSource(it.url()) } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/RustMediaLoader.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/RustMediaLoader.kt index 6d7d48db24..52d4d7ccf4 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/RustMediaLoader.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/RustMediaLoader.kt @@ -16,14 +16,15 @@ package io.element.android.libraries.matrix.impl.media +import android.net.Uri import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.matrix.api.media.MatrixMediaLoader +import io.element.android.libraries.matrix.api.media.MatrixMediaSource import kotlinx.coroutines.withContext import org.matrix.rustcomponents.sdk.Client import org.matrix.rustcomponents.sdk.mediaSourceFromUrl import org.matrix.rustcomponents.sdk.use -import java.nio.file.Path -import kotlin.io.path.Path +import java.io.File class RustMediaLoader( private val dispatchers: CoroutineDispatchers, @@ -31,10 +32,10 @@ class RustMediaLoader( ) : MatrixMediaLoader { @OptIn(ExperimentalUnsignedTypes::class) - override suspend fun loadMediaContent(url: String): Result = + override suspend fun loadMediaContent(source: MatrixMediaSource): Result = withContext(dispatchers.io) { runCatching { - mediaSourceFromUrl(url).use { source -> + mediaSourceFromUrl(source.url).use { source -> innerClient.getMediaContent(source).toUByteArray().toByteArray() } } @@ -42,13 +43,13 @@ class RustMediaLoader( @OptIn(ExperimentalUnsignedTypes::class) override suspend fun loadMediaThumbnail( - url: String, + source: MatrixMediaSource, width: Long, height: Long ): Result = withContext(dispatchers.io) { runCatching { - mediaSourceFromUrl(url).use { mediaSource -> + mediaSourceFromUrl(source.url).use { mediaSource -> innerClient.getMediaThumbnail( mediaSource = mediaSource, width = width.toULong(), @@ -58,15 +59,16 @@ class RustMediaLoader( } } - override suspend fun loadMediaFile(url: String, mimeType: String?): Result = + override suspend fun loadMediaFile(source: MatrixMediaSource, mimeType: String?): Result = withContext(dispatchers.io) { runCatching { - mediaSourceFromUrl(url).use { mediaSource -> + mediaSourceFromUrl(source.url).use { mediaSource -> innerClient.getMediaFile( mediaSource = mediaSource, mimeType = mimeType ?: "application/octet-stream" ).use { - Path(it.path()) + val file = File(it.path()) + Uri.fromFile(file) } } } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/VideoInfo.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/VideoInfo.kt index 0db364f544..09e717c85d 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/VideoInfo.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/VideoInfo.kt @@ -26,6 +26,6 @@ fun RustVideoInfo.map(): VideoInfo = VideoInfo( mimetype = mimetype, size = size?.toLong(), thumbnailInfo = thumbnailInfo?.map(), - thumbnailUrl = thumbnailSource?.useUrl(), + thumbnailSource = thumbnailSource?.map(), blurhash = blurhash ) 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 a000783e86..439ce46b1d 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 @@ -29,7 +29,6 @@ import io.element.android.libraries.matrix.api.timeline.item.event.TextMessageTy import io.element.android.libraries.matrix.api.timeline.item.event.UnknownMessageType import io.element.android.libraries.matrix.api.timeline.item.event.VideoMessageType import io.element.android.libraries.matrix.impl.media.map -import io.element.android.libraries.matrix.impl.media.useUrl import org.matrix.rustcomponents.sdk.Message import org.matrix.rustcomponents.sdk.MessageType import org.matrix.rustcomponents.sdk.use @@ -42,13 +41,13 @@ class EventMessageMapper { val type = message.msgtype().use { type -> when (type) { is MessageType.Audio -> { - AudioMessageType(type.content.body, type.content.source.useUrl(), type.content.info?.map()) + AudioMessageType(type.content.body, type.content.source.map(), type.content.info?.map()) } is MessageType.File -> { - FileMessageType(type.content.body, type.content.source.useUrl(), type.content.info?.map()) + FileMessageType(type.content.body, type.content.source.map(), type.content.info?.map()) } is MessageType.Image -> { - ImageMessageType(type.content.body, type.content.source.useUrl(), type.content.info?.map()) + ImageMessageType(type.content.body, type.content.source.map(), type.content.info?.map()) } is MessageType.Notice -> { NoticeMessageType(type.content.body, type.content.formatted?.map()) @@ -60,7 +59,7 @@ class EventMessageMapper { EmoteMessageType(type.content.body, type.content.formatted?.map()) } is MessageType.Video -> { - VideoMessageType(type.content.body, type.content.source.useUrl(), type.content.info?.map()) + VideoMessageType(type.content.body, type.content.source.map(), type.content.info?.map()) } null -> { UnknownMessageType 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 51e84b441f..2a30d80323 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 @@ -17,6 +17,7 @@ package io.element.android.libraries.matrix.impl.timeline.item.event import io.element.android.libraries.matrix.api.core.UserId +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.MembershipChange @@ -26,7 +27,6 @@ import io.element.android.libraries.matrix.api.timeline.item.event.RedactedConte 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.EventContent import io.element.android.libraries.matrix.api.timeline.item.event.UnableToDecryptContent import io.element.android.libraries.matrix.api.timeline.item.event.UnknownContent import io.element.android.libraries.matrix.impl.media.map @@ -88,7 +88,7 @@ class TimelineEventContentMapper(private val eventMessageMapper: EventMessageMap StickerContent( body = kind.body, info = kind.info.map(), - url = kind.url + url = kind.url, ) } is TimelineItemContentKind.UnableToDecrypt -> { diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/media/FakeMediaLoader.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/media/FakeMediaLoader.kt index fc14de03da..c8a383fadc 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/media/FakeMediaLoader.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/media/FakeMediaLoader.kt @@ -16,15 +16,16 @@ package io.element.android.libraries.matrix.test.media +import android.net.Uri import io.element.android.libraries.matrix.api.media.MatrixMediaLoader -import java.nio.file.Path -import kotlin.io.path.Path +import io.element.android.libraries.matrix.api.media.MatrixMediaSource +import java.io.File class FakeMediaLoader : MatrixMediaLoader { var shouldFail = false - override suspend fun loadMediaContent(url: String): Result { + override suspend fun loadMediaContent(source: MatrixMediaSource): Result { return if (shouldFail) { Result.failure(RuntimeException()) } else { @@ -32,7 +33,7 @@ class FakeMediaLoader : MatrixMediaLoader { } } - override suspend fun loadMediaThumbnail(url: String, width: Long, height: Long): Result { + override suspend fun loadMediaThumbnail(source: MatrixMediaSource, width: Long, height: Long): Result { return if (shouldFail) { Result.failure(RuntimeException()) } else { @@ -40,11 +41,11 @@ class FakeMediaLoader : MatrixMediaLoader { } } - override suspend fun loadMediaFile(url: String, mimeType: String?): Result { + override suspend fun loadMediaFile(source: MatrixMediaSource, mimeType: String?): Result { return if (shouldFail) { Result.failure(RuntimeException()) } else { - return Result.success(Path("path")) + return Result.success(Uri.fromFile(File("path"))) } } } diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/AvatatarDataExt.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/AvatatarDataExt.kt index 37a7f02922..aa06e960c4 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/AvatatarDataExt.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/AvatatarDataExt.kt @@ -17,13 +17,12 @@ package io.element.android.libraries.matrix.ui.media import io.element.android.libraries.designsystem.components.avatar.AvatarData +import io.element.android.libraries.matrix.api.media.MatrixMediaSource import kotlin.math.roundToLong -fun AvatarData.toMediaRequestData(): MediaRequestData? { - return url?.let { - MediaRequestData( - url = it, - kind = MediaRequestData.Kind.Thumbnail(size.dp.value.roundToLong()) - ) - } +fun AvatarData.toMediaRequestData(): MediaRequestData { + return MediaRequestData( + source = url?.let { MatrixMediaSource(it) }, + kind = MediaRequestData.Kind.Thumbnail(size.dp.value.roundToLong()) + ) } diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/CoilMediaFetcher.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/CoilMediaFetcher.kt index 81b47c4022..a409b9de97 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/CoilMediaFetcher.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/CoilMediaFetcher.kt @@ -38,19 +38,15 @@ internal class CoilMediaFetcher( ByteBuffer.wrap(data) }.map { byteBuffer -> imageLoader.components.newFetcher(byteBuffer, options, imageLoader)?.first?.fetch() - } - .fold( - { result -> result }, - { failure -> throw failure } - ) + }.getOrThrow() } private suspend fun loadMedia(): Result { - if (mediaData == null) return Result.failure(IllegalStateException("No media data to fetch.")) + if (mediaData?.source == null) return Result.failure(IllegalStateException("No media data to fetch.")) return when (mediaData.kind) { - is MediaRequestData.Kind.Content -> mediaLoader.loadMediaContent(url = mediaData.url) + is MediaRequestData.Kind.Content -> mediaLoader.loadMediaContent(source = mediaData.source) is MediaRequestData.Kind.Thumbnail -> mediaLoader.loadMediaThumbnail( - url = mediaData.url, + source = mediaData.source, width = mediaData.kind.width, height = mediaData.kind.height ) 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 f2f9bf9794..f3049ab4af 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 @@ -16,8 +16,10 @@ package io.element.android.libraries.matrix.ui.media +import io.element.android.libraries.matrix.api.media.MatrixMediaSource + data class MediaRequestData( - val url: String, + val source: MatrixMediaSource?, val kind: Kind ) { diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/MediaRequestDataKeyer.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/MediaRequestDataKeyer.kt index 56ff15fd44..0064c1b63b 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/MediaRequestDataKeyer.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/MediaRequestDataKeyer.kt @@ -22,7 +22,7 @@ import io.element.android.libraries.designsystem.components.avatar.AvatarData internal class AvatarDataKeyer : Keyer { override fun key(data: AvatarData, options: Options): String? { - return data.toMediaRequestData()?.toKey() + return data.toMediaRequestData().toKey() } } @@ -32,4 +32,7 @@ internal class MediaRequestDataKeyer : Keyer { } } -private fun MediaRequestData.toKey() = "${url}_${kind}" +private fun MediaRequestData.toKey(): String? { + if (source == null) return null + return "${source.url}_${kind}" +}