Media: improve a bit the viewers
This commit is contained in:
parent
922b63e746
commit
1c48f0afac
11 changed files with 71 additions and 28 deletions
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
) {
|
) {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 = {}
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -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) =
|
||||||
|
|
|
||||||
|
|
@ -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(
|
||||||
|
|
|
||||||
|
|
@ -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(
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue