diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewPresenter.kt index 537d259599..d80359e88c 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewPresenter.kt @@ -25,7 +25,6 @@ import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import io.element.android.features.messages.impl.attachments.Attachment -import io.element.android.features.messages.impl.media.local.LocalMedia import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.executeResult @@ -85,13 +84,7 @@ class AttachmentsPreviewPresenter @AssistedInject constructor( sendActionState: MutableState>, ) { suspend { - when (mediaAttachment.localMedia.source) { - is LocalMedia.Source.FromUri -> { - mediaSender.sendMedia(mediaAttachment.localMedia.source.uri, mediaAttachment.localMedia.mimeType, mediaAttachment.compressIfPossible) - } - else -> error("Attachment should be defined by a uri") - } - + mediaSender.sendMedia(mediaAttachment.localMedia.uri, mediaAttachment.localMedia.mimeType, mediaAttachment.compressIfPossible) }.executeResult(sendActionState) } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewStateProvider.kt index e7a3968386..26565a226a 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewStateProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewStateProvider.kt @@ -17,11 +17,11 @@ package io.element.android.features.messages.impl.attachments.preview import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import androidx.core.net.toUri import io.element.android.features.messages.impl.attachments.Attachment import io.element.android.features.messages.impl.media.local.LocalMedia import io.element.android.libraries.architecture.Async import io.element.android.libraries.core.mimetype.MimeTypes -import java.io.File open class AttachmentsPreviewStateProvider : PreviewParameterProvider { override val values: Sequence @@ -34,7 +34,7 @@ open class AttachmentsPreviewStateProvider : PreviewParameterProvider = Async.Uninitialized) = AttachmentsPreviewState( attachment = Attachment.Media( - localMedia = LocalMedia(LocalMedia.Source.FromFile(File("path")), MimeTypes.Jpeg, "an image", 1000L), + localMedia = LocalMedia("path".toUri(), MimeTypes.Jpeg, "an image", 1000L), compressIfPossible = true ), sendActionState = sendActionState, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/local/AndroidLocalMediaActionsHandler.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/local/AndroidLocalMediaActionsHandler.kt index 7b619ab419..1e3056e146 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/local/AndroidLocalMediaActionsHandler.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/local/AndroidLocalMediaActionsHandler.kt @@ -16,10 +16,8 @@ package io.element.android.features.messages.impl.media.local -import android.content.ContentResolver import android.content.ContentValues import android.content.Context -import android.net.Uri import android.os.Build import android.os.Environment import android.provider.MediaStore @@ -62,10 +60,10 @@ class AndroidLocalMediaActionsHandler @Inject constructor( put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS) } val resolver = context.contentResolver - val uri = resolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, contentValues) - if (uri != null) { - localMedia.openStream(resolver)?.use { input -> - resolver.openOutputStream(uri).use { output -> + val outputUri = resolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, contentValues) + if (outputUri != null) { + localMedia.openStream()?.use { input -> + resolver.openOutputStream(outputUri).use { output -> input.copyTo(output!!, DEFAULT_BUFFER_SIZE) } } @@ -77,18 +75,14 @@ class AndroidLocalMediaActionsHandler @Inject constructor( Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), localMedia.name ?: "" ) - localMedia.openStream(context.contentResolver)?.use { input -> + localMedia.openStream()?.use { input -> FileOutputStream(target).use { output -> input.copyTo(output) } } } - private fun LocalMedia.openStream(contentResolver: ContentResolver): InputStream? { - return when (val model = model) { - is File -> model.inputStream() - is Uri -> contentResolver.openInputStream(model) - else -> null - } + private fun LocalMedia.openStream(): InputStream? { + return context.contentResolver.openInputStream(uri) } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/local/AndroidLocalMediaFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/local/AndroidLocalMediaFactory.kt index 4ee00914ed..68c1b935d8 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/local/AndroidLocalMediaFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/local/AndroidLocalMediaFactory.kt @@ -18,6 +18,7 @@ package io.element.android.features.messages.impl.media.local import android.content.Context import android.net.Uri +import androidx.core.net.toUri import com.squareup.anvil.annotations.ContributesBinding import io.element.android.libraries.androidutils.file.getFileName import io.element.android.libraries.androidutils.file.getFileSize @@ -25,7 +26,7 @@ import io.element.android.libraries.core.mimetype.MimeTypes import io.element.android.libraries.di.AppScope import io.element.android.libraries.di.ApplicationContext import io.element.android.libraries.matrix.api.media.MediaFile -import java.io.File +import io.element.android.libraries.matrix.api.media.toFile import javax.inject.Inject @ContributesBinding(AppScope::class) @@ -34,16 +35,8 @@ class AndroidLocalMediaFactory @Inject constructor( ) : LocalMediaFactory { override fun createFromMediaFile(mediaFile: MediaFile, mimeType: String?): LocalMedia { - val resolvedMimeType = mimeType ?: MimeTypes.OctetStream - val file = File(mediaFile.path()) - val fileName = file.name - val fileSize = file.length() - return LocalMedia( - source = LocalMedia.Source.FromFile(file), - mimeType = resolvedMimeType, - name = fileName, - size = fileSize - ) + val uri = mediaFile.toFile().toUri() + return createFromUri(uri, mimeType) } override fun createFromUri(uri: Uri, mimeType: String?): LocalMedia { @@ -51,7 +44,7 @@ class AndroidLocalMediaFactory @Inject constructor( val fileName = context.getFileName(uri) val fileSize = context.getFileSize(uri) return LocalMedia( - source = LocalMedia.Source.FromUri(uri), + uri = uri, mimeType = resolvedMimeType, name = fileName, size = fileSize 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 5d1afd6bec..e125e531bf 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 @@ -21,27 +21,12 @@ import android.os.Parcelable import androidx.compose.runtime.Immutable import kotlinx.parcelize.IgnoredOnParcel import kotlinx.parcelize.Parcelize -import java.io.File @Parcelize @Immutable data class LocalMedia( - val source: Source, + val uri: Uri, val mimeType: String, val name: String?, val size: Long, -) : Parcelable { - - sealed interface Source : Parcelable { - @Parcelize - data class FromUri(val uri: Uri) : Source - - @Parcelize - data class FromFile(val file: File) : Source - } - - @IgnoredOnParcel val model: Any = when (source) { - is Source.FromUri -> source.uri - is Source.FromFile -> source.file - } -} +) : Parcelable 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 c1d9e2f119..4a4bc5c9fc 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 @@ -31,13 +31,13 @@ import androidx.compose.ui.platform.LocalInspectionMode import androidx.compose.ui.res.painterResource import androidx.compose.ui.viewinterop.AndroidView import androidx.lifecycle.Lifecycle +import androidx.media3.common.MediaItem import androidx.media3.common.MimeTypes import androidx.media3.common.Player import androidx.media3.common.util.UnstableApi import androidx.media3.ui.AspectRatioFrameLayout import androidx.media3.ui.PlayerView import io.element.android.features.messages.impl.media.local.exoplayer.ExoPlayerWrapper -import io.element.android.features.messages.impl.media.local.exoplayer.toMediaItem import io.element.android.libraries.designsystem.R import io.element.android.libraries.designsystem.utils.OnLifecycleEvent import me.saket.telephoto.zoomable.ZoomSpec @@ -93,7 +93,7 @@ private fun MediaImageView( ZoomableAsyncImage( modifier = modifier.fillMaxSize(), state = zoomableImageState, - model = localMedia?.model, + model = localMedia?.uri, contentDescription = "Image", contentScale = ContentScale.Fit, ) @@ -107,7 +107,6 @@ fun MediaVideoView( onReady: () -> Unit, modifier: Modifier = Modifier, ) { - val context = LocalContext.current val playerListener = object : Player.Listener { override fun onRenderedFirstFrame() { @@ -121,9 +120,9 @@ fun MediaVideoView( this.prepare() } } - if (localMedia?.source != null) { - LaunchedEffect(localMedia.source) { - val mediaItem = localMedia.toMediaItem() + if (localMedia?.uri != null) { + LaunchedEffect(localMedia.uri) { + val mediaItem = MediaItem.fromUri(localMedia.uri) exoPlayer.setMediaItem(mediaItem) } } else { diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/local/exoplayer/LocalMediaExt.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/local/exoplayer/LocalMediaExt.kt deleted file mode 100644 index 9a96a6a39b..0000000000 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/local/exoplayer/LocalMediaExt.kt +++ /dev/null @@ -1,27 +0,0 @@ -/* - * 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.local.exoplayer - -import androidx.media3.common.MediaItem -import io.element.android.features.messages.impl.media.local.LocalMedia - -fun LocalMedia.toMediaItem(): MediaItem { - return when (source) { - is LocalMedia.Source.FromFile -> MediaItem.fromUri(source.file.path) - is LocalMedia.Source.FromUri -> MediaItem.fromUri(source.uri) - } -} 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 c961f10caf..6c54eb3b05 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 @@ -16,11 +16,11 @@ package io.element.android.features.messages.impl.media.viewer +import android.net.Uri import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.media3.common.MimeTypes import io.element.android.features.messages.impl.media.local.LocalMedia import io.element.android.libraries.architecture.Async -import java.io.File open class MediaViewerStateProvider : PreviewParameterProvider { override val values: Sequence @@ -31,14 +31,14 @@ open class MediaViewerStateProvider : PreviewParameterProvider aMediaViewerState( Async.Success( LocalMedia( - LocalMedia.Source.FromFile(File("")), MimeTypes.IMAGE_JPEG, "an image file", 100L + Uri.EMPTY, MimeTypes.IMAGE_JPEG, "an image file", 100L ) ), ), aMediaViewerState( Async.Success( LocalMedia( - LocalMedia.Source.FromFile(File("")), MimeTypes.VIDEO_MP4, "a video file", 100L + Uri.EMPTY, MimeTypes.VIDEO_MP4, "a video file", 100L ) ), ) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt index 74f533c6fe..ae975f4ddb 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt @@ -31,7 +31,6 @@ import androidx.media3.common.MimeTypes import androidx.media3.common.util.UnstableApi import io.element.android.features.messages.impl.attachments.Attachment import io.element.android.features.messages.impl.attachments.preview.error.sendAttachmentError -import io.element.android.features.messages.impl.media.local.LocalMedia import io.element.android.features.messages.impl.media.local.LocalMediaFactory import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.core.data.StableCharSequence @@ -192,7 +191,8 @@ class MessageComposerPresenter @Inject constructor( when (attachment) { is Attachment.Media -> { sendMedia( - media = attachment.localMedia, + uri = attachment.localMedia.uri, + mimeType = attachment.localMedia.mimeType, attachmentState = attachmentState ) } @@ -226,12 +226,11 @@ class MessageComposerPresenter @Inject constructor( } private suspend fun sendMedia( - media: LocalMedia, + uri: Uri, + mimeType: String, attachmentState: MutableState, ) { - if (media.source !is LocalMedia.Source.FromUri) error("Attachment should use Uri") - val uri = media.source.uri - mediaSender.sendMedia(uri, media.mimeType, compressIfPossible = false) + mediaSender.sendMedia(uri, mimeType, compressIfPossible = false) .onSuccess { attachmentState.value = AttachmentsState.None }.onFailure { diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/fixtures/media.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/fixtures/media.kt index 399c39cc88..60a01a76d4 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/fixtures/media.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/fixtures/media.kt @@ -20,6 +20,7 @@ import android.net.Uri import androidx.media3.common.MimeTypes import io.element.android.features.messages.impl.attachments.Attachment import io.element.android.features.messages.impl.media.local.LocalMedia +import io.mockk.mockk fun aLocalMedia( uri: Uri, @@ -27,7 +28,7 @@ fun aLocalMedia( name: String = "a media", size: Long = 1000, ) = LocalMedia( - source = LocalMedia.Source.FromUri(uri), + uri = uri, mimeType = mimeType, name = name, size = size, 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 index 3ef659133d..d4989dbffc 100644 --- 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 @@ -17,6 +17,7 @@ package io.element.android.libraries.matrix.api.media import java.io.Closeable +import java.io.File /** * A wrapper around a media file on the disk. @@ -25,3 +26,7 @@ import java.io.Closeable interface MediaFile : Closeable { fun path(): String } + +fun MediaFile.toFile(): File { + return File(path()) +}