From c191d5991d34e4c38b25ce2bd6ed97d816db692e Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 12 May 2023 18:48:24 +0200 Subject: [PATCH] Media: improve media viewer --- .../messages/impl/MessagesFlowNode.kt | 12 ++-- .../messages/impl/media/local/LocalMedia.kt | 2 +- .../impl/media/local/LocalMediaView.kt | 24 ++++--- .../impl/media/viewer/MediaViewerEvents.kt | 22 ++++++ .../impl/media/viewer/MediaViewerNode.kt | 3 +- .../impl/media/viewer/MediaViewerPresenter.kt | 68 ++++++++++++++----- .../impl/media/viewer/MediaViewerState.kt | 3 +- .../media/viewer/MediaViewerStateProvider.kt | 3 +- .../impl/media/viewer/MediaViewerView.kt | 44 ++++++++++-- .../TimelineItemContentMessageFactory.kt | 3 +- .../model/event/TimelineItemImageContent.kt | 1 + .../event/TimelineItemImageContentProvider.kt | 1 + .../model/event/TimelineItemVideoContent.kt | 2 +- .../event/TimelineItemVideoContentProvider.kt | 3 +- .../matrix/api/media/MatrixMediaLoader.kt | 6 +- .../libraries/matrix/api/media/MediaFile.kt | 27 ++++++++ .../matrix/impl/media/MediaHandle.kt | 31 +++++++++ .../matrix/impl/media/RustMediaLoader.kt | 14 ++-- .../matrix/test/media/FakeMediaFile.kt | 27 ++++++++ .../matrix/test/media/FakeMediaLoader.kt | 7 +- 20 files changed, 244 insertions(+), 59 deletions(-) create mode 100644 features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/viewer/MediaViewerEvents.kt create mode 100644 libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/media/MediaFile.kt create mode 100644 libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/MediaHandle.kt create mode 100644 libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/media/FakeMediaFile.kt 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 24122459a6..a6b906d6fb 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 @@ -62,7 +62,11 @@ class MessagesFlowNode @AssistedInject constructor( object Messages : NavTarget @Parcelize - data class MediaViewer(val title: String, val mediaSource: MatrixMediaSource) : NavTarget + data class MediaViewer( + val title: String, + val mediaSource: MatrixMediaSource, + val mimeType: String? + ) : NavTarget @Parcelize data class AttachmentPreview(val attachment: Attachment) : NavTarget @@ -89,7 +93,7 @@ class MessagesFlowNode @AssistedInject constructor( createNode(buildContext, listOf(callback)) } is NavTarget.MediaViewer -> { - val inputs = MediaViewerNode.Inputs(navTarget.title, navTarget.mediaSource) + val inputs = MediaViewerNode.Inputs(navTarget.title, navTarget.mediaSource, navTarget.mimeType) createNode(buildContext, listOf(inputs)) } is NavTarget.AttachmentPreview -> { @@ -103,12 +107,12 @@ class MessagesFlowNode @AssistedInject constructor( when (event.content) { is TimelineItemImageContent -> { val mediaSource = event.content.mediaSource - val navTarget = NavTarget.MediaViewer(event.content.body, mediaSource) + val navTarget = NavTarget.MediaViewer(event.content.body, mediaSource, event.content.mimeType) backstack.push(navTarget) } is TimelineItemVideoContent -> { val mediaSource = event.content.videoSource - val navTarget = NavTarget.MediaViewer(event.content.body, mediaSource) + val navTarget = NavTarget.MediaViewer(event.content.body, mediaSource, event.content.mimeType) backstack.push(navTarget) } else -> Unit diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/local/LocalMedia.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/local/LocalMedia.kt index 1803bb3fb1..a1ece7329a 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/local/LocalMedia.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/local/LocalMedia.kt @@ -18,7 +18,7 @@ package io.element.android.features.messages.impl.media.local import android.net.Uri import android.os.Parcelable -import kotlinx.android.parcel.Parcelize +import kotlinx.parcelize.Parcelize @Parcelize data class LocalMedia( diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/local/LocalMediaView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/local/LocalMediaView.kt index 2cbe08543c..7fc6ac5608 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/local/LocalMediaView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/local/LocalMediaView.kt @@ -22,6 +22,8 @@ import android.view.ViewGroup.LayoutParams.MATCH_PARENT import android.widget.FrameLayout import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.layout.ContentScale @@ -84,13 +86,17 @@ fun MediaVideoView( modifier: Modifier = Modifier, ) { val context = LocalContext.current - val exoPlayer = ExoPlayer.Builder(LocalContext.current).build() - val mediaItem = MediaItem.Builder() - .setUri(uri) - .build() - exoPlayer.playWhenReady - exoPlayer.setMediaItem(mediaItem) - exoPlayer.prepare() + val exoPlayer = remember { + ExoPlayer.Builder(context).build() + .apply { + this.playWhenReady = true + this.prepare() + } + } + LaunchedEffect(uri) { + val mediaItem = MediaItem.fromUri(uri) + exoPlayer.setMediaItem(mediaItem) + } AndroidView( factory = { @@ -98,8 +104,10 @@ fun MediaVideoView( player = exoPlayer resizeMode = AspectRatioFrameLayout.RESIZE_MODE_FIT layoutParams = FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT) + controllerShowTimeoutMs = 3000 } - }, modifier = modifier.fillMaxSize() + }, + modifier = modifier.fillMaxSize() ) OnLifecycleEvent { _, event -> diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/viewer/MediaViewerEvents.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/viewer/MediaViewerEvents.kt new file mode 100644 index 0000000000..bfb346202c --- /dev/null +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/viewer/MediaViewerEvents.kt @@ -0,0 +1,22 @@ +/* + * 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.media.viewer + +sealed interface MediaViewerEvents { + object RetryLoading : MediaViewerEvents + object SaveOnDisk : MediaViewerEvents +} diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/viewer/MediaViewerNode.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/viewer/MediaViewerNode.kt index 02ec781815..7f52e7e07a 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/viewer/MediaViewerNode.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/viewer/MediaViewerNode.kt @@ -39,11 +39,12 @@ class MediaViewerNode @AssistedInject constructor( data class Inputs( val name: String, val mediaSource: MatrixMediaSource, + val mimeType: String? ) : NodeInputs private val inputs: Inputs = inputs() - private val presenter = presenterFactory.create(inputs.name, inputs.mediaSource) + private val presenter = presenterFactory.create(inputs) @Composable override fun View(modifier: Modifier) { diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/viewer/MediaViewerPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/viewer/MediaViewerPresenter.kt index 70408cc580..d727035176 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/viewer/MediaViewerPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/viewer/MediaViewerPresenter.kt @@ -17,8 +17,14 @@ package io.element.android.features.messages.impl.media.viewer import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue -import androidx.compose.runtime.produceState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.core.net.toUri import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject @@ -27,37 +33,65 @@ import io.element.android.features.messages.impl.media.local.LocalMediaFactory import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.matrix.api.MatrixClient -import io.element.android.libraries.matrix.api.media.MatrixMediaSource +import io.element.android.libraries.matrix.api.media.MediaFile +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch class MediaViewerPresenter @AssistedInject constructor( - @Assisted private val name: String, - @Assisted private val mediaSource: MatrixMediaSource, + @Assisted private val inputs: MediaViewerNode.Inputs, private val localMediaFactory: LocalMediaFactory, private val client: MatrixClient, ) : Presenter { @AssistedFactory interface Factory { - fun create(name: String, mediaSource: MatrixMediaSource): MediaViewerPresenter + fun create(inputs: MediaViewerNode.Inputs): MediaViewerPresenter } @Composable override fun present(): MediaViewerState { - val localMedia by produceState>(initialValue = Async.Uninitialized) { - value = Async.Loading(null) - //TODO we are missing some permissions to use this API - client.mediaLoader.loadMediaFile(mediaSource, null) - .onSuccess { - val localMedia = localMediaFactory.createFromUri(uri = it, null) - Async.Success(localMedia) - }.onFailure { - Async.Failure(it, null) - } + val coroutineScope = rememberCoroutineScope() + var loadMediaTrigger by remember { mutableStateOf(0) } + val mediaFile: MutableState = remember { + mutableStateOf(null) + } + val localMedia: MutableState> = remember { + mutableStateOf(Async.Uninitialized) + } + DisposableEffect(loadMediaTrigger) { + coroutineScope.loadMedia(mediaFile, localMedia) + onDispose { + mediaFile.value?.close() + } + } + + fun handleEvents(mediaViewerEvents: MediaViewerEvents) { + when (mediaViewerEvents) { + MediaViewerEvents.RetryLoading -> loadMediaTrigger++ + MediaViewerEvents.SaveOnDisk -> TODO() + } } return MediaViewerState( - name = name, - downloadedMedia = localMedia, + name = inputs.name, + downloadedMedia = localMedia.value, + eventSink = ::handleEvents ) } + + private fun CoroutineScope.loadMedia(mediaFile: MutableState, localMedia: MutableState>) = launch { + mediaFile.value = null + localMedia.value = Async.Loading() + client.mediaLoader.loadMediaFile(inputs.mediaSource, inputs.mimeType) + .onSuccess { + mediaFile.value = it + }.mapCatching { + val uri = it.path().toUri() + localMediaFactory.createFromUri(uri, inputs.mimeType)!! + }.onSuccess { + localMedia.value = Async.Success(it) + }.onFailure { + localMedia.value = Async.Failure(it) + } + } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/viewer/MediaViewerState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/viewer/MediaViewerState.kt index 6a486b04fe..b5279af6c8 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/viewer/MediaViewerState.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/viewer/MediaViewerState.kt @@ -21,5 +21,6 @@ import io.element.android.libraries.architecture.Async data class MediaViewerState( val name: String, - val downloadedMedia: Async + val downloadedMedia: Async, + val eventSink: (MediaViewerEvents) -> Unit ) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/viewer/MediaViewerStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/viewer/MediaViewerStateProvider.kt index 19bc34535b..66c41a0eba 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/viewer/MediaViewerStateProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/viewer/MediaViewerStateProvider.kt @@ -29,5 +29,6 @@ open class MediaViewerStateProvider : PreviewParameterProvider fun aMediaViewerState() = MediaViewerState( name = "A media", - downloadedMedia = Async.Uninitialized + downloadedMedia = Async.Uninitialized, + eventSink = {} ) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/viewer/MediaViewerView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/viewer/MediaViewerView.kt index f3b42f1cc6..a075656b78 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/viewer/MediaViewerView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/viewer/MediaViewerView.kt @@ -19,37 +19,50 @@ package io.element.android.features.messages.impl.media.viewer import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource 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.media.local.LocalMediaView import io.element.android.libraries.architecture.Async -import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog import io.element.android.libraries.designsystem.preview.ElementPreviewDark import io.element.android.libraries.designsystem.preview.ElementPreviewLight +import io.element.android.libraries.designsystem.theme.components.Button import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator import io.element.android.libraries.designsystem.theme.components.Scaffold +import io.element.android.libraries.designsystem.theme.components.Text +import io.element.android.libraries.ui.strings.R.string as StringR @Composable fun MediaViewerView( state: MediaViewerState, modifier: Modifier = Modifier, ) { + + fun onRetry() { + state.eventSink(MediaViewerEvents.RetryLoading) + } + Scaffold(modifier) { Box( - modifier = Modifier.fillMaxSize(), + modifier = Modifier + .fillMaxSize() + .padding(it), contentAlignment = Alignment.Center ) { when (state.downloadedMedia) { is Async.Success -> LocalMediaView(state.downloadedMedia.state) - is Async.Failure -> ErrorDialog( - content = "Error while downloading the media", - ) + is Async.Failure -> ErrorView("Error while downloading", ::onRetry) else -> CircularProgressIndicator( strokeWidth = 2.dp, ) @@ -58,6 +71,27 @@ fun MediaViewerView( } } +@Composable +private fun ErrorView( + errorMessage: String, + onRetry: () -> Unit, + modifier: Modifier = Modifier, +) { + Column( + modifier = modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Text(text = errorMessage) + Spacer(modifier = Modifier.size(8.dp)) + Button( + onClick = onRetry + ) { + Text(text = stringResource(id = StringR.action_retry)) + } + + } +} + @Preview @Composable fun MediaViewerViewLightPreview(@PreviewParameter(MediaViewerStateProvider::class) state: MediaViewerState) = 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 857049d1be..be751f4ece 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 @@ -52,6 +52,7 @@ class TimelineItemContentMessageFactory @Inject constructor() { body = messageType.body, height = messageType.info?.height?.toInt(), width = messageType.info?.width?.toInt(), + mimeType = messageType.info?.mimetype, mediaSource = messageType.source, blurhash = messageType.info?.blurhash, aspectRatio = aspectRatio @@ -69,7 +70,7 @@ class TimelineItemContentMessageFactory @Inject constructor() { body = messageType.body, thumbnailSource = messageType.info?.thumbnailSource, videoSource = messageType.source, - mimetype = messageType.info?.mimetype, + mimeType = messageType.info?.mimetype, width = messageType.info?.width?.toInt(), height = messageType.info?.height?.toInt(), duration = messageType.info?.duration ?: 0L, 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 9f70d16de4..0d136a3144 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 @@ -21,6 +21,7 @@ import io.element.android.libraries.matrix.api.media.MatrixMediaSource data class TimelineItemImageContent( val body: String, val mediaSource: MatrixMediaSource, + val mimeType: String?, val blurhash: String?, val width: Int?, val height: Int?, 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 a43551cd2d..0a3c339fdd 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 @@ -34,6 +34,7 @@ fun aTimelineItemImageContent() = TimelineItemImageContent( mediaSource = MatrixMediaSource(""), blurhash = "TQF5:I_NtRE4kXt7Z#MwkCIARPjr", aspectRatio = 0.5f, + mimeType = "null", height = null, width = null ) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemVideoContent.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemVideoContent.kt index f45719a837..51a548e04c 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemVideoContent.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemVideoContent.kt @@ -27,7 +27,7 @@ data class TimelineItemVideoContent( val blurhash: String?, val height: Int?, val width: Int?, - val mimetype: String?, + val mimeType: String?, ) : TimelineItemEventContent { override val type: String = "TimelineItemImageContent" } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemVideoContentProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemVideoContentProvider.kt index 7d9af16c79..4b4cc7240e 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemVideoContentProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemVideoContentProvider.kt @@ -18,7 +18,6 @@ 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 TimelineItemVideoContentProvider : PreviewParameterProvider { override val values: Sequence @@ -38,5 +37,5 @@ fun aTimelineItemVideoContent() = TimelineItemVideoContent( videoSource = MatrixMediaSource(""), height = null, width = null, - mimetype = null + mimeType = null ) 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 43ab98c3b9..1fa7b3e592 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,8 +16,6 @@ package io.element.android.libraries.matrix.api.media -import android.net.Uri - interface MatrixMediaLoader { /** * @param url to fetch the content for. @@ -36,7 +34,7 @@ interface MatrixMediaLoader { /** * @param url to fetch the data for. * @param mimeType: optional mime type - * @return a [Result] of [Uri]. It's the uri of the downloaded file. + * @return a [Result] of [MediaFile] */ - suspend fun loadMediaFile(source: MatrixMediaSource, 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/MediaFile.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/media/MediaFile.kt new file mode 100644 index 0000000000..3ef659133d --- /dev/null +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/media/MediaFile.kt @@ -0,0 +1,27 @@ +/* + * 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 java.io.Closeable + +/** + * A wrapper around a media file on the disk. + * When closed the file will be removed from the disk. + */ +interface MediaFile : Closeable { + fun path(): String +} diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/MediaHandle.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/MediaHandle.kt new file mode 100644 index 0000000000..4b26b8c6c6 --- /dev/null +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/MediaHandle.kt @@ -0,0 +1,31 @@ +/* + * 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.impl.media + +import io.element.android.libraries.matrix.api.media.MediaFile +import org.matrix.rustcomponents.sdk.MediaFileHandle + +class RustMediaFile(private val inner: MediaFileHandle) : MediaFile { + + override fun path(): String { + return inner.path() + } + + override fun close() { + inner.close() + } +} 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 52d4d7ccf4..87805f98bd 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,15 +16,14 @@ 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 io.element.android.libraries.matrix.api.media.MediaFile import kotlinx.coroutines.withContext import org.matrix.rustcomponents.sdk.Client import org.matrix.rustcomponents.sdk.mediaSourceFromUrl import org.matrix.rustcomponents.sdk.use -import java.io.File class RustMediaLoader( private val dispatchers: CoroutineDispatchers, @@ -59,19 +58,16 @@ class RustMediaLoader( } } - override suspend fun loadMediaFile(source: MatrixMediaSource, mimeType: String?): Result = + override suspend fun loadMediaFile(source: MatrixMediaSource, mimeType: String?): Result = withContext(dispatchers.io) { runCatching { mediaSourceFromUrl(source.url).use { mediaSource -> - innerClient.getMediaFile( + val mediaFile = innerClient.getMediaFile( mediaSource = mediaSource, mimeType = mimeType ?: "application/octet-stream" - ).use { - val file = File(it.path()) - Uri.fromFile(file) - } + ) + RustMediaFile(mediaFile) } } - } } diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/media/FakeMediaFile.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/media/FakeMediaFile.kt new file mode 100644 index 0000000000..275580d11e --- /dev/null +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/media/FakeMediaFile.kt @@ -0,0 +1,27 @@ +/* + * 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.test.media + +import io.element.android.libraries.matrix.api.media.MediaFile + +class FakeMediaFile(private val path: String) : MediaFile { + override fun path(): String { + return path + } + + override fun close() = Unit +} 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 c8a383fadc..8369c309cc 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,10 +16,9 @@ package io.element.android.libraries.matrix.test.media -import android.net.Uri import io.element.android.libraries.matrix.api.media.MatrixMediaLoader import io.element.android.libraries.matrix.api.media.MatrixMediaSource -import java.io.File +import io.element.android.libraries.matrix.api.media.MediaFile class FakeMediaLoader : MatrixMediaLoader { @@ -41,11 +40,11 @@ class FakeMediaLoader : MatrixMediaLoader { } } - override suspend fun loadMediaFile(source: MatrixMediaSource, mimeType: String?): Result { + override suspend fun loadMediaFile(source: MatrixMediaSource, mimeType: String?): Result { return if (shouldFail) { Result.failure(RuntimeException()) } else { - return Result.success(Uri.fromFile(File("path"))) + return Result.success(FakeMediaFile("")) } } }