Media: finally revert to using only uri but with the proper scheme..
This commit is contained in:
@@ -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<Async<Unit>>,
|
||||
) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<AttachmentsPreviewState> {
|
||||
override val values: Sequence<AttachmentsPreviewState>
|
||||
@@ -34,7 +34,7 @@ open class AttachmentsPreviewStateProvider : PreviewParameterProvider<Attachment
|
||||
|
||||
fun anAttachmentsPreviewState(sendActionState: Async<Unit> = 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,
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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<MediaViewerState> {
|
||||
override val values: Sequence<MediaViewerState>
|
||||
@@ -31,14 +31,14 @@ open class MediaViewerStateProvider : PreviewParameterProvider<MediaViewerState>
|
||||
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
|
||||
)
|
||||
),
|
||||
)
|
||||
|
||||
@@ -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<AttachmentsState>,
|
||||
) {
|
||||
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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user