Skip to content

Commit 270fcb9

Browse files
authored
Merge pull request #4045 from element-hq/feature/bma/inlinePlayer
Inline voice message player in the files gallery.
2 parents ce7a8e6 + ea85e5d commit 270fcb9

File tree

52 files changed

+825
-102
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+825
-102
lines changed

libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/MediaInfo.kt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,3 +125,24 @@ fun anAudioMediaInfo(
125125
dateSentFull = dateSentFull,
126126
waveform = waveForm,
127127
)
128+
129+
fun aVoiceMediaInfo(
130+
filename: String = "a voice file.ogg",
131+
caption: String? = null,
132+
senderName: String? = null,
133+
dateSent: String? = null,
134+
dateSentFull: String? = null,
135+
waveForm: List<Float>? = null,
136+
): MediaInfo = MediaInfo(
137+
filename = filename,
138+
caption = caption,
139+
mimeType = MimeTypes.Ogg,
140+
formattedFileSize = "3MB",
141+
fileExtension = "ogg",
142+
senderId = UserId("@alice:server.org"),
143+
senderName = senderName,
144+
senderAvatar = null,
145+
dateSent = dateSent,
146+
dateSentFull = dateSentFull,
147+
waveform = waveForm,
148+
)

libraries/mediaviewer/impl/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ dependencies {
4343
implementation(projects.libraries.matrix.api)
4444
implementation(projects.libraries.matrixui)
4545
implementation(projects.libraries.uiStrings)
46+
implementation(projects.libraries.voiceplayer.api)
4647
implementation(projects.services.toolbox.api)
4748

4849
api(projects.libraries.mediaviewer.api)

libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/EventItemFactory.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import io.element.android.libraries.matrix.api.timeline.item.event.getAvatarUrl
4040
import io.element.android.libraries.matrix.api.timeline.item.event.getDisambiguatedDisplayName
4141
import io.element.android.libraries.mediaviewer.api.MediaInfo
4242
import io.element.android.libraries.mediaviewer.api.util.FileExtensionExtractor
43+
import kotlinx.collections.immutable.persistentListOf
4344
import timber.log.Timber
4445
import javax.inject.Inject
4546

@@ -104,7 +105,6 @@ class EventItemFactory @Inject constructor(
104105
),
105106
mediaSource = type.source,
106107
duration = type.info?.duration?.inWholeMilliseconds?.toHumanReadableDuration(),
107-
waveform = null,
108108
)
109109
is FileMessageType -> MediaItem.File(
110110
id = currentTimelineItem.uniqueId,
@@ -182,7 +182,7 @@ class EventItemFactory @Inject constructor(
182182
thumbnailSource = type.info?.thumbnailSource,
183183
duration = type.info?.duration?.inWholeMilliseconds?.toHumanReadableDuration(),
184184
)
185-
is VoiceMessageType -> MediaItem.Audio(
185+
is VoiceMessageType -> MediaItem.Voice(
186186
id = currentTimelineItem.uniqueId,
187187
eventId = currentTimelineItem.eventId,
188188
mediaInfo = MediaInfo(
@@ -200,7 +200,7 @@ class EventItemFactory @Inject constructor(
200200
),
201201
mediaSource = type.source,
202202
duration = type.info?.duration?.inWholeMilliseconds?.toHumanReadableDuration(),
203-
waveform = type.details?.waveform,
203+
waveform = type.details?.waveform ?: persistentListOf(),
204204
)
205205
}
206206
}

libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryNode.kt

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
package io.element.android.libraries.mediaviewer.impl.gallery
99

1010
import androidx.compose.runtime.Composable
11+
import androidx.compose.runtime.CompositionLocalProvider
1112
import androidx.compose.ui.Modifier
1213
import com.bumble.appyx.core.modality.BuildContext
1314
import com.bumble.appyx.core.node.Node
@@ -18,12 +19,15 @@ import dagger.assisted.AssistedInject
1819
import io.element.android.anvilannotations.ContributesNode
1920
import io.element.android.libraries.di.RoomScope
2021
import io.element.android.libraries.matrix.api.core.EventId
22+
import io.element.android.libraries.mediaviewer.impl.gallery.di.LocalMediaItemPresenterFactories
23+
import io.element.android.libraries.mediaviewer.impl.gallery.di.MediaItemPresenterFactories
2124

2225
@ContributesNode(RoomScope::class)
2326
class MediaGalleryNode @AssistedInject constructor(
2427
@Assisted buildContext: BuildContext,
2528
@Assisted plugins: List<Plugin>,
2629
presenterFactory: MediaGalleryPresenter.Factory,
30+
private val mediaItemPresenterFactories: MediaItemPresenterFactories,
2731
) : Node(buildContext, plugins = plugins),
2832
MediaGalleryNavigator {
2933
private val presenter = presenterFactory.create(
@@ -56,12 +60,16 @@ class MediaGalleryNode @AssistedInject constructor(
5660

5761
@Composable
5862
override fun View(modifier: Modifier) {
59-
val state = presenter.present()
60-
MediaGalleryView(
61-
state = state,
62-
onBackClick = ::onBackClick,
63-
onItemClick = ::onItemClick,
64-
modifier = modifier,
65-
)
63+
CompositionLocalProvider(
64+
LocalMediaItemPresenterFactories provides mediaItemPresenterFactories,
65+
) {
66+
val state = presenter.present()
67+
MediaGalleryView(
68+
state = state,
69+
onBackClick = ::onBackClick,
70+
onItemClick = ::onItemClick,
71+
modifier = modifier,
72+
)
73+
}
6674
}
6775
}

libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenter.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ class MediaGalleryPresenter @AssistedInject constructor(
137137
is MediaItem.Video -> event.mediaItem.thumbnailSource ?: event.mediaItem.mediaSource
138138
is MediaItem.Audio -> null
139139
is MediaItem.File -> null
140+
is MediaItem.Voice -> null
140141
},
141142
)
142143
}

libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryStateProvider.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import io.element.android.libraries.mediaviewer.impl.gallery.ui.aMediaItemFile
1919
import io.element.android.libraries.mediaviewer.impl.gallery.ui.aMediaItemImage
2020
import io.element.android.libraries.mediaviewer.impl.gallery.ui.aMediaItemLoadingIndicator
2121
import io.element.android.libraries.mediaviewer.impl.gallery.ui.aMediaItemVideo
22+
import io.element.android.libraries.mediaviewer.impl.gallery.ui.aMediaItemVoice
2223
import kotlinx.collections.immutable.toImmutableList
2324

2425
open class MediaGalleryStateProvider : PreviewParameterProvider<MediaGalleryState> {
@@ -63,9 +64,8 @@ open class MediaGalleryStateProvider : PreviewParameterProvider<MediaGalleryStat
6364
id = UniqueId("2"),
6465
formattedDate = "September 2004",
6566
),
66-
aMediaItemFile(id = UniqueId("3")),
6767
aMediaItemAudio(id = UniqueId("4")),
68-
aMediaItemAudio(
68+
aMediaItemVoice(
6969
id = UniqueId("5"),
7070
waveform = aWaveForm(),
7171
),

libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryView.kt

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import androidx.compose.foundation.pager.rememberPagerState
2727
import androidx.compose.material3.ExperimentalMaterial3Api
2828
import androidx.compose.material3.SingleChoiceSegmentedButtonRow
2929
import androidx.compose.runtime.Composable
30+
import androidx.compose.runtime.CompositionLocalProvider
3031
import androidx.compose.runtime.LaunchedEffect
3132
import androidx.compose.runtime.getValue
3233
import androidx.compose.runtime.rememberUpdatedState
@@ -40,6 +41,7 @@ import androidx.compose.ui.unit.dp
4041
import io.element.android.compound.theme.ElementTheme
4142
import io.element.android.compound.tokens.generated.CompoundIcons
4243
import io.element.android.libraries.architecture.AsyncData
44+
import io.element.android.libraries.architecture.Presenter
4345
import io.element.android.libraries.designsystem.components.BigIcon
4446
import io.element.android.libraries.designsystem.components.PageTitle
4547
import io.element.android.libraries.designsystem.components.async.AsyncFailure
@@ -60,11 +62,16 @@ import io.element.android.libraries.mediaviewer.impl.R
6062
import io.element.android.libraries.mediaviewer.impl.details.MediaBottomSheetState
6163
import io.element.android.libraries.mediaviewer.impl.details.MediaDeleteConfirmationBottomSheet
6264
import io.element.android.libraries.mediaviewer.impl.details.MediaDetailsBottomSheet
65+
import io.element.android.libraries.mediaviewer.impl.gallery.di.LocalMediaItemPresenterFactories
66+
import io.element.android.libraries.mediaviewer.impl.gallery.di.aFakeMediaItemPresenterFactories
67+
import io.element.android.libraries.mediaviewer.impl.gallery.di.rememberPresenter
6368
import io.element.android.libraries.mediaviewer.impl.gallery.ui.AudioItemView
6469
import io.element.android.libraries.mediaviewer.impl.gallery.ui.DateItemView
6570
import io.element.android.libraries.mediaviewer.impl.gallery.ui.FileItemView
6671
import io.element.android.libraries.mediaviewer.impl.gallery.ui.ImageItemView
6772
import io.element.android.libraries.mediaviewer.impl.gallery.ui.VideoItemView
73+
import io.element.android.libraries.mediaviewer.impl.gallery.ui.VoiceItemView
74+
import io.element.android.libraries.voiceplayer.api.VoiceMessageState
6875
import kotlinx.collections.immutable.ImmutableList
6976
import kotlin.math.max
7077

@@ -255,6 +262,7 @@ private fun MediaGalleryFilesList(
255262
eventSink: (MediaGalleryEvents) -> Unit,
256263
onItemClick: (MediaItem.Event) -> Unit,
257264
) {
265+
val presenterFactories = LocalMediaItemPresenterFactories.current
258266
LazyColumn(
259267
modifier = Modifier.fillMaxSize(),
260268
) {
@@ -274,6 +282,16 @@ private fun MediaGalleryFilesList(
274282
onDownloadClick = { eventSink(MediaGalleryEvents.SaveOnDisk(item)) },
275283
onInfoClick = { eventSink(MediaGalleryEvents.OpenInfo(item)) },
276284
)
285+
is MediaItem.Voice -> {
286+
val presenter: Presenter<VoiceMessageState> = presenterFactories.rememberPresenter(item)
287+
VoiceItemView(
288+
presenter.present(),
289+
item,
290+
onShareClick = { eventSink(MediaGalleryEvents.Share(item)) },
291+
onDownloadClick = { eventSink(MediaGalleryEvents.SaveOnDisk(item)) },
292+
onInfoClick = { eventSink(MediaGalleryEvents.OpenInfo(item)) },
293+
)
294+
}
277295
is MediaItem.DateSeparator -> DateItemView(item)
278296
is MediaItem.Image,
279297
is MediaItem.Video -> {
@@ -332,6 +350,9 @@ private fun MediaGalleryImageGrid(
332350
is MediaItem.Audio -> {
333351
// Should not happen
334352
}
353+
is MediaItem.Voice -> {
354+
// Should not happen
355+
}
335356
is MediaItem.File -> {
336357
// Should not happen
337358
}
@@ -452,9 +473,13 @@ private fun LoadingContent(
452473
internal fun MediaGalleryViewPreview(
453474
@PreviewParameter(MediaGalleryStateProvider::class) state: MediaGalleryState
454475
) = ElementPreview {
455-
MediaGalleryView(
456-
state = state,
457-
onBackClick = {},
458-
onItemClick = {},
459-
)
476+
CompositionLocalProvider(
477+
LocalMediaItemPresenterFactories provides aFakeMediaItemPresenterFactories(),
478+
) {
479+
MediaGalleryView(
480+
state = state,
481+
onBackClick = {},
482+
onItemClick = {},
483+
)
484+
}
460485
}

libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaItem.kt

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,15 @@ sealed interface MediaItem {
5858
val mediaInfo: MediaInfo,
5959
val mediaSource: MediaSource,
6060
val duration: String?,
61-
val waveform: ImmutableList<Float>?,
61+
) : Event
62+
63+
data class Voice(
64+
val id: UniqueId,
65+
val eventId: EventId?,
66+
val mediaInfo: MediaInfo,
67+
val mediaSource: MediaSource,
68+
val duration: String?,
69+
val waveform: ImmutableList<Float>,
6270
) : Event
6371

6472
data class File(
@@ -77,6 +85,7 @@ fun MediaItem.id(): UniqueId {
7785
is MediaItem.Video -> id
7886
is MediaItem.File -> id
7987
is MediaItem.Audio -> id
88+
is MediaItem.Voice -> id
8089
}
8190
}
8291

@@ -86,6 +95,7 @@ fun MediaItem.Event.eventId(): EventId? {
8695
is MediaItem.Video -> eventId
8796
is MediaItem.File -> eventId
8897
is MediaItem.Audio -> eventId
98+
is MediaItem.Voice -> eventId
8999
}
90100
}
91101

@@ -95,6 +105,7 @@ fun MediaItem.Event.mediaInfo(): MediaInfo {
95105
is MediaItem.Video -> mediaInfo
96106
is MediaItem.File -> mediaInfo
97107
is MediaItem.Audio -> mediaInfo
108+
is MediaItem.Voice -> mediaInfo
98109
}
99110
}
100111

@@ -104,6 +115,7 @@ fun MediaItem.Event.mediaSource(): MediaSource {
104115
is MediaItem.Video -> mediaSource
105116
is MediaItem.File -> mediaSource
106117
is MediaItem.Audio -> mediaSource
118+
is MediaItem.Voice -> mediaSource
107119
}
108120
}
109121

@@ -113,5 +125,6 @@ fun MediaItem.Event.thumbnailSource(): MediaSource? {
113125
is MediaItem.Video -> thumbnailSource
114126
is MediaItem.File -> null
115127
is MediaItem.Audio -> null
128+
is MediaItem.Voice -> null
116129
}
117130
}

libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaItemsPostProcessor.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ class MediaItemsPostProcessor @Inject constructor() {
5757
imageAndVideoItemsSubList.add(0, item)
5858
}
5959
is MediaItem.Audio,
60+
is MediaItem.Voice,
6061
is MediaItem.File -> {
6162
fileItemsSublist.add(0, item)
6263
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* Copyright 2024 New Vector Ltd.
3+
*
4+
* SPDX-License-Identifier: AGPL-3.0-only
5+
* Please see LICENSE in the repository root for full details.
6+
*/
7+
8+
package io.element.android.libraries.mediaviewer.impl.gallery.di
9+
10+
import io.element.android.libraries.architecture.Presenter
11+
import io.element.android.libraries.mediaviewer.impl.gallery.MediaItem
12+
import io.element.android.libraries.voiceplayer.api.VoiceMessageState
13+
import io.element.android.libraries.voiceplayer.api.aVoiceMessageState
14+
15+
/**
16+
* A fake [MediaItemPresenterFactories] for screenshot tests.
17+
*/
18+
fun aFakeMediaItemPresenterFactories() = MediaItemPresenterFactories(
19+
mapOf(
20+
Pair(
21+
MediaItem.Voice::class.java,
22+
MediaItemPresenterFactory<MediaItem.Voice, VoiceMessageState> { Presenter { aVoiceMessageState() } },
23+
),
24+
)
25+
)

0 commit comments

Comments
 (0)