Merge pull request #6933 from element-hq/feature/bma/filePreviewImprovement

[Media viewer] File preview improvement
This commit is contained in:
Benoit Marty
2026-06-02 15:19:09 +02:00
committed by GitHub
55 changed files with 181 additions and 128 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,
@@ -12,8 +12,8 @@ fun formatFileExtensionAndSize(extension: String, size: String?): String {
return buildString {
append(extension.uppercase())
if (size != null) {
append(' ')
append("($size)")
append(" • ")
append(size)
}
}
}
@@ -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(),
)
}
@@ -106,7 +106,7 @@ import me.saket.telephoto.zoomable.OverzoomEffect
import me.saket.telephoto.zoomable.ZoomSpec
import me.saket.telephoto.zoomable.rememberZoomableState
val topAppBarHeight = 88.dp
val topAppBarHeight = 112.dp
/**
* Ref: https://www.figma.com/design/pDlJZGBsri47FNTXMnEdXB/Compound-Android-Templates?node-id=3361-16623
@@ -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,
)
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:43ab4873fc5fc812bf18af50cbe620c83b273ee70305b5ea06a7aeabdf8dbc93
size 137637
oid sha256:58e3353ce8aecff415158d146347206b1dbf167d15523301847f47c2e81b1c5b
size 137215