From 23b5776474a1373dca080cfe0c6aa60665cf027f Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 29 Nov 2024 13:50:19 +0100 Subject: [PATCH] MediaViewer: iterate on design --- .../messages/impl/MessagesFlowNode.kt | 20 ++- .../libraries/mediaviewer/api/MediaInfo.kt | 43 ++++- .../impl/DefaultMediaViewerEntryPoint.kt | 4 +- .../impl/local/AndroidLocalMediaFactory.kt | 12 +- .../impl/local/DefaultLocalMediaRenderer.kt | 1 + .../mediaviewer/impl/local/LocalMediaView.kt | 2 + .../impl/local/video/MediaVideoView.kt | 9 +- .../impl/viewer/MediaViewerStateProvider.kt | 96 ++++++----- .../impl/viewer/MediaViewerView.kt | 151 ++++++++++++++---- .../local/AndroidLocalMediaFactoryTest.kt | 8 +- .../mediaviewer/test/FakeLocalMediaFactory.kt | 4 +- 11 files changed, 264 insertions(+), 86 deletions(-) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesFlowNode.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesFlowNode.kt index 9d5ed6ff8c..e80f99a67c 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesFlowNode.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesFlowNode.kt @@ -337,7 +337,9 @@ class MessagesFlowNode @AssistedInject constructor( caption = event.content.caption, mimeType = event.content.mimeType, formattedFileSize = event.content.formattedFileSize, - fileExtension = event.content.fileExtension + fileExtension = event.content.fileExtension, + senderName = event.safeSenderName, + dateSent = event.sentTime, ), mediaSource = event.content.mediaSource, thumbnailSource = event.content.thumbnailSource, @@ -355,7 +357,9 @@ class MessagesFlowNode @AssistedInject constructor( caption = event.content.caption, mimeType = event.content.mimeType, formattedFileSize = event.content.formattedFileSize, - fileExtension = event.content.fileExtension + fileExtension = event.content.fileExtension, + senderName = event.safeSenderName, + dateSent = event.sentTime, ), mediaSource = event.content.preferredMediaSource, thumbnailSource = event.content.thumbnailSource, @@ -373,7 +377,9 @@ class MessagesFlowNode @AssistedInject constructor( caption = event.content.caption, mimeType = event.content.mimeType, formattedFileSize = event.content.formattedFileSize, - fileExtension = event.content.fileExtension + fileExtension = event.content.fileExtension, + senderName = event.safeSenderName, + dateSent = event.sentTime, ), mediaSource = event.content.videoSource, thumbnailSource = event.content.thumbnailSource, @@ -388,7 +394,9 @@ class MessagesFlowNode @AssistedInject constructor( caption = event.content.caption, mimeType = event.content.mimeType, formattedFileSize = event.content.formattedFileSize, - fileExtension = event.content.fileExtension + fileExtension = event.content.fileExtension, + senderName = event.safeSenderName, + dateSent = event.sentTime, ), mediaSource = event.content.fileSource, thumbnailSource = event.content.thumbnailSource, @@ -403,7 +411,9 @@ class MessagesFlowNode @AssistedInject constructor( caption = event.content.caption, mimeType = event.content.mimeType, formattedFileSize = event.content.formattedFileSize, - fileExtension = event.content.fileExtension + fileExtension = event.content.fileExtension, + senderName = event.safeSenderName, + dateSent = event.sentTime, ), mediaSource = event.content.mediaSource, thumbnailSource = null, diff --git a/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/MediaInfo.kt b/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/MediaInfo.kt index 93cd2f9378..5c317b1d6a 100644 --- a/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/MediaInfo.kt +++ b/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/MediaInfo.kt @@ -18,44 +18,73 @@ data class MediaInfo( val mimeType: String, val formattedFileSize: String, val fileExtension: String, + val senderName: String?, + val dateSent: String?, ) : Parcelable -fun anImageMediaInfo(): MediaInfo = MediaInfo( +fun anImageMediaInfo( + caption: String? = null, + senderName: String? = null, + dateSent: String? = null, +): MediaInfo = MediaInfo( filename = "an image file.jpg", - caption = null, + caption = caption, mimeType = MimeTypes.Jpeg, formattedFileSize = "4MB", fileExtension = "jpg", + senderName = senderName, + dateSent = dateSent, ) -fun aVideoMediaInfo(): MediaInfo = MediaInfo( +fun aVideoMediaInfo( + caption: String? = null, + senderName: String? = null, + dateSent: String? = null, +): MediaInfo = MediaInfo( filename = "a video file.mp4", - caption = null, + caption = caption, mimeType = MimeTypes.Mp4, formattedFileSize = "14MB", fileExtension = "mp4", + senderName = senderName, + dateSent = dateSent, ) -fun aPdfMediaInfo(): MediaInfo = MediaInfo( +fun aPdfMediaInfo( + senderName: String? = null, + dateSent: String? = null, +): MediaInfo = MediaInfo( filename = "a pdf file.pdf", caption = null, mimeType = MimeTypes.Pdf, formattedFileSize = "23MB", fileExtension = "pdf", + senderName = senderName, + dateSent = dateSent, ) -fun anApkMediaInfo(): MediaInfo = MediaInfo( +fun anApkMediaInfo( + senderName: String? = null, + dateSent: String? = null, +): MediaInfo = MediaInfo( filename = "an apk file.apk", caption = null, mimeType = MimeTypes.Apk, formattedFileSize = "50MB", fileExtension = "apk", + senderName = senderName, + dateSent = dateSent, ) -fun anAudioMediaInfo(): MediaInfo = MediaInfo( +fun anAudioMediaInfo( + senderName: String? = null, + dateSent: String? = null, +): MediaInfo = MediaInfo( filename = "an audio file.mp3", caption = null, mimeType = MimeTypes.Mp3, formattedFileSize = "7MB", fileExtension = "mp3", + senderName = senderName, + dateSent = dateSent, ) diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/DefaultMediaViewerEntryPoint.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/DefaultMediaViewerEntryPoint.kt index 5bd16c840b..86d7bca722 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/DefaultMediaViewerEntryPoint.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/DefaultMediaViewerEntryPoint.kt @@ -46,7 +46,9 @@ class DefaultMediaViewerEntryPoint @Inject constructor() : MediaViewerEntryPoint caption = null, mimeType = mimeType, formattedFileSize = "", - fileExtension = "" + fileExtension = "", + senderName = null, + dateSent = null, ), mediaSource = MediaSource(url = avatarUrl), thumbnailSource = null, diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaFactory.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaFactory.kt index 461563f465..8b5163cf6c 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaFactory.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaFactory.kt @@ -41,6 +41,8 @@ class AndroidLocalMediaFactory @Inject constructor( name = mediaInfo.filename, caption = mediaInfo.caption, formattedFileSize = mediaInfo.formattedFileSize, + senderName = mediaInfo.senderName, + dateSent = mediaInfo.dateSent, ) override fun createFromUri( @@ -54,6 +56,8 @@ class AndroidLocalMediaFactory @Inject constructor( name = name, caption = null, formattedFileSize = formattedFileSize, + senderName = null, + dateSent = null, ) private fun createFromUri( @@ -61,7 +65,9 @@ class AndroidLocalMediaFactory @Inject constructor( mimeType: String?, name: String?, caption: String?, - formattedFileSize: String? + formattedFileSize: String?, + senderName: String?, + dateSent: String?, ): LocalMedia { val resolvedMimeType = mimeType ?: context.getMimeType(uri) ?: MimeTypes.OctetStream val fileName = name ?: context.getFileName(uri) ?: "" @@ -74,7 +80,9 @@ class AndroidLocalMediaFactory @Inject constructor( filename = fileName, caption = caption, formattedFileSize = fileSize, - fileExtension = fileExtension + fileExtension = fileExtension, + senderName = senderName, + dateSent = dateSent, ) ) } diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/DefaultLocalMediaRenderer.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/DefaultLocalMediaRenderer.kt index 6b1005dccf..b801ebe00f 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/DefaultLocalMediaRenderer.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/DefaultLocalMediaRenderer.kt @@ -29,6 +29,7 @@ class DefaultLocalMediaRenderer @Inject constructor() : LocalMediaRenderer { ) LocalMediaView( modifier = Modifier.fillMaxSize(), + bottomPaddingInPixels = 0, localMedia = localMedia, localMediaViewState = localMediaViewState, onClick = {} diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/LocalMediaView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/LocalMediaView.kt index 44392e27b9..5d0a2993df 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/LocalMediaView.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/LocalMediaView.kt @@ -22,6 +22,7 @@ import io.element.android.libraries.mediaviewer.impl.local.video.MediaVideoView @Composable fun LocalMediaView( localMedia: LocalMedia?, + bottomPaddingInPixels: Int, onClick: () -> Unit, modifier: Modifier = Modifier, localMediaViewState: LocalMediaViewState = rememberLocalMediaViewState(), @@ -37,6 +38,7 @@ fun LocalMediaView( ) mimeType.isMimeTypeVideo() -> MediaVideoView( localMediaViewState = localMediaViewState, + bottomPaddingInPixels = bottomPaddingInPixels, localMedia = localMedia, modifier = modifier, ) diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/video/MediaVideoView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/video/MediaVideoView.kt index d978549192..6d172823e7 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/video/MediaVideoView.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/video/MediaVideoView.kt @@ -14,6 +14,7 @@ import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.derivedStateOf @@ -37,6 +38,7 @@ import androidx.media3.ui.PlayerView import io.element.android.compound.theme.ElementTheme import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.libraries.designsystem.text.toDp import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.utils.KeepScreenOn import io.element.android.libraries.designsystem.utils.OnLifecycleEvent @@ -51,6 +53,7 @@ import kotlin.time.Duration.Companion.seconds @Composable fun MediaVideoView( localMediaViewState: LocalMediaViewState, + bottomPaddingInPixels: Int, localMedia: LocalMedia?, modifier: Modifier = Modifier, ) { @@ -66,6 +69,7 @@ fun MediaVideoView( } ExoPlayerMediaVideoView( localMediaViewState = localMediaViewState, + bottomPaddingInPixels = bottomPaddingInPixels, exoPlayer = exoPlayer, localMedia = localMedia, modifier = modifier, @@ -76,6 +80,7 @@ fun MediaVideoView( @Composable private fun ExoPlayerMediaVideoView( localMediaViewState: LocalMediaViewState, + bottomPaddingInPixels: Int, exoPlayer: ExoPlayer, localMedia: LocalMedia?, modifier: Modifier = Modifier, @@ -232,7 +237,8 @@ private fun ExoPlayerMediaVideoView( }, modifier = Modifier .fillMaxWidth() - .align(Alignment.BottomCenter), + .align(Alignment.BottomCenter) + .padding(bottom = bottomPaddingInPixels.toDp()), ) } @@ -254,6 +260,7 @@ private fun ExoPlayerMediaVideoView( internal fun MediaVideoViewPreview() = ElementPreview { MediaVideoView( modifier = Modifier.fillMaxSize(), + bottomPaddingInPixels = 0, localMediaViewState = rememberLocalMediaViewState(), localMedia = null, ) diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerStateProvider.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerStateProvider.kt index 4ffcb75b07..6c7a9fb704 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerStateProvider.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerStateProvider.kt @@ -24,52 +24,72 @@ open class MediaViewerStateProvider : PreviewParameterProvider aMediaViewerState(), aMediaViewerState(AsyncData.Loading()), aMediaViewerState(AsyncData.Failure(IllegalStateException("error"))), - aMediaViewerState( - AsyncData.Success( - LocalMedia(Uri.EMPTY, anImageMediaInfo()) - ), - anImageMediaInfo(), - ), - aMediaViewerState( - AsyncData.Success( - LocalMedia(Uri.EMPTY, aVideoMediaInfo()) - ), - aVideoMediaInfo(), - ), - aMediaViewerState( - AsyncData.Success( - LocalMedia(Uri.EMPTY, aPdfMediaInfo()) - ), - aPdfMediaInfo(), - ), + anImageMediaInfo( + senderName = "Sally Sanderson", + dateSent = "21 NOV, 2024", + caption = "A caption", + ).let { + aMediaViewerState( + AsyncData.Success( + LocalMedia(Uri.EMPTY, it) + ), + it, + ) + }, + aVideoMediaInfo( + senderName = "Sally Sanderson", + dateSent = "21 NOV, 2024", + caption = "A caption", + ).let { + aMediaViewerState( + AsyncData.Success( + LocalMedia(Uri.EMPTY, it) + ), + it, + ) + }, + aPdfMediaInfo().let { + aMediaViewerState( + AsyncData.Success( + LocalMedia(Uri.EMPTY, it) + ), + it, + ) + }, aMediaViewerState( AsyncData.Loading(), anApkMediaInfo(), ), - aMediaViewerState( - AsyncData.Success( - LocalMedia(Uri.EMPTY, anApkMediaInfo()) - ), - anApkMediaInfo(), - ), + anApkMediaInfo().let { + aMediaViewerState( + AsyncData.Success( + LocalMedia(Uri.EMPTY, it) + ), + it, + ) + }, aMediaViewerState( AsyncData.Loading(), anAudioMediaInfo(), ), - aMediaViewerState( - AsyncData.Success( - LocalMedia(Uri.EMPTY, anAudioMediaInfo()) - ), - anAudioMediaInfo(), - ), - aMediaViewerState( - AsyncData.Success( - LocalMedia(Uri.EMPTY, anImageMediaInfo()) - ), - anImageMediaInfo(), - canDownload = false, - canShare = false, - ), + anAudioMediaInfo().let { + aMediaViewerState( + AsyncData.Success( + LocalMedia(Uri.EMPTY, it) + ), + it, + ) + }, + anImageMediaInfo().let { + aMediaViewerState( + AsyncData.Success( + LocalMedia(Uri.EMPTY, it) + ), + it, + canDownload = false, + canShare = false, + ) + }, ) } diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerView.kt index ae5e9401cb..1e3c264d14 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerView.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerView.kt @@ -16,10 +16,14 @@ import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.navigationBarsPadding +import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.OpenInNew import androidx.compose.material3.ExperimentalMaterial3Api @@ -28,6 +32,7 @@ import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberUpdatedState @@ -36,12 +41,15 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.platform.LocalInspectionMode import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp import coil.compose.AsyncImage +import io.element.android.compound.theme.ElementTheme import io.element.android.compound.tokens.generated.CompoundIcons import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.core.mimetype.MimeTypes @@ -51,6 +59,7 @@ import io.element.android.libraries.designsystem.preview.ElementPreviewDark import io.element.android.libraries.designsystem.theme.components.Icon import io.element.android.libraries.designsystem.theme.components.IconButton import io.element.android.libraries.designsystem.theme.components.Scaffold +import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.theme.components.TopAppBar import io.element.android.libraries.designsystem.utils.snackbar.SnackbarHost import io.element.android.libraries.designsystem.utils.snackbar.rememberSnackbarHostState @@ -80,6 +89,7 @@ fun MediaViewerView( val snackbarHostState = rememberSnackbarHostState(snackbarMessage = state.snackbarMessage) var showOverlay by remember { mutableStateOf(true) } + var bottomPaddingInPixels by remember { mutableIntStateOf(0) } BackHandler { onBackClick() } Scaffold( modifier, @@ -88,6 +98,7 @@ fun MediaViewerView( ) { MediaViewerPage( showOverlay = showOverlay, + bottomPaddingInPixels = bottomPaddingInPixels, state = state, onDismiss = { onBackClick() @@ -97,14 +108,29 @@ fun MediaViewerView( } ) AnimatedVisibility(visible = showOverlay, enter = fadeIn(), exit = fadeOut()) { - MediaViewerTopBar( - actionsEnabled = state.downloadedMedia is AsyncData.Success, - mimeType = state.mediaInfo.mimeType, - onBackClick = onBackClick, - canDownload = state.canDownload, - canShare = state.canShare, - eventSink = state.eventSink - ) + Box( + modifier = Modifier + .fillMaxSize() + .navigationBarsPadding() + ) { + MediaViewerTopBar( + actionsEnabled = state.downloadedMedia is AsyncData.Success, + senderName = state.mediaInfo.senderName, + dateSent = state.mediaInfo.dateSent, + onBackClick = onBackClick, + eventSink = state.eventSink + ) + MediaViewerBottomBar( + modifier = Modifier.align(Alignment.BottomCenter), + actionsEnabled = state.downloadedMedia is AsyncData.Success, + canDownload = state.canDownload, + canShare = state.canShare, + mimeType = state.mediaInfo.mimeType, + caption = state.mediaInfo.caption, + onHeightChanged = { bottomPaddingInPixels = it }, + eventSink = state.eventSink + ) + } } } } @@ -112,6 +138,7 @@ fun MediaViewerView( @Composable private fun MediaViewerPage( showOverlay: Boolean, + bottomPaddingInPixels: Int, state: MediaViewerState, onDismiss: () -> Unit, onShowOverlayChange: (Boolean) -> Unit, @@ -148,8 +175,8 @@ private fun MediaViewerPage( Box( modifier = Modifier - .fillMaxSize() - .navigationBarsPadding() + .fillMaxSize() + .navigationBarsPadding() ) { Box(contentAlignment = Alignment.Center) { val zoomableState = rememberZoomableState( @@ -168,6 +195,7 @@ private fun MediaViewerPage( LocalMediaView( modifier = Modifier.fillMaxSize(), + bottomPaddingInPixels = bottomPaddingInPixels, localMediaViewState = localMediaViewState, localMedia = state.downloadedMedia.dataOrNull(), mediaInfo = state.mediaInfo, @@ -193,8 +221,8 @@ private fun MediaViewerPage( if (showProgress) { LinearProgressIndicator( modifier = Modifier - .fillMaxWidth() - .height(2.dp) + .fillMaxWidth() + .height(2.dp) ) } } @@ -246,23 +274,99 @@ private fun rememberShowProgress(downloadedMedia: AsyncData): Boolea return showProgress } +@Suppress("UNUSED_PARAMETER") @OptIn(ExperimentalMaterial3Api::class) @Composable private fun MediaViewerTopBar( actionsEnabled: Boolean, - canDownload: Boolean, - canShare: Boolean, - mimeType: String, + senderName: String?, + dateSent: String?, onBackClick: () -> Unit, eventSink: (MediaViewerEvents) -> Unit, ) { TopAppBar( - title = {}, + title = { + if (senderName != null && dateSent != null) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(end = 48.dp), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Text( + text = senderName, + style = ElementTheme.typography.fontBodyMdMedium, + color = ElementTheme.colors.textPrimary, + ) + Text( + text = dateSent, + style = ElementTheme.typography.fontBodySmRegular, + color = ElementTheme.colors.textPrimary, + ) + } + } + }, colors = TopAppBarDefaults.topAppBarColors( containerColor = Color.Transparent.copy(0.6f), ), navigationIcon = { BackButton(onClick = onBackClick) }, actions = { + // TODO Add action to open infos. + } + ) +} + +@Composable +private fun MediaViewerBottomBar( + actionsEnabled: Boolean, + canDownload: Boolean, + canShare: Boolean, + mimeType: String, + caption: String?, + onHeightChanged: (Int) -> Unit, + eventSink: (MediaViewerEvents) -> Unit, + modifier: Modifier = Modifier, +) { + Column( + modifier = modifier + .fillMaxWidth() + .background(Color(0x99101317)) + .onSizeChanged { + onHeightChanged(it.height) + }, + ) { + if (caption != null) { + Text( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + text = caption, + maxLines = 5, + overflow = TextOverflow.Ellipsis, + style = ElementTheme.typography.fontBodyLgRegular, + ) + } + Row( + modifier = modifier + .fillMaxWidth() + .padding(start = 8.dp, end = 8.dp, bottom = 8.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + if (canShare) { + IconButton( + enabled = actionsEnabled, + onClick = { + eventSink(MediaViewerEvents.Share) + }, + modifier = Modifier.align(Alignment.CenterVertically) + ) { + Icon( + imageVector = CompoundIcons.ShareAndroid(), + contentDescription = stringResource(id = CommonStrings.action_share) + ) + } + } + Spacer(modifier = Modifier.weight(1f)) IconButton( enabled = actionsEnabled, onClick = { @@ -293,21 +397,8 @@ private fun MediaViewerTopBar( ) } } - if (canShare) { - IconButton( - enabled = actionsEnabled, - onClick = { - eventSink(MediaViewerEvents.Share) - }, - ) { - Icon( - imageVector = CompoundIcons.ShareAndroid(), - contentDescription = stringResource(id = CommonStrings.action_share) - ) - } - } } - ) + } } @Composable diff --git a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaFactoryTest.kt b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaFactoryTest.kt index 5729c5310d..c341f6e751 100644 --- a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaFactoryTest.kt +++ b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaFactoryTest.kt @@ -11,6 +11,7 @@ import com.google.common.truth.Truth.assertThat import io.element.android.libraries.androidutils.filesize.FakeFileSizeFormatter import io.element.android.libraries.core.mimetype.MimeTypes import io.element.android.libraries.matrix.api.media.MediaFile +import io.element.android.libraries.matrix.test.A_USER_NAME import io.element.android.libraries.matrix.test.media.FakeMediaFile import io.element.android.libraries.mediaviewer.api.MediaInfo import io.element.android.libraries.mediaviewer.api.anImageMediaInfo @@ -25,7 +26,10 @@ class AndroidLocalMediaFactoryTest { @Test fun `test AndroidLocalMediaFactory`() { val sut = createAndroidLocalMediaFactory() - val result = sut.createFromMediaFile(aMediaFile(), anImageMediaInfo()) + val result = sut.createFromMediaFile(aMediaFile(), anImageMediaInfo( + senderName = A_USER_NAME, + dateSent = "12:34", + )) assertThat(result.uri.toString()).endsWith("aPath") assertThat(result.info).isEqualTo( MediaInfo( @@ -34,6 +38,8 @@ class AndroidLocalMediaFactoryTest { mimeType = MimeTypes.Jpeg, formattedFileSize = "4MB", fileExtension = "jpg", + senderName = A_USER_NAME, + dateSent = "12:34" ) ) } diff --git a/libraries/mediaviewer/test/src/main/kotlin/io/element/android/libraries/mediaviewer/test/FakeLocalMediaFactory.kt b/libraries/mediaviewer/test/src/main/kotlin/io/element/android/libraries/mediaviewer/test/FakeLocalMediaFactory.kt index df85f2d53d..a0f36c6f0f 100644 --- a/libraries/mediaviewer/test/src/main/kotlin/io/element/android/libraries/mediaviewer/test/FakeLocalMediaFactory.kt +++ b/libraries/mediaviewer/test/src/main/kotlin/io/element/android/libraries/mediaviewer/test/FakeLocalMediaFactory.kt @@ -36,7 +36,9 @@ class FakeLocalMediaFactory( caption = null, mimeType = mimeType ?: fallbackMimeType, formattedFileSize = formattedFileSize ?: fallbackFileSize, - fileExtension = fileExtensionExtractor.extractFromName(safeName) + fileExtension = fileExtensionExtractor.extractFromName(safeName), + senderName = null, + dateSent = null ) return aLocalMedia(uri, mediaInfo) }