From 03523c9567a083393395a0caaef4451393abbbe9 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 17 Jan 2025 16:07:36 +0100 Subject: [PATCH] Provide duration --- .../messages/impl/MessagesFlowNode.kt | 3 + .../model/event/TimelineItemEventContent.kt | 10 +++ .../dateformatter/api/DurationFormatter.kt | 3 + .../libraries/mediaviewer/api/MediaInfo.kt | 10 +++ .../impl/DefaultMediaViewerEntryPoint.kt | 1 + .../impl/gallery/EventItemFactory.kt | 9 +- .../mediaviewer/impl/gallery/MediaItem.kt | 4 - .../gallery/SingleMediaGalleryDataSource.kt | 90 +++++++------------ .../impl/gallery/ui/MediaItemVideoProvider.kt | 5 +- .../impl/gallery/ui/MediaItemVoiceProvider.kt | 5 +- .../impl/gallery/ui/VideoItemView.kt | 4 +- .../impl/gallery/ui/VoiceItemView.kt | 4 +- .../impl/local/AndroidLocalMediaFactory.kt | 4 + 13 files changed, 79 insertions(+), 73 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 2b34ec0d1b..f9d9bbf27f 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 @@ -48,6 +48,7 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStickerContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVideoContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVoiceContent +import io.element.android.features.messages.impl.timeline.model.event.duration import io.element.android.features.poll.api.create.CreatePollEntryPoint import io.element.android.features.poll.api.create.CreatePollMode import io.element.android.libraries.architecture.BackstackWithOverlayBox @@ -58,6 +59,7 @@ import io.element.android.libraries.architecture.overlay.operation.hide import io.element.android.libraries.architecture.overlay.operation.show import io.element.android.libraries.dateformatter.api.DateFormatter import io.element.android.libraries.dateformatter.api.DateFormatterMode +import io.element.android.libraries.dateformatter.api.toHumanReadableDuration import io.element.android.libraries.di.RoomScope import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.core.EventId @@ -449,6 +451,7 @@ class MessagesFlowNode @AssistedInject constructor( mode = DateFormatterMode.Full, ), waveform = (content as? TimelineItemVoiceContent)?.waveform, + duration = content.duration()?.toHumanReadableDuration(), ), mediaSource = mediaSource, thumbnailSource = thumbnailSource, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemEventContent.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemEventContent.kt index aeb847b80a..9eda2e7253 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemEventContent.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemEventContent.kt @@ -9,6 +9,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 +import kotlin.time.Duration @Immutable sealed interface TimelineItemEventContent { @@ -90,3 +91,12 @@ fun TimelineItemEventContent.isEdited(): Boolean = when (this) { is TimelineItemEventMutableContent -> isEdited else -> false } + +fun TimelineItemEventContentWithAttachment.duration(): Duration? { + return when (this) { + is TimelineItemAudioContent -> duration + is TimelineItemVideoContent -> duration + is TimelineItemVoiceContent -> duration + else -> null + } +} diff --git a/libraries/dateformatter/api/src/main/kotlin/io/element/android/libraries/dateformatter/api/DurationFormatter.kt b/libraries/dateformatter/api/src/main/kotlin/io/element/android/libraries/dateformatter/api/DurationFormatter.kt index 63e30d4928..04c6df1d02 100644 --- a/libraries/dateformatter/api/src/main/kotlin/io/element/android/libraries/dateformatter/api/DurationFormatter.kt +++ b/libraries/dateformatter/api/src/main/kotlin/io/element/android/libraries/dateformatter/api/DurationFormatter.kt @@ -8,6 +8,7 @@ package io.element.android.libraries.dateformatter.api import java.util.Locale +import kotlin.time.Duration /** * Convert milliseconds to human readable duration. @@ -38,3 +39,5 @@ fun Long.toHumanReadableDuration(): String { String.format(Locale.US, "%d:%02d", minutes, seconds) } } + +fun Duration.toHumanReadableDuration() = inWholeMilliseconds.toHumanReadableDuration() 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 374bf701a6..7426251ca0 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 @@ -25,6 +25,7 @@ data class MediaInfo( val dateSent: String?, val dateSentFull: String?, val waveform: List?, + val duration: String?, ) : Parcelable fun anImageMediaInfo( @@ -45,6 +46,7 @@ fun anImageMediaInfo( dateSent = dateSent, dateSentFull = dateSentFull, waveform = null, + duration = null, ) fun aVideoMediaInfo( @@ -52,6 +54,7 @@ fun aVideoMediaInfo( senderName: String? = null, dateSent: String? = null, dateSentFull: String? = null, + duration: String? = null, ): MediaInfo = MediaInfo( filename = "a video file.mp4", caption = caption, @@ -64,6 +67,7 @@ fun aVideoMediaInfo( dateSent = dateSent, dateSentFull = dateSentFull, waveform = null, + duration = duration, ) fun aPdfMediaInfo( @@ -84,6 +88,7 @@ fun aPdfMediaInfo( dateSent = dateSent, dateSentFull = dateSentFull, waveform = null, + duration = null, ) fun anApkMediaInfo( @@ -103,6 +108,7 @@ fun anApkMediaInfo( dateSent = dateSent, dateSentFull = dateSentFull, waveform = null, + duration = null, ) fun anAudioMediaInfo( @@ -112,6 +118,7 @@ fun anAudioMediaInfo( dateSent: String? = null, dateSentFull: String? = null, waveForm: List? = null, + duration: String? = null, ): MediaInfo = MediaInfo( filename = filename, caption = caption, @@ -124,6 +131,7 @@ fun anAudioMediaInfo( dateSent = dateSent, dateSentFull = dateSentFull, waveform = waveForm, + duration = duration, ) fun aVoiceMediaInfo( @@ -133,6 +141,7 @@ fun aVoiceMediaInfo( dateSent: String? = null, dateSentFull: String? = null, waveForm: List? = null, + duration: String? = null, ): MediaInfo = MediaInfo( filename = filename, caption = caption, @@ -145,4 +154,5 @@ fun aVoiceMediaInfo( dateSent = dateSent, dateSentFull = dateSentFull, waveform = waveForm, + duration = duration, ) 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 e6482b0c21..64cd9093a2 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 @@ -56,6 +56,7 @@ class DefaultMediaViewerEntryPoint @Inject constructor() : MediaViewerEntryPoint dateSent = null, dateSentFull = null, waveform = null, + duration = null, ), mediaSource = MediaSource(url = avatarUrl), thumbnailSource = null, diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/EventItemFactory.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/EventItemFactory.kt index e8e242d2e0..75fa590b86 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/EventItemFactory.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/EventItemFactory.kt @@ -102,6 +102,7 @@ class EventItemFactory @Inject constructor( dateSent = dateSent, dateSentFull = dateSentFull, waveform = null, + duration = null, ), mediaSource = type.source, ) @@ -120,6 +121,7 @@ class EventItemFactory @Inject constructor( dateSent = dateSent, dateSentFull = dateSentFull, waveform = null, + duration = null, ), mediaSource = type.source, ) @@ -138,6 +140,7 @@ class EventItemFactory @Inject constructor( dateSent = dateSent, dateSentFull = dateSentFull, waveform = null, + duration = null, ), mediaSource = type.source, thumbnailSource = null, @@ -157,6 +160,7 @@ class EventItemFactory @Inject constructor( dateSent = dateSent, dateSentFull = dateSentFull, waveform = null, + duration = null, ), mediaSource = type.source, thumbnailSource = null, @@ -176,10 +180,10 @@ class EventItemFactory @Inject constructor( dateSent = dateSent, dateSentFull = dateSentFull, waveform = null, + duration = type.info?.duration?.inWholeMilliseconds?.toHumanReadableDuration(), ), mediaSource = type.source, thumbnailSource = type.info?.thumbnailSource, - duration = type.info?.duration?.inWholeMilliseconds?.toHumanReadableDuration(), ) is VoiceMessageType -> MediaItem.Voice( id = currentTimelineItem.uniqueId, @@ -196,10 +200,9 @@ class EventItemFactory @Inject constructor( dateSent = dateSent, dateSentFull = dateSentFull, waveform = type.details?.waveform.orEmpty(), + duration = type.info?.duration?.inWholeMilliseconds?.toHumanReadableDuration(), ), mediaSource = type.source, - duration = type.info?.duration?.inWholeMilliseconds?.toHumanReadableDuration(), - waveform = type.details?.waveform ?: persistentListOf(), ) } } diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaItem.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaItem.kt index 2f2054ebe0..e1bd4d779a 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaItem.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaItem.kt @@ -13,7 +13,6 @@ import io.element.android.libraries.matrix.api.media.MediaSource import io.element.android.libraries.matrix.api.timeline.Timeline import io.element.android.libraries.matrix.ui.media.MediaRequestData import io.element.android.libraries.mediaviewer.api.MediaInfo -import kotlinx.collections.immutable.ImmutableList sealed interface MediaItem { data class DateSeparator( @@ -46,7 +45,6 @@ sealed interface MediaItem { val mediaInfo: MediaInfo, val mediaSource: MediaSource, val thumbnailSource: MediaSource?, - val duration: String?, ) : Event { val thumbnailMediaRequestData: MediaRequestData get() = MediaRequestData(thumbnailSource ?: mediaSource, MediaRequestData.Kind.Thumbnail(100)) @@ -64,8 +62,6 @@ sealed interface MediaItem { val eventId: EventId?, val mediaInfo: MediaInfo, val mediaSource: MediaSource, - val duration: String?, - val waveform: ImmutableList, ) : Event data class File( diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/SingleMediaGalleryDataSource.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/SingleMediaGalleryDataSource.kt index 29a3fdd550..8e6b708a7f 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/SingleMediaGalleryDataSource.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/SingleMediaGalleryDataSource.kt @@ -16,7 +16,6 @@ import io.element.android.libraries.matrix.api.core.UniqueId import io.element.android.libraries.matrix.api.timeline.Timeline import io.element.android.libraries.mediaviewer.api.MediaViewerEntryPoint import kotlinx.collections.immutable.persistentListOf -import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.flow.flowOf class SingleMediaGalleryDataSource( @@ -32,77 +31,54 @@ class SingleMediaGalleryDataSource( fun createFrom(params: MediaViewerEntryPoint.Params) = SingleMediaGalleryDataSource( data = when { params.mediaInfo.mimeType.isMimeTypeImage() -> { - GroupedMediaItems( - imageAndVideoItems = persistentListOf( - MediaItem.Image( - id = UniqueId("dummy"), - eventId = params.eventId, - mediaInfo = params.mediaInfo, - mediaSource = params.mediaSource, - thumbnailSource = params.thumbnailSource, - ) - ), - fileItems = persistentListOf(), + MediaItem.Image( + id = UniqueId("dummy"), + eventId = params.eventId, + mediaInfo = params.mediaInfo, + mediaSource = params.mediaSource, + thumbnailSource = params.thumbnailSource, ) } params.mediaInfo.mimeType.isMimeTypeVideo() -> { - GroupedMediaItems( - imageAndVideoItems = persistentListOf( - MediaItem.Video( - id = UniqueId("dummy"), - eventId = params.eventId, - mediaInfo = params.mediaInfo, - mediaSource = params.mediaSource, - thumbnailSource = params.thumbnailSource, - duration = "TODO", // TODO Duration - ) - ), - fileItems = persistentListOf(), + MediaItem.Video( + id = UniqueId("dummy"), + eventId = params.eventId, + mediaInfo = params.mediaInfo, + mediaSource = params.mediaSource, + thumbnailSource = params.thumbnailSource, ) } params.mediaInfo.mimeType.isMimeTypeAudio() -> { if (params.mediaInfo.waveform == null) { - GroupedMediaItems( - imageAndVideoItems = persistentListOf( - MediaItem.Audio( - id = UniqueId("dummy"), - eventId = params.eventId, - mediaInfo = params.mediaInfo, - mediaSource = params.mediaSource, - ) - ), - fileItems = persistentListOf(), + MediaItem.Audio( + id = UniqueId("dummy"), + eventId = params.eventId, + mediaInfo = params.mediaInfo, + mediaSource = params.mediaSource, ) } else { - GroupedMediaItems( - imageAndVideoItems = persistentListOf( - MediaItem.Voice( - id = UniqueId("dummy"), - eventId = params.eventId, - mediaInfo = params.mediaInfo, - mediaSource = params.mediaSource, - duration = "TODO", // TODO Duration - waveform = params.mediaInfo.waveform.orEmpty().toImmutableList(), - ) - ), - fileItems = persistentListOf(), + MediaItem.Voice( + id = UniqueId("dummy"), + eventId = params.eventId, + mediaInfo = params.mediaInfo, + mediaSource = params.mediaSource, ) } } else -> { - // Always use imageAndVideoItems, in Single mode, this is the data that will be used - GroupedMediaItems( - imageAndVideoItems = persistentListOf( - MediaItem.File( - id = UniqueId("dummy"), - eventId = params.eventId, - mediaInfo = params.mediaInfo, - mediaSource = params.mediaSource, - ) - ), - fileItems = persistentListOf(), + MediaItem.File( + id = UniqueId("dummy"), + eventId = params.eventId, + mediaInfo = params.mediaInfo, + mediaSource = params.mediaSource, ) } + }.let { mediaItem -> + GroupedMediaItems( + // Always use imageAndVideoItems, in Single mode, this is the data that will be used + imageAndVideoItems = persistentListOf(mediaItem), + fileItems = persistentListOf(), + ) } ) } diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/MediaItemVideoProvider.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/MediaItemVideoProvider.kt index 47642d4837..8e59b925b7 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/MediaItemVideoProvider.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/MediaItemVideoProvider.kt @@ -31,9 +31,10 @@ fun aMediaItemVideo( return MediaItem.Video( id = id, eventId = null, - mediaInfo = aVideoMediaInfo(), + mediaInfo = aVideoMediaInfo( + duration = duration + ), mediaSource = mediaSource, thumbnailSource = null, - duration = duration, ) } diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/MediaItemVoiceProvider.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/MediaItemVoiceProvider.kt index c84a74c7a1..43e04491de 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/MediaItemVoiceProvider.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/MediaItemVoiceProvider.kt @@ -14,7 +14,6 @@ import io.element.android.libraries.matrix.api.core.UniqueId import io.element.android.libraries.matrix.api.media.MediaSource import io.element.android.libraries.mediaviewer.api.aVoiceMediaInfo import io.element.android.libraries.mediaviewer.impl.gallery.MediaItem -import kotlinx.collections.immutable.toImmutableList class MediaItemVoiceProvider : PreviewParameterProvider { override val values: Sequence @@ -46,9 +45,9 @@ fun aMediaItemVoice( mediaInfo = aVoiceMediaInfo( filename = filename, caption = caption, + duration = duration, + waveForm = waveform, ), mediaSource = MediaSource(""), - duration = duration, - waveform = waveform.toImmutableList(), ) } diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/VideoItemView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/VideoItemView.kt index 0adeabd20f..6b394e7c55 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/VideoItemView.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/VideoItemView.kt @@ -101,10 +101,10 @@ private fun VideoInfoRow( imageVector = CompoundIcons.VideoCallSolid(), contentDescription = null ) - if (video.duration != null) { + video.mediaInfo.duration?.let { duration -> Spacer(Modifier.weight(1f)) Text( - text = video.duration, + text = duration, style = ElementTheme.typography.fontBodySmMedium, color = ElementTheme.colors.textPrimary, ) diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/VoiceItemView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/VoiceItemView.kt index a5f55875bd..d34555e175 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/VoiceItemView.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/VoiceItemView.kt @@ -115,7 +115,7 @@ private fun VoiceInfoRow( } Spacer(Modifier.width(8.dp)) Text( - text = if (state.progress > 0f) state.time else voice.duration ?: state.time, + text = if (state.progress > 0f) state.time else voice.mediaInfo.duration ?: state.time, color = ElementTheme.colors.textSecondary, style = ElementTheme.typography.fontBodyMdMedium, maxLines = 1, @@ -128,7 +128,7 @@ private fun VoiceInfoRow( .height(34.dp), showCursor = state.showCursor, playbackProgress = state.progress, - waveform = voice.waveform.toPersistentList(), + waveform = voice.mediaInfo.waveform.orEmpty().toPersistentList(), onSeek = { state.eventSink(VoiceMessageEvents.Seek(it)) }, 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 297ee4dac2..b7ae566ab1 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 @@ -48,6 +48,7 @@ class AndroidLocalMediaFactory @Inject constructor( dateSent = mediaInfo.dateSent, dateSentFull = mediaInfo.dateSentFull, waveform = mediaInfo.waveform, + duration = mediaInfo.duration, ) override fun createFromUri( @@ -67,6 +68,7 @@ class AndroidLocalMediaFactory @Inject constructor( dateSent = null, dateSentFull = null, waveform = null, + duration = null, ) private fun createFromUri( @@ -81,6 +83,7 @@ class AndroidLocalMediaFactory @Inject constructor( dateSent: String?, dateSentFull: String?, waveform: List?, + duration: String?, ): LocalMedia { val resolvedMimeType = mimeType ?: context.getMimeType(uri) ?: MimeTypes.OctetStream val fileName = name ?: context.getFileName(uri) ?: "" @@ -100,6 +103,7 @@ class AndroidLocalMediaFactory @Inject constructor( dateSent = dateSent, dateSentFull = dateSentFull, waveform = waveform, + duration = duration, ) ) }