Media: improve a bit the viewers

This commit is contained in:
ganfra 2023-05-24 22:19:18 +02:00
parent 922b63e746
commit 1c48f0afac
11 changed files with 71 additions and 28 deletions

View file

@ -29,13 +29,12 @@ open class AttachmentsPreviewStateProvider : PreviewParameterProvider<Attachment
anAttachmentsPreviewState(), anAttachmentsPreviewState(),
anAttachmentsPreviewState(sendActionState = Async.Loading()), anAttachmentsPreviewState(sendActionState = Async.Loading()),
anAttachmentsPreviewState(sendActionState = Async.Failure(RuntimeException())), anAttachmentsPreviewState(sendActionState = Async.Failure(RuntimeException())),
// Add other states here
) )
} }
fun anAttachmentsPreviewState(sendActionState: Async<Unit> = Async.Uninitialized) = AttachmentsPreviewState( fun anAttachmentsPreviewState(sendActionState: Async<Unit> = Async.Uninitialized) = AttachmentsPreviewState(
attachment = Attachment.Media( attachment = Attachment.Media(
localMedia = LocalMedia("".toUri(), mimeType = MimeTypes.OctetStream), localMedia = LocalMedia("path".toUri(), MimeTypes.Jpeg, "an image", 1000L),
compressIfPossible = true compressIfPossible = true
), ),
sendActionState = sendActionState, sendActionState = sendActionState,

View file

@ -79,13 +79,13 @@ fun AttachmentsPreviewView(
onSendClicked = ::postSendAttachment, onSendClicked = ::postSendAttachment,
onDismiss = onDismiss onDismiss = onDismiss
) )
AttachmentSendStateView(
sendActionState = state.sendActionState,
onRetryClicked = ::postSendAttachment,
onRetryDismissed = ::postClearSendState
)
} }
} }
AttachmentSendStateView(
sendActionState = state.sendActionState,
onRetryClicked = ::postSendAttachment,
onRetryDismissed = ::postClearSendState
)
} }
@Composable @Composable
@ -106,7 +106,6 @@ private fun AttachmentSendStateView(
onRetry = onRetryClicked onRetry = onRetryClicked
) )
} }
else -> Unit else -> Unit
} }
} }
@ -115,10 +114,11 @@ private fun AttachmentSendStateView(
private fun AttachmentPreviewContent( private fun AttachmentPreviewContent(
attachment: Attachment, attachment: Attachment,
onSendClicked: () -> Unit, onSendClicked: () -> Unit,
onDismiss: () -> Unit onDismiss: () -> Unit,
modifier: Modifier = Modifier,
) { ) {
Column( Column(
modifier = Modifier modifier = modifier
.fillMaxSize() .fillMaxSize()
.padding(top = 24.dp) .padding(top = 24.dp)
) { ) {

View file

@ -20,6 +20,8 @@ import android.content.Context
import android.net.Uri import android.net.Uri
import androidx.core.net.toUri 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.getFileSize
import io.element.android.libraries.core.mimetype.MimeTypes 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
@ -32,13 +34,19 @@ 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.path().toUri() val uri = mediaFile.path().toUri()
return LocalMedia(uri, resolvedMimeType) return createFromUri(uri, mimeType)
} }
override fun createFromUri(uri: Uri, mimeType: String?): LocalMedia { override fun createFromUri(uri: Uri, mimeType: String?): LocalMedia {
val resolvedMimeType = mimeType ?: context.contentResolver.getType(uri) ?: MimeTypes.OctetStream 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
)
} }
} }

View file

@ -25,6 +25,8 @@ import kotlinx.parcelize.Parcelize
data class LocalMedia( data class LocalMedia(
val uri: Uri, val uri: Uri,
val mimeType: String, val mimeType: String,
val name: String?,
val size: Long,
) : Parcelable { ) : Parcelable {
/** /**

View file

@ -19,6 +19,7 @@ package io.element.android.features.messages.impl.media.local
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.view.ViewGroup.LayoutParams.MATCH_PARENT import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import android.widget.FrameLayout import android.widget.FrameLayout
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
@ -26,6 +27,8 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext 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.compose.ui.viewinterop.AndroidView
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.media3.common.MediaItem import androidx.media3.common.MediaItem
@ -34,6 +37,7 @@ import androidx.media3.common.util.UnstableApi
import androidx.media3.exoplayer.ExoPlayer import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.ui.AspectRatioFrameLayout import androidx.media3.ui.AspectRatioFrameLayout
import androidx.media3.ui.PlayerView import androidx.media3.ui.PlayerView
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
import me.saket.telephoto.zoomable.coil.ZoomableAsyncImage import me.saket.telephoto.zoomable.coil.ZoomableAsyncImage
@ -64,6 +68,13 @@ private fun MediaImageView(
localMedia: LocalMedia, localMedia: LocalMedia,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
if (LocalInspectionMode.current) {
Image(
painter = painterResource(id = R.drawable.sample_background),
modifier = modifier.fillMaxSize(),
contentDescription = null,
)
}
val zoomableState = rememberZoomableState( val zoomableState = rememberZoomableState(
zoomSpec = ZoomSpec(maxZoomFactor = 3f) zoomSpec = ZoomSpec(maxZoomFactor = 3f)
) )

View file

@ -40,7 +40,8 @@ object UriToFileMapper {
} }
private fun isApplicable(data: Uri): Boolean { 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 data.path.orEmpty().startsWith('/') && data.firstPathSegment != null
} }

View file

@ -16,19 +16,30 @@
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 io.element.android.features.messages.impl.media.local.LocalMedia
import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Async
open class MediaViewerStateProvider : PreviewParameterProvider<MediaViewerState> { open class MediaViewerStateProvider : PreviewParameterProvider<MediaViewerState> {
override val values: Sequence<MediaViewerState> override val values: Sequence<MediaViewerState>
get() = sequenceOf( get() = sequenceOf(
aMediaViewerState(), 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<LocalMedia> = Async.Uninitialized) = MediaViewerState(
name = "A media", name = "A media",
downloadedMedia = Async.Uninitialized, downloadedMedia = downloadedMedia,
eventSink = {} eventSink = {}
) )

View file

@ -63,9 +63,7 @@ fun MediaViewerView(
when (state.downloadedMedia) { when (state.downloadedMedia) {
is Async.Success -> LocalMediaView(state.downloadedMedia.state) is Async.Success -> LocalMediaView(state.downloadedMedia.state)
is Async.Failure -> ErrorView(stringResource(id = StringR.string.error_unknown), ::onRetry) is Async.Failure -> ErrorView(stringResource(id = StringR.string.error_unknown), ::onRetry)
else -> CircularProgressIndicator( else -> CircularProgressIndicator()
strokeWidth = 2.dp,
)
} }
} }
} }
@ -92,11 +90,6 @@ private fun ErrorView(
} }
} }
@Preview
@Composable
fun MediaViewerViewLightPreview(@PreviewParameter(MediaViewerStateProvider::class) state: MediaViewerState) =
ElementPreviewLight { ContentToPreview(state) }
@Preview @Preview
@Composable @Composable
fun MediaViewerViewDarkPreview(@PreviewParameter(MediaViewerStateProvider::class) state: MediaViewerState) = fun MediaViewerViewDarkPreview(@PreviewParameter(MediaViewerStateProvider::class) state: MediaViewerState) =

View file

@ -84,7 +84,7 @@ class AttachmentsPreviewPresenterTest {
} }
private fun anAttachmentsPreviewPresenter( private fun anAttachmentsPreviewPresenter(
localMedia: LocalMedia = aLocalMedia(mimeType = MimeTypes.IMAGE_JPEG), localMedia: LocalMedia = aLocalMedia(MimeTypes.IMAGE_JPEG),
room: MatrixRoom = FakeMatrixRoom() room: MatrixRoom = FakeMatrixRoom()
): AttachmentsPreviewPresenter { ): AttachmentsPreviewPresenter {
return AttachmentsPreviewPresenter( return AttachmentsPreviewPresenter(

View file

@ -22,11 +22,15 @@ import io.element.android.features.messages.impl.media.local.LocalMedia
import io.mockk.mockk import io.mockk.mockk
fun aLocalMedia( fun aLocalMedia(
mimeType: String,
uri: Uri = mockk("localMediaUri"), uri: Uri = mockk("localMediaUri"),
mimeType: String name: String = "a media",
size: Long = 1000,
) = LocalMedia( ) = LocalMedia(
uri = uri, uri = uri,
mimeType = mimeType mimeType = mimeType,
name = name,
size = size,
) )
fun aMediaAttachment(localMedia: LocalMedia, compressIfPossible: Boolean = true) = Attachment.Media( fun aMediaAttachment(localMedia: LocalMedia, compressIfPossible: Boolean = true) = Attachment.Media(

View file

@ -27,6 +27,20 @@ fun Context.getFileName(uri: Uri): String? = when (uri.scheme) {
else -> uri.path?.let(::File)?.name 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 { private fun Context.getContentFileName(uri: Uri): String? = runCatching {
contentResolver.query(uri, null, null, null, null)?.use { cursor -> contentResolver.query(uri, null, null, null, null)?.use { cursor ->
cursor.moveToFirst() cursor.moveToFirst()