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 220b4554dc..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 @@ -29,13 +29,12 @@ open class AttachmentsPreviewStateProvider : PreviewParameterProvider = Async.Uninitialized) = AttachmentsPreviewState( attachment = Attachment.Media( - localMedia = LocalMedia("".toUri(), mimeType = MimeTypes.OctetStream), + 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/attachments/preview/AttachmentsPreviewView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewView.kt index 3ff750f101..28c78da1be 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewView.kt @@ -79,13 +79,13 @@ fun AttachmentsPreviewView( onSendClicked = ::postSendAttachment, onDismiss = onDismiss ) - AttachmentSendStateView( - sendActionState = state.sendActionState, - onRetryClicked = ::postSendAttachment, - onRetryDismissed = ::postClearSendState - ) } } + AttachmentSendStateView( + sendActionState = state.sendActionState, + onRetryClicked = ::postSendAttachment, + onRetryDismissed = ::postClearSendState + ) } @Composable @@ -106,7 +106,6 @@ private fun AttachmentSendStateView( onRetry = onRetryClicked ) } - else -> Unit } } @@ -115,10 +114,11 @@ private fun AttachmentSendStateView( private fun AttachmentPreviewContent( attachment: Attachment, onSendClicked: () -> Unit, - onDismiss: () -> Unit + onDismiss: () -> Unit, + modifier: Modifier = Modifier, ) { Column( - modifier = Modifier + modifier = modifier .fillMaxSize() .padding(top = 24.dp) ) { 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 ebc9bb9490..bc2f1a066c 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 @@ -20,6 +20,8 @@ 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 import io.element.android.libraries.core.mimetype.MimeTypes import io.element.android.libraries.di.AppScope import io.element.android.libraries.di.ApplicationContext @@ -32,13 +34,19 @@ class AndroidLocalMediaFactory @Inject constructor( ) : LocalMediaFactory { override fun createFromMediaFile(mediaFile: MediaFile, mimeType: String?): LocalMedia { - val resolvedMimeType = mimeType ?: MimeTypes.OctetStream val uri = mediaFile.path().toUri() - return LocalMedia(uri, resolvedMimeType) + return createFromUri(uri, mimeType) } override fun createFromUri(uri: Uri, mimeType: String?): LocalMedia { val resolvedMimeType = mimeType ?: context.contentResolver.getType(uri) ?: MimeTypes.OctetStream - return LocalMedia(uri, resolvedMimeType) + val fileName = context.getFileName(uri) + val fileSize = context.getFileSize(uri) + return LocalMedia( + 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 016fa382c3..41ff1025ff 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 @@ -25,6 +25,8 @@ import kotlinx.parcelize.Parcelize data class LocalMedia( val uri: Uri, val mimeType: String, + val name: String?, + val size: Long, ) : 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 f814dd6662..2365ac317f 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 @@ -19,6 +19,7 @@ package io.element.android.features.messages.impl.media.local import android.annotation.SuppressLint import android.view.ViewGroup.LayoutParams.MATCH_PARENT import android.widget.FrameLayout +import androidx.compose.foundation.Image import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -26,6 +27,8 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext +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 @@ -34,6 +37,7 @@ import androidx.media3.common.util.UnstableApi import androidx.media3.exoplayer.ExoPlayer import androidx.media3.ui.AspectRatioFrameLayout import androidx.media3.ui.PlayerView +import io.element.android.libraries.designsystem.R import io.element.android.libraries.designsystem.utils.OnLifecycleEvent import me.saket.telephoto.zoomable.ZoomSpec import me.saket.telephoto.zoomable.coil.ZoomableAsyncImage @@ -64,6 +68,13 @@ private fun MediaImageView( localMedia: LocalMedia, modifier: Modifier = Modifier, ) { + if (LocalInspectionMode.current) { + Image( + painter = painterResource(id = R.drawable.sample_background), + modifier = modifier.fillMaxSize(), + contentDescription = null, + ) + } val zoomableState = rememberZoomableState( zoomSpec = ZoomSpec(maxZoomFactor = 3f) ) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/local/UriToFileMapper.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/local/UriToFileMapper.kt index 417219c60e..d27b667883 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/local/UriToFileMapper.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/local/UriToFileMapper.kt @@ -40,7 +40,8 @@ object UriToFileMapper { } private fun isApplicable(data: Uri): Boolean { - return data.scheme.let { it == null || it == ContentResolver.SCHEME_FILE } && + return !isAssetUri(data) && + data.scheme.let { it == null || it == ContentResolver.SCHEME_FILE } && data.path.orEmpty().startsWith('/') && data.firstPathSegment != null } 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 66c41a0eba..45ac530b8e 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,19 +16,30 @@ 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 open class MediaViewerStateProvider : PreviewParameterProvider { override val values: Sequence get() = sequenceOf( aMediaViewerState(), - // Add other states here + aMediaViewerState(Async.Loading()), + aMediaViewerState(Async.Failure(IllegalStateException())), + aMediaViewerState( + Async.Success( + LocalMedia( + Uri.EMPTY, MimeTypes.IMAGE_JPEG, "a file", 100L + ) + ), + ) ) } -fun aMediaViewerState() = MediaViewerState( +fun aMediaViewerState(downloadedMedia: Async = Async.Uninitialized) = MediaViewerState( name = "A media", - downloadedMedia = Async.Uninitialized, + downloadedMedia = downloadedMedia, 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 9dbcaf5559..b5ef22b596 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 @@ -63,9 +63,7 @@ fun MediaViewerView( when (state.downloadedMedia) { is Async.Success -> LocalMediaView(state.downloadedMedia.state) is Async.Failure -> ErrorView(stringResource(id = StringR.string.error_unknown), ::onRetry) - else -> CircularProgressIndicator( - strokeWidth = 2.dp, - ) + else -> CircularProgressIndicator() } } } @@ -92,11 +90,6 @@ private fun ErrorView( } } -@Preview -@Composable -fun MediaViewerViewLightPreview(@PreviewParameter(MediaViewerStateProvider::class) state: MediaViewerState) = - ElementPreviewLight { ContentToPreview(state) } - @Preview @Composable fun MediaViewerViewDarkPreview(@PreviewParameter(MediaViewerStateProvider::class) state: MediaViewerState) = diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/attachments/AttachmentsPreviewPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/attachments/AttachmentsPreviewPresenterTest.kt index 0b16a254a1..27657bb560 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/attachments/AttachmentsPreviewPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/attachments/AttachmentsPreviewPresenterTest.kt @@ -84,7 +84,7 @@ class AttachmentsPreviewPresenterTest { } private fun anAttachmentsPreviewPresenter( - localMedia: LocalMedia = aLocalMedia(mimeType = MimeTypes.IMAGE_JPEG), + localMedia: LocalMedia = aLocalMedia(MimeTypes.IMAGE_JPEG), room: MatrixRoom = FakeMatrixRoom() ): AttachmentsPreviewPresenter { return AttachmentsPreviewPresenter( 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 5b63bc1e68..df4aa1f258 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 @@ -22,11 +22,15 @@ import io.element.android.features.messages.impl.media.local.LocalMedia import io.mockk.mockk fun aLocalMedia( + mimeType: String, uri: Uri = mockk("localMediaUri"), - mimeType: String + name: String = "a media", + size: Long = 1000, ) = LocalMedia( uri = uri, - mimeType = mimeType + mimeType = mimeType, + name = name, + size = size, ) fun aMediaAttachment(localMedia: LocalMedia, compressIfPossible: Boolean = true) = Attachment.Media( diff --git a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/file/Context.kt b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/file/Context.kt index a5fab7545c..6bf784b100 100644 --- a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/file/Context.kt +++ b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/file/Context.kt @@ -27,6 +27,20 @@ fun Context.getFileName(uri: Uri): String? = when (uri.scheme) { else -> uri.path?.let(::File)?.name } +fun Context.getFileSize(uri: Uri): Long { + return when (uri.scheme) { + ContentResolver.SCHEME_CONTENT -> getContentFileSize(uri) + else -> uri.path?.let(::File)?.length() + } ?: 0 +} + +private fun Context.getContentFileSize(uri: Uri): Long? = runCatching { + contentResolver.query(uri, null, null, null, null)?.use { cursor -> + cursor.moveToFirst() + return@use cursor.getColumnIndexOrThrow(OpenableColumns.SIZE).let(cursor::getLong) + } +}.getOrNull() + private fun Context.getContentFileName(uri: Uri): String? = runCatching { contentResolver.query(uri, null, null, null, null)?.use { cursor -> cursor.moveToFirst()