Merge pull request #3979 from element-hq/feature/bma/mediaCaption
MediaViewer: iterate on design
This commit is contained in:
commit
71e0ff7342
49 changed files with 429 additions and 227 deletions
|
|
@ -40,6 +40,7 @@ import io.element.android.features.messages.impl.timeline.TimelineController
|
|||
import io.element.android.features.messages.impl.timeline.debug.EventDebugInfoNode
|
||||
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemAudioContent
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContentWithAttachment
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemFileContent
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemImageContent
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemLocationContent
|
||||
|
|
@ -329,100 +330,93 @@ class MessagesFlowNode @AssistedInject constructor(
|
|||
}
|
||||
|
||||
private fun processEventClick(event: TimelineItem.Event): Boolean {
|
||||
return when (event.content) {
|
||||
val navTarget = when (event.content) {
|
||||
is TimelineItemImageContent -> {
|
||||
val navTarget = NavTarget.MediaViewer(
|
||||
mediaInfo = MediaInfo(
|
||||
filename = event.content.filename,
|
||||
caption = event.content.caption,
|
||||
mimeType = event.content.mimeType,
|
||||
formattedFileSize = event.content.formattedFileSize,
|
||||
fileExtension = event.content.fileExtension
|
||||
),
|
||||
buildMediaViewerNavTarget(
|
||||
event = event,
|
||||
content = event.content,
|
||||
mediaSource = event.content.mediaSource,
|
||||
thumbnailSource = event.content.thumbnailSource,
|
||||
)
|
||||
overlay.show(navTarget)
|
||||
true
|
||||
}
|
||||
is TimelineItemStickerContent -> {
|
||||
/* Sticker may have an empty url and no thumbnail
|
||||
if encrypted on certain bridges */
|
||||
if (event.content.preferredMediaSource != null) {
|
||||
val navTarget = NavTarget.MediaViewer(
|
||||
mediaInfo = MediaInfo(
|
||||
filename = event.content.filename,
|
||||
caption = event.content.caption,
|
||||
mimeType = event.content.mimeType,
|
||||
formattedFileSize = event.content.formattedFileSize,
|
||||
fileExtension = event.content.fileExtension
|
||||
),
|
||||
mediaSource = event.content.preferredMediaSource,
|
||||
event.content.preferredMediaSource?.let { preferredMediaSource ->
|
||||
buildMediaViewerNavTarget(
|
||||
event = event,
|
||||
content = event.content,
|
||||
mediaSource = preferredMediaSource,
|
||||
thumbnailSource = event.content.thumbnailSource,
|
||||
)
|
||||
overlay.show(navTarget)
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
is TimelineItemVideoContent -> {
|
||||
val navTarget = NavTarget.MediaViewer(
|
||||
mediaInfo = MediaInfo(
|
||||
filename = event.content.filename,
|
||||
caption = event.content.caption,
|
||||
mimeType = event.content.mimeType,
|
||||
formattedFileSize = event.content.formattedFileSize,
|
||||
fileExtension = event.content.fileExtension
|
||||
),
|
||||
mediaSource = event.content.videoSource,
|
||||
buildMediaViewerNavTarget(
|
||||
event = event,
|
||||
content = event.content,
|
||||
mediaSource = event.content.mediaSource,
|
||||
thumbnailSource = event.content.thumbnailSource,
|
||||
)
|
||||
overlay.show(navTarget)
|
||||
true
|
||||
}
|
||||
is TimelineItemFileContent -> {
|
||||
val navTarget = NavTarget.MediaViewer(
|
||||
mediaInfo = MediaInfo(
|
||||
filename = event.content.filename,
|
||||
caption = event.content.caption,
|
||||
mimeType = event.content.mimeType,
|
||||
formattedFileSize = event.content.formattedFileSize,
|
||||
fileExtension = event.content.fileExtension
|
||||
),
|
||||
mediaSource = event.content.fileSource,
|
||||
buildMediaViewerNavTarget(
|
||||
event = event,
|
||||
content = event.content,
|
||||
mediaSource = event.content.mediaSource,
|
||||
thumbnailSource = event.content.thumbnailSource,
|
||||
)
|
||||
overlay.show(navTarget)
|
||||
true
|
||||
}
|
||||
is TimelineItemAudioContent -> {
|
||||
val navTarget = NavTarget.MediaViewer(
|
||||
mediaInfo = MediaInfo(
|
||||
filename = event.content.filename,
|
||||
caption = event.content.caption,
|
||||
mimeType = event.content.mimeType,
|
||||
formattedFileSize = event.content.formattedFileSize,
|
||||
fileExtension = event.content.fileExtension
|
||||
),
|
||||
buildMediaViewerNavTarget(
|
||||
event = event,
|
||||
content = event.content,
|
||||
mediaSource = event.content.mediaSource,
|
||||
thumbnailSource = null,
|
||||
)
|
||||
overlay.show(navTarget)
|
||||
true
|
||||
}
|
||||
is TimelineItemLocationContent -> {
|
||||
val navTarget = NavTarget.LocationViewer(
|
||||
NavTarget.LocationViewer(
|
||||
location = event.content.location,
|
||||
description = event.content.description,
|
||||
)
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
return when (navTarget) {
|
||||
is NavTarget.MediaViewer -> {
|
||||
overlay.show(navTarget)
|
||||
true
|
||||
}
|
||||
is NavTarget.LocationViewer -> {
|
||||
backstack.push(navTarget)
|
||||
true
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildMediaViewerNavTarget(
|
||||
event: TimelineItem.Event,
|
||||
content: TimelineItemEventContentWithAttachment,
|
||||
mediaSource: MediaSource,
|
||||
thumbnailSource: MediaSource?,
|
||||
): NavTarget {
|
||||
return NavTarget.MediaViewer(
|
||||
mediaInfo = MediaInfo(
|
||||
filename = content.filename,
|
||||
caption = content.caption,
|
||||
mimeType = content.mimeType,
|
||||
formattedFileSize = content.formattedFileSize,
|
||||
fileExtension = content.fileExtension,
|
||||
senderName = event.safeSenderName,
|
||||
dateSent = event.sentTime,
|
||||
),
|
||||
mediaSource = mediaSource,
|
||||
thumbnailSource = thumbnailSource,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun View(modifier: Modifier) {
|
||||
mentionSpanTheme.updateStyles(currentUserId = room.sessionId)
|
||||
|
|
|
|||
|
|
@ -11,12 +11,14 @@ import androidx.activity.compose.BackHandler
|
|||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.IntrinsicSize
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.imePadding
|
||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
|
|
@ -81,8 +83,9 @@ fun AttachmentsPreviewView(
|
|||
title = {},
|
||||
)
|
||||
}
|
||||
) {
|
||||
) { paddingValues ->
|
||||
AttachmentPreviewContent(
|
||||
modifier = Modifier.padding(paddingValues),
|
||||
state = state,
|
||||
localMediaRenderer = localMediaRenderer,
|
||||
onSendClick = ::postSendAttachment,
|
||||
|
|
@ -134,14 +137,16 @@ private fun AttachmentPreviewContent(
|
|||
state: AttachmentsPreviewState,
|
||||
localMediaRenderer: LocalMediaRenderer,
|
||||
onSendClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
Column(
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.navigationBarsPadding(),
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
modifier = Modifier
|
||||
.weight(1f),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
when (val attachment = state.attachment) {
|
||||
|
|
@ -157,7 +162,6 @@ private fun AttachmentPreviewContent(
|
|||
.fillMaxWidth()
|
||||
.background(ElementTheme.colors.bgCanvasDefault)
|
||||
.height(IntrinsicSize.Min)
|
||||
.align(Alignment.BottomCenter)
|
||||
.imePadding(),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -147,7 +147,7 @@ class TimelineItemContentMessageFactory @Inject constructor(
|
|||
formattedCaption = parseHtml(messageType.formattedCaption) ?: messageType.caption?.withLinks(),
|
||||
isEdited = content.isEdited,
|
||||
thumbnailSource = messageType.info?.thumbnailSource,
|
||||
videoSource = messageType.source,
|
||||
mediaSource = messageType.source,
|
||||
mimeType = messageType.info?.mimetype ?: MimeTypes.OctetStream,
|
||||
width = messageType.info?.width?.toInt(),
|
||||
height = messageType.info?.height?.toInt(),
|
||||
|
|
@ -186,6 +186,8 @@ class TimelineItemContentMessageFactory @Inject constructor(
|
|||
duration = messageType.info?.duration ?: Duration.ZERO,
|
||||
mimeType = messageType.info?.mimetype ?: MimeTypes.OctetStream,
|
||||
waveform = messageType.details?.waveform?.toImmutableList() ?: persistentListOf(),
|
||||
formattedFileSize = fileSizeFormatter.format(messageType.info?.size ?: 0),
|
||||
fileExtension = fileExtensionExtractor.extractFromName(messageType.filename)
|
||||
)
|
||||
}
|
||||
false -> {
|
||||
|
|
@ -211,7 +213,7 @@ class TimelineItemContentMessageFactory @Inject constructor(
|
|||
formattedCaption = parseHtml(messageType.formattedCaption) ?: messageType.caption?.withLinks(),
|
||||
isEdited = content.isEdited,
|
||||
thumbnailSource = messageType.info?.thumbnailSource,
|
||||
fileSource = messageType.source,
|
||||
mediaSource = messageType.source,
|
||||
mimeType = messageType.info?.mimetype ?: MimeTypes.fromFileExtension(fileExtension),
|
||||
formattedFileSize = fileSizeFormatter.format(messageType.info?.size ?: 0),
|
||||
fileExtension = fileExtension
|
||||
|
|
|
|||
|
|
@ -17,10 +17,10 @@ data class TimelineItemAudioContent(
|
|||
override val formattedCaption: CharSequence?,
|
||||
override val isEdited: Boolean,
|
||||
val duration: Duration,
|
||||
val mediaSource: MediaSource,
|
||||
val mimeType: String,
|
||||
val formattedFileSize: String,
|
||||
val fileExtension: String,
|
||||
override val mediaSource: MediaSource,
|
||||
override val mimeType: String,
|
||||
override val formattedFileSize: String,
|
||||
override val fileExtension: String,
|
||||
) : TimelineItemEventContentWithAttachment {
|
||||
val fileExtensionAndSize =
|
||||
formatFileExtensionAndSize(
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
package io.element.android.features.messages.impl.timeline.model.event
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import io.element.android.libraries.matrix.api.media.MediaSource
|
||||
|
||||
@Immutable
|
||||
sealed interface TimelineItemEventContent {
|
||||
|
|
@ -26,6 +27,10 @@ sealed interface TimelineItemEventContentWithAttachment :
|
|||
val filename: String
|
||||
val caption: String?
|
||||
val formattedCaption: CharSequence?
|
||||
val mediaSource: MediaSource
|
||||
val mimeType: String
|
||||
val formattedFileSize: String
|
||||
val fileExtension: String
|
||||
|
||||
val bestDescription: String
|
||||
get() = caption ?: filename
|
||||
|
|
|
|||
|
|
@ -15,11 +15,11 @@ data class TimelineItemFileContent(
|
|||
override val caption: String?,
|
||||
override val formattedCaption: CharSequence?,
|
||||
override val isEdited: Boolean,
|
||||
val fileSource: MediaSource,
|
||||
override val mediaSource: MediaSource,
|
||||
val thumbnailSource: MediaSource?,
|
||||
val formattedFileSize: String,
|
||||
val fileExtension: String,
|
||||
val mimeType: String,
|
||||
override val formattedFileSize: String,
|
||||
override val fileExtension: String,
|
||||
override val mimeType: String,
|
||||
) : TimelineItemEventContentWithAttachment {
|
||||
override val type: String = "TimelineItemFileContent"
|
||||
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ fun aTimelineItemFileContent(
|
|||
formattedCaption = null,
|
||||
isEdited = false,
|
||||
thumbnailSource = null,
|
||||
fileSource = MediaSource(url = ""),
|
||||
mediaSource = MediaSource(url = ""),
|
||||
mimeType = MimeTypes.Pdf,
|
||||
formattedFileSize = "100kB",
|
||||
fileExtension = "pdf"
|
||||
|
|
|
|||
|
|
@ -18,11 +18,11 @@ data class TimelineItemImageContent(
|
|||
override val caption: String?,
|
||||
override val formattedCaption: CharSequence?,
|
||||
override val isEdited: Boolean,
|
||||
val mediaSource: MediaSource,
|
||||
override val mediaSource: MediaSource,
|
||||
val thumbnailSource: MediaSource?,
|
||||
val formattedFileSize: String,
|
||||
val fileExtension: String,
|
||||
val mimeType: String,
|
||||
override val formattedFileSize: String,
|
||||
override val fileExtension: String,
|
||||
override val mimeType: String,
|
||||
val blurhash: String?,
|
||||
val width: Int?,
|
||||
val height: Int?,
|
||||
|
|
|
|||
|
|
@ -14,11 +14,11 @@ data class TimelineItemStickerContent(
|
|||
override val caption: String?,
|
||||
override val formattedCaption: CharSequence?,
|
||||
override val isEdited: Boolean,
|
||||
val mediaSource: MediaSource,
|
||||
override val mediaSource: MediaSource,
|
||||
val thumbnailSource: MediaSource?,
|
||||
val formattedFileSize: String,
|
||||
val fileExtension: String,
|
||||
val mimeType: String,
|
||||
override val formattedFileSize: String,
|
||||
override val fileExtension: String,
|
||||
override val mimeType: String,
|
||||
val blurhash: String?,
|
||||
val width: Int?,
|
||||
val height: Int?,
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ data class TimelineItemVideoContent(
|
|||
override val formattedCaption: CharSequence?,
|
||||
override val isEdited: Boolean,
|
||||
val duration: Duration,
|
||||
val videoSource: MediaSource,
|
||||
override val mediaSource: MediaSource,
|
||||
val thumbnailSource: MediaSource?,
|
||||
val aspectRatio: Float?,
|
||||
val blurHash: String?,
|
||||
|
|
@ -24,9 +24,9 @@ data class TimelineItemVideoContent(
|
|||
val width: Int?,
|
||||
val thumbnailWidth: Int?,
|
||||
val thumbnailHeight: Int?,
|
||||
val mimeType: String,
|
||||
val formattedFileSize: String,
|
||||
val fileExtension: String,
|
||||
override val mimeType: String,
|
||||
override val formattedFileSize: String,
|
||||
override val fileExtension: String,
|
||||
) : TimelineItemEventContentWithAttachment {
|
||||
override val type: String = "TimelineItemImageContent"
|
||||
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ fun aTimelineItemVideoContent(
|
|||
blurHash = blurhash,
|
||||
aspectRatio = aspectRatio,
|
||||
duration = 100.milliseconds,
|
||||
videoSource = MediaSource(""),
|
||||
mediaSource = MediaSource(""),
|
||||
width = 150,
|
||||
height = 300,
|
||||
thumbnailWidth = 150,
|
||||
|
|
|
|||
|
|
@ -19,8 +19,10 @@ data class TimelineItemVoiceContent(
|
|||
override val formattedCaption: CharSequence?,
|
||||
override val isEdited: Boolean,
|
||||
val duration: Duration,
|
||||
val mediaSource: MediaSource,
|
||||
val mimeType: String,
|
||||
override val mediaSource: MediaSource,
|
||||
override val formattedFileSize: String,
|
||||
override val fileExtension: String,
|
||||
override val mimeType: String,
|
||||
val waveform: ImmutableList<Float>,
|
||||
) : TimelineItemEventContentWithAttachment {
|
||||
override val type: String = "TimelineItemAudioContent"
|
||||
|
|
|
|||
|
|
@ -53,4 +53,6 @@ fun aTimelineItemVoiceContent(
|
|||
mediaSource = mediaSource,
|
||||
mimeType = mimeType,
|
||||
waveform = waveform.toPersistentList(),
|
||||
formattedFileSize = "1.0 MB",
|
||||
fileExtension = "ogg",
|
||||
)
|
||||
|
|
|
|||
|
|
@ -371,7 +371,7 @@ class MessagesPresenterTest {
|
|||
formattedCaption = null,
|
||||
isEdited = false,
|
||||
duration = 10.milliseconds,
|
||||
videoSource = MediaSource(AN_AVATAR_URL),
|
||||
mediaSource = MediaSource(AN_AVATAR_URL),
|
||||
thumbnailSource = MediaSource(AN_AVATAR_URL),
|
||||
mimeType = MimeTypes.Mp4,
|
||||
blurHash = null,
|
||||
|
|
@ -413,7 +413,7 @@ class MessagesPresenterTest {
|
|||
caption = null,
|
||||
isEdited = false,
|
||||
formattedCaption = null,
|
||||
fileSource = MediaSource(AN_AVATAR_URL),
|
||||
mediaSource = MediaSource(AN_AVATAR_URL),
|
||||
thumbnailSource = MediaSource(AN_AVATAR_URL),
|
||||
formattedFileSize = "10 MB",
|
||||
mimeType = MimeTypes.Pdf,
|
||||
|
|
|
|||
|
|
@ -239,7 +239,7 @@ class TimelineItemContentMessageFactoryTest {
|
|||
formattedCaption = null,
|
||||
isEdited = false,
|
||||
duration = Duration.ZERO,
|
||||
videoSource = MediaSource(url = "url", json = null),
|
||||
mediaSource = MediaSource(url = "url", json = null),
|
||||
thumbnailSource = null,
|
||||
aspectRatio = null,
|
||||
blurHash = null,
|
||||
|
|
@ -291,7 +291,7 @@ class TimelineItemContentMessageFactoryTest {
|
|||
formattedCaption = SpannedString("formatted"),
|
||||
isEdited = true,
|
||||
duration = 1.minutes,
|
||||
videoSource = MediaSource(url = "url", json = null),
|
||||
mediaSource = MediaSource(url = "url", json = null),
|
||||
thumbnailSource = MediaSource("url_thumbnail"),
|
||||
aspectRatio = 3f,
|
||||
blurHash = A_BLUR_HASH,
|
||||
|
|
@ -380,7 +380,9 @@ class TimelineItemContentMessageFactoryTest {
|
|||
duration = Duration.ZERO,
|
||||
mediaSource = MediaSource(url = "url", json = null),
|
||||
mimeType = MimeTypes.OctetStream,
|
||||
waveform = emptyList<Float>().toImmutableList()
|
||||
waveform = emptyList<Float>().toImmutableList(),
|
||||
fileExtension = "",
|
||||
formattedFileSize = "0 Bytes",
|
||||
)
|
||||
assertThat(result).isEqualTo(expected)
|
||||
}
|
||||
|
|
@ -419,7 +421,9 @@ class TimelineItemContentMessageFactoryTest {
|
|||
duration = 1.minutes,
|
||||
mediaSource = MediaSource(url = "url", json = null),
|
||||
mimeType = MimeTypes.Ogg,
|
||||
waveform = persistentListOf(1f, 2f)
|
||||
waveform = persistentListOf(1f, 2f),
|
||||
fileExtension = "ogg",
|
||||
formattedFileSize = "123 Bytes",
|
||||
)
|
||||
assertThat(result).isEqualTo(expected)
|
||||
}
|
||||
|
|
@ -571,7 +575,7 @@ class TimelineItemContentMessageFactoryTest {
|
|||
caption = null,
|
||||
formattedCaption = null,
|
||||
isEdited = false,
|
||||
fileSource = MediaSource(url = "url", json = null),
|
||||
mediaSource = MediaSource(url = "url", json = null),
|
||||
thumbnailSource = null,
|
||||
formattedFileSize = "0 Bytes",
|
||||
fileExtension = "",
|
||||
|
|
@ -612,7 +616,7 @@ class TimelineItemContentMessageFactoryTest {
|
|||
caption = null,
|
||||
formattedCaption = null,
|
||||
isEdited = true,
|
||||
fileSource = MediaSource(url = "url", json = null),
|
||||
mediaSource = MediaSource(url = "url", json = null),
|
||||
thumbnailSource = MediaSource("url_thumbnail"),
|
||||
formattedFileSize = "123 Bytes",
|
||||
fileExtension = "pdf",
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ class DefaultLocalMediaRenderer @Inject constructor() : LocalMediaRenderer {
|
|||
)
|
||||
LocalMediaView(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
bottomPaddingInPixels = 0,
|
||||
localMedia = localMedia,
|
||||
localMediaViewState = localMediaViewState,
|
||||
onClick = {}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -11,10 +11,13 @@ import androidx.compose.animation.AnimatedVisibility
|
|||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.widthIn
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableFloatStateOf
|
||||
|
|
@ -22,6 +25,7 @@ import androidx.compose.runtime.remember
|
|||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
|
|
@ -63,8 +67,22 @@ fun MediaPlayerControllerView(
|
|||
.widthIn(max = 480.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
IconButton(
|
||||
onClick = onTogglePlay,
|
||||
val bgColor = if (state.isPlaying) {
|
||||
ElementTheme.colors.bgCanvasDefault
|
||||
} else {
|
||||
ElementTheme.colors.textPrimary
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(36.dp)
|
||||
.background(
|
||||
color = bgColor,
|
||||
shape = CircleShape,
|
||||
)
|
||||
.clip(CircleShape)
|
||||
.clickable { onTogglePlay() }
|
||||
.padding(8.dp),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
if (state.isPlaying) {
|
||||
Icon(
|
||||
|
|
@ -75,7 +93,7 @@ fun MediaPlayerControllerView(
|
|||
} else {
|
||||
Icon(
|
||||
imageVector = CompoundIcons.PlaySolid(),
|
||||
tint = ElementTheme.colors.iconPrimary,
|
||||
tint = ElementTheme.colors.iconOnSolidPrimary,
|
||||
contentDescription = stringResource(CommonStrings.a11y_play)
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
@ -229,7 +234,8 @@ private fun ExoPlayerMediaVideoView(
|
|||
},
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.align(Alignment.BottomCenter),
|
||||
.align(Alignment.BottomCenter)
|
||||
.padding(bottom = bottomPaddingInPixels.toDp()),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -252,6 +258,7 @@ private fun ExoPlayerMediaVideoView(
|
|||
internal fun MediaVideoViewPreview() = ElementPreview {
|
||||
MediaVideoView(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
bottomPaddingInPixels = 0,
|
||||
localMediaViewState = rememberLocalMediaViewState(),
|
||||
localMedia = null,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -24,52 +24,72 @@ open class MediaViewerStateProvider : PreviewParameterProvider<MediaViewerState>
|
|||
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,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,21 +41,26 @@ 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
|
||||
import io.element.android.libraries.designsystem.components.button.BackButton
|
||||
import io.element.android.libraries.designsystem.components.dialogs.RetryDialog
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
|
||||
import io.element.android.libraries.designsystem.theme.components.HorizontalDivider
|
||||
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 +90,8 @@ fun MediaViewerView(
|
|||
val snackbarHostState = rememberSnackbarHostState(snackbarMessage = state.snackbarMessage)
|
||||
var showOverlay by remember { mutableStateOf(true) }
|
||||
|
||||
val defaultBottomPaddingInPixels = if (LocalInspectionMode.current) 303 else 0
|
||||
var bottomPaddingInPixels by remember { mutableIntStateOf(defaultBottomPaddingInPixels) }
|
||||
BackHandler { onBackClick() }
|
||||
Scaffold(
|
||||
modifier,
|
||||
|
|
@ -88,6 +100,7 @@ fun MediaViewerView(
|
|||
) {
|
||||
MediaViewerPage(
|
||||
showOverlay = showOverlay,
|
||||
bottomPaddingInPixels = bottomPaddingInPixels,
|
||||
state = state,
|
||||
onDismiss = {
|
||||
onBackClick()
|
||||
|
|
@ -97,14 +110,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,
|
||||
onHeightChange = { bottomPaddingInPixels = it },
|
||||
eventSink = state.eventSink
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -112,6 +140,7 @@ fun MediaViewerView(
|
|||
@Composable
|
||||
private fun MediaViewerPage(
|
||||
showOverlay: Boolean,
|
||||
bottomPaddingInPixels: Int,
|
||||
state: MediaViewerState,
|
||||
onDismiss: () -> Unit,
|
||||
onShowOverlayChange: (Boolean) -> Unit,
|
||||
|
|
@ -148,8 +177,8 @@ private fun MediaViewerPage(
|
|||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.navigationBarsPadding()
|
||||
.fillMaxSize()
|
||||
.navigationBarsPadding()
|
||||
) {
|
||||
Box(contentAlignment = Alignment.Center) {
|
||||
val zoomableState = rememberZoomableState(
|
||||
|
|
@ -168,6 +197,7 @@ private fun MediaViewerPage(
|
|||
|
||||
LocalMediaView(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
bottomPaddingInPixels = bottomPaddingInPixels,
|
||||
localMediaViewState = localMediaViewState,
|
||||
localMedia = state.downloadedMedia.dataOrNull(),
|
||||
mediaInfo = state.mediaInfo,
|
||||
|
|
@ -193,8 +223,8 @@ private fun MediaViewerPage(
|
|||
if (showProgress) {
|
||||
LinearProgressIndicator(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(2.dp)
|
||||
.fillMaxWidth()
|
||||
.height(2.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -246,23 +276,100 @@ private fun rememberShowProgress(downloadedMedia: AsyncData<LocalMedia>): 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?,
|
||||
onHeightChange: (Int) -> Unit,
|
||||
eventSink: (MediaViewerEvents) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.background(Color(0x99101317))
|
||||
.onSizeChanged {
|
||||
onHeightChange(it.height)
|
||||
},
|
||||
) {
|
||||
HorizontalDivider()
|
||||
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 +400,8 @@ private fun MediaViewerTopBar(
|
|||
)
|
||||
}
|
||||
}
|
||||
if (canShare) {
|
||||
IconButton(
|
||||
enabled = actionsEnabled,
|
||||
onClick = {
|
||||
eventSink(MediaViewerEvents.Share)
|
||||
},
|
||||
) {
|
||||
Icon(
|
||||
imageVector = CompoundIcons.ShareAndroid(),
|
||||
contentDescription = stringResource(id = CommonStrings.action_share)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:7839db8763b29e38ad67380e09101e48fd3a650080967fb5e51a8bf6411d0427
|
||||
size 397423
|
||||
oid sha256:17aa655ef22beec5cef50f5d44cb2ecd8ad3a1979bc072a33079a49a8c4495c5
|
||||
size 397284
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:2c40163401ccd77a79d314bd373bb09d9b9bd0bd8d0afe281792c19d6fd593d3
|
||||
size 51465
|
||||
oid sha256:8a0e722772be6b9d38c5f0ef3198502ca6e6ebb2599e3a4ebb4cc95cde907095
|
||||
size 52012
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:1cba25287c6d2beee594937c3139b3800630379e1b5a552c5cca886f8646bbfe
|
||||
size 51429
|
||||
oid sha256:aa4cae0da657162117538b6839032ebf467542a043f18e308eeaf5ae28ca14e8
|
||||
size 51984
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:b4f33aa3545c29c25b3c40e6de90772e4e1f0685095429bdbcc4216e9020161c
|
||||
size 89038
|
||||
oid sha256:df6fc9bcb3636549fdfeacb4c9e19cc3c699b7519558bd14f409e7b0c6a84d80
|
||||
size 89846
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:507c7dffae1aabfa687174f1f964e2c40b004183b6bc3a70b56d764e0d308b47
|
||||
size 392923
|
||||
oid sha256:aedd5d3a01457b8ed06b16cd63d08f24ae34568e2b912b82fb892b154c94e6df
|
||||
size 392780
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:7839db8763b29e38ad67380e09101e48fd3a650080967fb5e51a8bf6411d0427
|
||||
size 397423
|
||||
oid sha256:17aa655ef22beec5cef50f5d44cb2ecd8ad3a1979bc072a33079a49a8c4495c5
|
||||
size 397284
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:e7a7ea1da7e7602cc53cc162c4a685516a7a46ca148eafca6e19a5748630bda5
|
||||
size 7036
|
||||
oid sha256:333a21a41a7d5f8d47946648a7381dadeccf213a1176696b3512c08ae929d4d6
|
||||
size 7819
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:bc35254c6962b5b113a2c25c4c9dd0c94449521152ffd73f41a0c36429fabbb0
|
||||
size 7258
|
||||
oid sha256:cba8f49caf65856569266a561206437840a43da2d41ed5f08321ecda99204329
|
||||
size 8236
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:9f69e358ddd7e00f7301598f5dd235a2f5b9077f8926c2bfa3a4bca1168900f0
|
||||
size 7173
|
||||
oid sha256:773785bb234c0719c0f27ee91a970c63d7f971518dc0c83298a4b6adb9acc4cf
|
||||
size 8066
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:b096093e04eb187fce0a85496c8a5b251afe0d0bccd977abcfb4d1e3dbe32a20
|
||||
size 7436
|
||||
oid sha256:5424e7ce4a5259aab297a7911741352f0fba01e5fdac972768441db9edf41171
|
||||
size 7675
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:3b7160dc01dd4704bb07a8c91858bef0d4e99c0f55ca1d7dbdc3e22546a25125
|
||||
size 12210
|
||||
oid sha256:267d4be8b727a0ecb5af1e5f1e69adfd68f50f32f1de78f4f9fde60f635244b5
|
||||
size 13045
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:2591b742c64d3d058d4638e5014080f8b3eb13eba3a73951d71dfdc60e7676d9
|
||||
size 12455
|
||||
oid sha256:412476e819f688b5314651fd3d59439fc0e4f53ee777e3a070b82fd19d51700e
|
||||
size 13272
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:a6fe771fa6492768239d0caf7a9c10613efb95ff9c2c1b786ee0a6974392af9f
|
||||
size 389635
|
||||
oid sha256:9df1a0ff2b3aaab2d1a346cb134b560d65c462286575ffc6124099529562eac1
|
||||
size 389594
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:2b286342ff4d46637beac1f980294f77b3e2eb6824d56448cdbdce7b41c911ab
|
||||
size 388612
|
||||
oid sha256:4fde3cef34d23c894ae480a1b4c961065f244d845584f8607c4bb21e0a7e5f10
|
||||
size 388615
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:7b023a77242de9cab647e87b18edcd86a48a7843aa8fa51c84b443e3e8b41bcb
|
||||
size 389669
|
||||
oid sha256:cf07ed00618c0552205b1e726050382369041bb7f2d9742b754a5652eca48265
|
||||
size 389631
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:75ff0cd0cfe594a217d2b58b48c2bb4aedad692a270c2cee8aa3d4bdf6758e1e
|
||||
size 94797
|
||||
oid sha256:143285221dd5a1e714a9897293f13c45ca567635e60350738033b315f44f742a
|
||||
size 94735
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:0dc5fdbd7d0b3980ad19bf74640067e8bb294def913e3d13d7bc4ad9c822382d
|
||||
size 389921
|
||||
oid sha256:a4e1b6a7a3dcc1627aec1767ac8edac95bec483ebdc98d71d65342084e08a4dc
|
||||
size 396792
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:05794effa4c47e3d2b68892e613f18a55c96789a4e65a182dd0bf2ca0c812d44
|
||||
size 14474
|
||||
oid sha256:6c3c2b7b1d64387ec1109ab21304df50de5bd4fac0317c27a2799e7405da3843
|
||||
size 22219
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:e6113747e1677c2f28c2a109bc2c7075a753917068c1bbc3a46d5bdef4db4e23
|
||||
size 5756
|
||||
oid sha256:364fce9e921a21dcaae9ef1d7c98efe47c4c9118d23958550ee9eafaf202e2fa
|
||||
size 5751
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:f5a6ec7984ceab5acf44431a9bff89c29c9ac27c95804d29b800124addd6e910
|
||||
size 14800
|
||||
oid sha256:769a00d9e84cb2da43d790158f35e66bc5ed6538fdcb7f5e316f5de13d9bd9ad
|
||||
size 14768
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:d03b94e2b502b9857b9b4c2527df86d1aa0a194abed794cbe601ec4172422dcc
|
||||
size 14987
|
||||
oid sha256:bf1804b21f3d383c94e28714746ac61987643c8ec0480d2850b19eb01991aa4e
|
||||
size 15043
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:56b9d11058f06732364668a5833f52fa4e62541fa8ee48f13996ae0a11e11100
|
||||
size 13609
|
||||
oid sha256:ec58fa08f3c160e1b7d462b580f79b1c05a5644b9f807a79cfeebbeea417ed10
|
||||
size 13502
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:25c1e3cb51b13bb65b281180862ac8fb6faa1e8bd0ce26d8d0e07321c03c116c
|
||||
size 13742
|
||||
oid sha256:689081427ece8bc009266a50b9d2a80def581f51f00bf65b35d295b0ea7dcab3
|
||||
size 13736
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue