Media: finally revert to using only uri but with the proper scheme..

This commit is contained in:
ganfra 2023-06-02 08:49:28 +02:00
parent ab48735ddc
commit 70e9dfa25d
11 changed files with 37 additions and 95 deletions

View file

@ -25,7 +25,6 @@ import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
import io.element.android.features.messages.impl.attachments.Attachment 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.Async
import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.architecture.executeResult import io.element.android.libraries.architecture.executeResult
@ -85,13 +84,7 @@ class AttachmentsPreviewPresenter @AssistedInject constructor(
sendActionState: MutableState<Async<Unit>>, sendActionState: MutableState<Async<Unit>>,
) { ) {
suspend { suspend {
when (mediaAttachment.localMedia.source) { mediaSender.sendMedia(mediaAttachment.localMedia.uri, mediaAttachment.localMedia.mimeType, mediaAttachment.compressIfPossible)
is LocalMedia.Source.FromUri -> {
mediaSender.sendMedia(mediaAttachment.localMedia.source.uri, mediaAttachment.localMedia.mimeType, mediaAttachment.compressIfPossible)
}
else -> error("Attachment should be defined by a uri")
}
}.executeResult(sendActionState) }.executeResult(sendActionState)
} }
} }

View file

@ -17,11 +17,11 @@
package io.element.android.features.messages.impl.attachments.preview package io.element.android.features.messages.impl.attachments.preview
import androidx.compose.ui.tooling.preview.PreviewParameterProvider 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.attachments.Attachment
import io.element.android.features.messages.impl.media.local.LocalMedia import io.element.android.features.messages.impl.media.local.LocalMedia
import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Async
import io.element.android.libraries.core.mimetype.MimeTypes import io.element.android.libraries.core.mimetype.MimeTypes
import java.io.File
open class AttachmentsPreviewStateProvider : PreviewParameterProvider<AttachmentsPreviewState> { open class AttachmentsPreviewStateProvider : PreviewParameterProvider<AttachmentsPreviewState> {
override val values: Sequence<AttachmentsPreviewState> override val values: Sequence<AttachmentsPreviewState>
@ -34,7 +34,7 @@ open class AttachmentsPreviewStateProvider : PreviewParameterProvider<Attachment
fun anAttachmentsPreviewState(sendActionState: Async<Unit> = Async.Uninitialized) = AttachmentsPreviewState( fun anAttachmentsPreviewState(sendActionState: Async<Unit> = Async.Uninitialized) = AttachmentsPreviewState(
attachment = Attachment.Media( 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 compressIfPossible = true
), ),
sendActionState = sendActionState, sendActionState = sendActionState,

View file

@ -16,10 +16,8 @@
package io.element.android.features.messages.impl.media.local package io.element.android.features.messages.impl.media.local
import android.content.ContentResolver
import android.content.ContentValues import android.content.ContentValues
import android.content.Context import android.content.Context
import android.net.Uri
import android.os.Build import android.os.Build
import android.os.Environment import android.os.Environment
import android.provider.MediaStore import android.provider.MediaStore
@ -62,10 +60,10 @@ class AndroidLocalMediaActionsHandler @Inject constructor(
put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS) put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS)
} }
val resolver = context.contentResolver val resolver = context.contentResolver
val uri = resolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, contentValues) val outputUri = resolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, contentValues)
if (uri != null) { if (outputUri != null) {
localMedia.openStream(resolver)?.use { input -> localMedia.openStream()?.use { input ->
resolver.openOutputStream(uri).use { output -> resolver.openOutputStream(outputUri).use { output ->
input.copyTo(output!!, DEFAULT_BUFFER_SIZE) input.copyTo(output!!, DEFAULT_BUFFER_SIZE)
} }
} }
@ -77,18 +75,14 @@ class AndroidLocalMediaActionsHandler @Inject constructor(
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),
localMedia.name ?: "" localMedia.name ?: ""
) )
localMedia.openStream(context.contentResolver)?.use { input -> localMedia.openStream()?.use { input ->
FileOutputStream(target).use { output -> FileOutputStream(target).use { output ->
input.copyTo(output) input.copyTo(output)
} }
} }
} }
private fun LocalMedia.openStream(contentResolver: ContentResolver): InputStream? { private fun LocalMedia.openStream(): InputStream? {
return when (val model = model) { return context.contentResolver.openInputStream(uri)
is File -> model.inputStream()
is Uri -> contentResolver.openInputStream(model)
else -> null
}
} }
} }

View file

@ -18,6 +18,7 @@ package io.element.android.features.messages.impl.media.local
import android.content.Context import android.content.Context
import android.net.Uri import android.net.Uri
import androidx.core.net.toUri
import com.squareup.anvil.annotations.ContributesBinding import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.libraries.androidutils.file.getFileName import io.element.android.libraries.androidutils.file.getFileName
import io.element.android.libraries.androidutils.file.getFileSize 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.AppScope
import io.element.android.libraries.di.ApplicationContext import io.element.android.libraries.di.ApplicationContext
import io.element.android.libraries.matrix.api.media.MediaFile 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 import javax.inject.Inject
@ContributesBinding(AppScope::class) @ContributesBinding(AppScope::class)
@ -34,16 +35,8 @@ class AndroidLocalMediaFactory @Inject constructor(
) : LocalMediaFactory { ) : LocalMediaFactory {
override fun createFromMediaFile(mediaFile: MediaFile, mimeType: String?): LocalMedia { override fun createFromMediaFile(mediaFile: MediaFile, mimeType: String?): LocalMedia {
val resolvedMimeType = mimeType ?: MimeTypes.OctetStream val uri = mediaFile.toFile().toUri()
val file = File(mediaFile.path()) return createFromUri(uri, mimeType)
val fileName = file.name
val fileSize = file.length()
return LocalMedia(
source = LocalMedia.Source.FromFile(file),
mimeType = resolvedMimeType,
name = fileName,
size = fileSize
)
} }
override fun createFromUri(uri: Uri, mimeType: String?): LocalMedia { override fun createFromUri(uri: Uri, mimeType: String?): LocalMedia {
@ -51,7 +44,7 @@ class AndroidLocalMediaFactory @Inject constructor(
val fileName = context.getFileName(uri) val fileName = context.getFileName(uri)
val fileSize = context.getFileSize(uri) val fileSize = context.getFileSize(uri)
return LocalMedia( return LocalMedia(
source = LocalMedia.Source.FromUri(uri), uri = uri,
mimeType = resolvedMimeType, mimeType = resolvedMimeType,
name = fileName, name = fileName,
size = fileSize size = fileSize

View file

@ -21,27 +21,12 @@ import android.os.Parcelable
import androidx.compose.runtime.Immutable import androidx.compose.runtime.Immutable
import kotlinx.parcelize.IgnoredOnParcel import kotlinx.parcelize.IgnoredOnParcel
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import java.io.File
@Parcelize @Parcelize
@Immutable @Immutable
data class LocalMedia( data class LocalMedia(
val source: Source, val uri: Uri,
val mimeType: String, val mimeType: String,
val name: String?, val name: String?,
val size: Long, val size: Long,
) : Parcelable { ) : 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
}
}

View file

@ -31,13 +31,13 @@ import androidx.compose.ui.platform.LocalInspectionMode
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.viewinterop.AndroidView import androidx.compose.ui.viewinterop.AndroidView
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.media3.common.MediaItem
import androidx.media3.common.MimeTypes import androidx.media3.common.MimeTypes
import androidx.media3.common.Player import androidx.media3.common.Player
import androidx.media3.common.util.UnstableApi import androidx.media3.common.util.UnstableApi
import androidx.media3.ui.AspectRatioFrameLayout import androidx.media3.ui.AspectRatioFrameLayout
import androidx.media3.ui.PlayerView 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.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.R
import io.element.android.libraries.designsystem.utils.OnLifecycleEvent import io.element.android.libraries.designsystem.utils.OnLifecycleEvent
import me.saket.telephoto.zoomable.ZoomSpec import me.saket.telephoto.zoomable.ZoomSpec
@ -93,7 +93,7 @@ private fun MediaImageView(
ZoomableAsyncImage( ZoomableAsyncImage(
modifier = modifier.fillMaxSize(), modifier = modifier.fillMaxSize(),
state = zoomableImageState, state = zoomableImageState,
model = localMedia?.model, model = localMedia?.uri,
contentDescription = "Image", contentDescription = "Image",
contentScale = ContentScale.Fit, contentScale = ContentScale.Fit,
) )
@ -107,7 +107,6 @@ fun MediaVideoView(
onReady: () -> Unit, onReady: () -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
val context = LocalContext.current val context = LocalContext.current
val playerListener = object : Player.Listener { val playerListener = object : Player.Listener {
override fun onRenderedFirstFrame() { override fun onRenderedFirstFrame() {
@ -121,9 +120,9 @@ fun MediaVideoView(
this.prepare() this.prepare()
} }
} }
if (localMedia?.source != null) { if (localMedia?.uri != null) {
LaunchedEffect(localMedia.source) { LaunchedEffect(localMedia.uri) {
val mediaItem = localMedia.toMediaItem() val mediaItem = MediaItem.fromUri(localMedia.uri)
exoPlayer.setMediaItem(mediaItem) exoPlayer.setMediaItem(mediaItem)
} }
} else { } else {

View file

@ -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)
}
}

View file

@ -16,11 +16,11 @@
package io.element.android.features.messages.impl.media.viewer package io.element.android.features.messages.impl.media.viewer
import android.net.Uri
import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import androidx.media3.common.MimeTypes import androidx.media3.common.MimeTypes
import io.element.android.features.messages.impl.media.local.LocalMedia import io.element.android.features.messages.impl.media.local.LocalMedia
import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Async
import java.io.File
open class MediaViewerStateProvider : PreviewParameterProvider<MediaViewerState> { open class MediaViewerStateProvider : PreviewParameterProvider<MediaViewerState> {
override val values: Sequence<MediaViewerState> override val values: Sequence<MediaViewerState>
@ -31,14 +31,14 @@ open class MediaViewerStateProvider : PreviewParameterProvider<MediaViewerState>
aMediaViewerState( aMediaViewerState(
Async.Success( Async.Success(
LocalMedia( LocalMedia(
LocalMedia.Source.FromFile(File("")), MimeTypes.IMAGE_JPEG, "an image file", 100L Uri.EMPTY, MimeTypes.IMAGE_JPEG, "an image file", 100L
) )
), ),
), ),
aMediaViewerState( aMediaViewerState(
Async.Success( Async.Success(
LocalMedia( LocalMedia(
LocalMedia.Source.FromFile(File("")), MimeTypes.VIDEO_MP4, "a video file", 100L Uri.EMPTY, MimeTypes.VIDEO_MP4, "a video file", 100L
) )
), ),
) )

View file

@ -31,7 +31,6 @@ import androidx.media3.common.MimeTypes
import androidx.media3.common.util.UnstableApi import androidx.media3.common.util.UnstableApi
import io.element.android.features.messages.impl.attachments.Attachment 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.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.features.messages.impl.media.local.LocalMediaFactory
import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.core.data.StableCharSequence import io.element.android.libraries.core.data.StableCharSequence
@ -192,7 +191,8 @@ class MessageComposerPresenter @Inject constructor(
when (attachment) { when (attachment) {
is Attachment.Media -> { is Attachment.Media -> {
sendMedia( sendMedia(
media = attachment.localMedia, uri = attachment.localMedia.uri,
mimeType = attachment.localMedia.mimeType,
attachmentState = attachmentState attachmentState = attachmentState
) )
} }
@ -226,12 +226,11 @@ class MessageComposerPresenter @Inject constructor(
} }
private suspend fun sendMedia( private suspend fun sendMedia(
media: LocalMedia, uri: Uri,
mimeType: String,
attachmentState: MutableState<AttachmentsState>, attachmentState: MutableState<AttachmentsState>,
) { ) {
if (media.source !is LocalMedia.Source.FromUri) error("Attachment should use Uri") mediaSender.sendMedia(uri, mimeType, compressIfPossible = false)
val uri = media.source.uri
mediaSender.sendMedia(uri, media.mimeType, compressIfPossible = false)
.onSuccess { .onSuccess {
attachmentState.value = AttachmentsState.None attachmentState.value = AttachmentsState.None
}.onFailure { }.onFailure {

View file

@ -20,6 +20,7 @@ import android.net.Uri
import androidx.media3.common.MimeTypes import androidx.media3.common.MimeTypes
import io.element.android.features.messages.impl.attachments.Attachment import io.element.android.features.messages.impl.attachments.Attachment
import io.element.android.features.messages.impl.media.local.LocalMedia import io.element.android.features.messages.impl.media.local.LocalMedia
import io.mockk.mockk
fun aLocalMedia( fun aLocalMedia(
uri: Uri, uri: Uri,
@ -27,7 +28,7 @@ fun aLocalMedia(
name: String = "a media", name: String = "a media",
size: Long = 1000, size: Long = 1000,
) = LocalMedia( ) = LocalMedia(
source = LocalMedia.Source.FromUri(uri), uri = uri,
mimeType = mimeType, mimeType = mimeType,
name = name, name = name,
size = size, size = size,

View file

@ -17,6 +17,7 @@
package io.element.android.libraries.matrix.api.media package io.element.android.libraries.matrix.api.media
import java.io.Closeable import java.io.Closeable
import java.io.File
/** /**
* A wrapper around a media file on the disk. * A wrapper around a media file on the disk.
@ -25,3 +26,7 @@ import java.io.Closeable
interface MediaFile : Closeable { interface MediaFile : Closeable {
fun path(): String fun path(): String
} }
fun MediaFile.toFile(): File {
return File(path())
}