[Media viewer] Improve rendering of files

Closes #6899
Closes #6908
This commit is contained in:
Benoit Marty
2026-06-02 10:22:34 +02:00
parent a095190902
commit 97135272ec
6 changed files with 78 additions and 37 deletions
@@ -103,6 +103,30 @@ fun aPdfMediaInfo(
duration = null,
)
fun aZipMediaInfo(
filename: String = "a zip file.zip",
caption: String? = null,
formattedCaption: CharSequence? = null,
senderName: String? = null,
dateSent: String? = null,
dateSentFull: String? = null,
): MediaInfo = MediaInfo(
filename = filename,
fileSize = 12 * 1024 * 1024,
caption = caption,
formattedCaption = formattedCaption,
mimeType = MimeTypes.Pdf,
formattedFileSize = "45MB",
fileExtension = "zip",
senderId = UserId("@alice:server.org"),
senderName = senderName,
senderAvatar = null,
dateSent = dateSent,
dateSentFull = dateSentFull,
waveform = null,
duration = null,
)
fun anApkMediaInfo(
senderId: UserId? = UserId("@alice:server.org"),
senderName: String? = null,
@@ -41,6 +41,7 @@ class DefaultLocalMediaRenderer(
textFileViewer = textFileViewer,
audioFocus = audioFocus,
onClick = {},
onOpenWith = null,
)
}
}
@@ -31,6 +31,7 @@ fun LocalMediaView(
bottomPaddingInPixels: Int,
audioFocus: AudioFocus?,
onClick: () -> Unit,
onOpenWith: (() -> Unit)?,
textFileViewer: TextFileViewer,
modifier: Modifier = Modifier,
isDisplayed: Boolean = true,
@@ -80,7 +81,7 @@ fun LocalMediaView(
uri = localMedia?.uri,
info = mediaInfo,
modifier = modifier,
onClick = onClick,
onOpenWith = onOpenWith,
)
}
}
@@ -9,23 +9,16 @@
package io.element.android.libraries.mediaviewer.impl.local.file
import android.net.Uri
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.rotate
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.PreviewParameter
@@ -33,55 +26,54 @@ import androidx.compose.ui.unit.dp
import io.element.android.compound.theme.ElementTheme
import io.element.android.compound.tokens.generated.CompoundIcons
import io.element.android.libraries.core.bool.orFalse
import io.element.android.libraries.core.mimetype.MimeTypes
import io.element.android.libraries.core.mimetype.MimeTypes.isMimeTypeAudio
import io.element.android.libraries.designsystem.components.BigIcon
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.theme.components.Icon
import io.element.android.libraries.designsystem.theme.components.ButtonSize
import io.element.android.libraries.designsystem.theme.components.IconSource
import io.element.android.libraries.designsystem.theme.components.OutlinedButton
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.mediaviewer.api.MediaInfo
import io.element.android.libraries.mediaviewer.api.helper.formatFileExtensionAndSize
import io.element.android.libraries.mediaviewer.impl.R
import io.element.android.libraries.mediaviewer.impl.local.LocalMediaViewState
import io.element.android.libraries.mediaviewer.impl.local.rememberLocalMediaViewState
import io.element.android.libraries.ui.strings.CommonStrings
/**
* Ref: https://www.figma.com/design/pDlJZGBsri47FNTXMnEdXB/Compound-Android-Templates?node-id=3361-16623
*/
@Composable
fun MediaFileView(
localMediaViewState: LocalMediaViewState,
uri: Uri?,
info: MediaInfo?,
onClick: () -> Unit,
onOpenWith: (() -> Unit)?,
modifier: Modifier = Modifier,
) {
val isAudio = info?.mimeType.isMimeTypeAudio().orFalse()
localMediaViewState.isReady = uri != null
val interactionSource = remember { MutableInteractionSource() }
Box(
modifier = modifier
.padding(horizontal = 8.dp)
.clickable(
onClick = onClick,
interactionSource = interactionSource,
indication = null
),
.padding(horizontal = 8.dp),
contentAlignment = Alignment.Center
) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Box(
modifier = Modifier
.size(72.dp)
.clip(CircleShape)
.background(ElementTheme.colors.iconPrimary),
contentAlignment = Alignment.Center,
) {
Icon(
imageVector = if (isAudio) CompoundIcons.Audio() else CompoundIcons.Attachment(),
contentDescription = null,
tint = ElementTheme.colors.iconOnSolidPrimary,
modifier = Modifier
.size(32.dp)
.rotate(if (isAudio) 0f else -45f),
)
val isApk = info?.mimeType == MimeTypes.Apk
val icon = when {
isAudio -> CompoundIcons.Audio()
isApk -> CompoundIcons.Bug()
else -> CompoundIcons.Files()
}
BigIcon(
modifier = Modifier.align(Alignment.CenterHorizontally),
style = BigIcon.Style.Default(
vectorIcon = icon,
usePrimaryTint = true,
),
)
if (info != null) {
Spacer(modifier = Modifier.height(20.dp))
Text(
@@ -100,6 +92,20 @@ fun MediaFileView(
overflow = TextOverflow.Ellipsis,
color = ElementTheme.colors.textPrimary
)
if (onOpenWith != null) {
val (icon, textResId) = if (isApk) {
IconSource.Resource(R.drawable.ic_apk_install) to CommonStrings.common_install_apk_android
} else {
IconSource.Vector(CompoundIcons.PopOut()) to CommonStrings.action_open_with
}
OutlinedButton(
modifier = Modifier.padding(top = 24.dp),
size = ButtonSize.Small,
leadingIcon = icon,
onClick = onOpenWith,
text = stringResource(id = textResId),
)
}
}
}
}
@@ -115,6 +121,6 @@ internal fun MediaFileViewPreview(
localMediaViewState = rememberLocalMediaViewState(),
uri = null,
info = info,
onClick = {},
onOpenWith = {},
)
}
@@ -10,11 +10,15 @@ package io.element.android.libraries.mediaviewer.impl.local.file
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.libraries.mediaviewer.api.MediaInfo
import io.element.android.libraries.mediaviewer.api.aPdfMediaInfo
import io.element.android.libraries.mediaviewer.api.aZipMediaInfo
import io.element.android.libraries.mediaviewer.api.anApkMediaInfo
import io.element.android.libraries.mediaviewer.api.anAudioMediaInfo
open class MediaInfoFileProvider : PreviewParameterProvider<MediaInfo> {
override val values: Sequence<MediaInfo>
get() = sequenceOf(
aPdfMediaInfo(),
aZipMediaInfo(),
anAudioMediaInfo(),
anApkMediaInfo(),
)
}
@@ -239,6 +239,9 @@ fun MediaViewerView(
onRetry = {
state.eventSink(MediaViewerEvent.LoadMedia(dataForPage))
},
onOpenWith = {
state.eventSink(MediaViewerEvent.OpenWith(dataForPage))
},
onDismissError = {
state.eventSink(MediaViewerEvent.ClearLoadingError(dataForPage))
},
@@ -333,6 +336,7 @@ private fun MediaViewerPage(
isUserSelected: Boolean,
onDismiss: () -> Unit,
onRetry: () -> Unit,
onOpenWith: () -> Unit,
onDismissError: () -> Unit,
onShowOverlayChange: (Boolean) -> Unit,
audioFocus: AudioFocus?,
@@ -387,6 +391,7 @@ private fun MediaViewerPage(
currentOnShowOverlayChange(!currentShowOverlay)
}
},
onOpenWith = onOpenWith,
isUserSelected = isUserSelected,
audioFocus = audioFocus,
)