Skip to content

Commit d7cb8e0

Browse files
authored
Merge pull request #900 from vector-im/feature/fga/better_media_handling
Feature/fga/better media handling
2 parents 1d99189 + e98aee9 commit d7cb8e0

File tree

47 files changed

+402
-77
lines changed

Some content is hidden

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

47 files changed

+402
-77
lines changed

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesFlowNode.kt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import io.element.android.features.messages.impl.media.viewer.MediaViewerNode
4141
import io.element.android.features.messages.impl.report.ReportMessageNode
4242
import io.element.android.features.messages.impl.timeline.debug.EventDebugInfoNode
4343
import io.element.android.features.messages.impl.timeline.model.TimelineItem
44+
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemAudioContent
4445
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemFileContent
4546
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemImageContent
4647
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemLocationContent
@@ -224,6 +225,20 @@ class MessagesFlowNode @AssistedInject constructor(
224225
)
225226
backstack.push(navTarget)
226227
}
228+
is TimelineItemAudioContent -> {
229+
val mediaSource = event.content.audioSource
230+
val navTarget = NavTarget.MediaViewer(
231+
mediaInfo = MediaInfo(
232+
name = event.content.body,
233+
mimeType = event.content.mimeType,
234+
formattedFileSize = event.content.formattedFileSize,
235+
fileExtension = event.content.fileExtension
236+
),
237+
mediaSource = mediaSource,
238+
thumbnailSource = null,
239+
)
240+
backstack.push(navTarget)
241+
}
227242
is TimelineItemLocationContent -> {
228243
val navTarget = NavTarget.LocationViewer(
229244
location = event.content.location,

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ import io.element.android.features.messages.impl.timeline.TimelinePresenter
4343
import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionPresenter
4444
import io.element.android.features.messages.impl.timeline.components.retrysendmenu.RetrySendMenuPresenter
4545
import io.element.android.features.messages.impl.timeline.model.TimelineItem
46+
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemAudioContent
4647
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEncryptedContent
4748
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemFileContent
4849
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemImageContent
@@ -108,10 +109,10 @@ class MessagesPresenter @AssistedInject constructor(
108109

109110
val syncUpdateFlow = room.syncUpdateFlow.collectAsState()
110111
val userHasPermissionToSendMessage by room.canSendEventAsState(type = MessageEventType.ROOM_MESSAGE, updateKey = syncUpdateFlow.value)
111-
val roomName by produceState(initialValue = room.displayName, key1 = syncUpdateFlow.value){
112+
val roomName by produceState(initialValue = room.displayName, key1 = syncUpdateFlow.value) {
112113
value = room.displayName
113114
}
114-
val roomAvatar by produceState(initialValue = room.avatarData(), key1 = syncUpdateFlow.value){
115+
val roomAvatar by produceState(initialValue = room.avatarData(), key1 = syncUpdateFlow.value) {
115116
value = room.avatarData()
116117
}
117118
var hasDismissedInviteDialog by rememberSaveable {
@@ -250,28 +251,28 @@ class MessagesPresenter @AssistedInject constructor(
250251
val textContent = messageSummaryFormatter.format(targetEvent)
251252
val attachmentThumbnailInfo = when (targetEvent.content) {
252253
is TimelineItemImageContent -> AttachmentThumbnailInfo(
253-
mediaSource = targetEvent.content.mediaSource,
254+
thumbnailSource = targetEvent.content.thumbnailSource,
254255
textContent = targetEvent.content.body,
255256
type = AttachmentThumbnailType.Image,
256257
blurHash = targetEvent.content.blurhash,
257258
)
258259
is TimelineItemVideoContent -> AttachmentThumbnailInfo(
259-
mediaSource = targetEvent.content.thumbnailSource,
260+
thumbnailSource = targetEvent.content.thumbnailSource,
260261
textContent = targetEvent.content.body,
261262
type = AttachmentThumbnailType.Video,
262263
blurHash = targetEvent.content.blurHash,
263264
)
264265
is TimelineItemFileContent -> AttachmentThumbnailInfo(
265-
mediaSource = targetEvent.content.thumbnailSource,
266+
thumbnailSource = targetEvent.content.thumbnailSource,
266267
textContent = targetEvent.content.body,
267268
type = AttachmentThumbnailType.File,
268-
blurHash = null,
269+
)
270+
is TimelineItemAudioContent -> AttachmentThumbnailInfo(
271+
textContent = targetEvent.content.body,
272+
type = AttachmentThumbnailType.Audio,
269273
)
270274
is TimelineItemLocationContent -> AttachmentThumbnailInfo(
271-
mediaSource = null,
272-
textContent = null,
273275
type = AttachmentThumbnailType.Location,
274-
blurHash = null,
275276
)
276277
is TimelineItemTextBasedContent,
277278
is TimelineItemRedactedContent,

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListView.kt

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
5656
import androidx.compose.ui.unit.dp
5757
import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction
5858
import io.element.android.features.messages.impl.timeline.model.TimelineItem
59+
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemAudioContent
5960
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEncryptedContent
6061
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemFileContent
6162
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemImageContent
@@ -246,8 +247,6 @@ private fun MessageSummary(event: TimelineItem.Event, modifier: Modifier = Modif
246247
info = AttachmentThumbnailInfo(
247248
type = AttachmentThumbnailType.Location,
248249
textContent = stringResource(CommonStrings.common_shared_location),
249-
mediaSource = null,
250-
blurHash = null,
251250
)
252251
)
253252
}
@@ -258,9 +257,9 @@ private fun MessageSummary(event: TimelineItem.Event, modifier: Modifier = Modif
258257
AttachmentThumbnail(
259258
modifier = imageModifier,
260259
info = AttachmentThumbnailInfo(
261-
mediaSource = event.content.mediaSource,
260+
thumbnailSource = event.content.mediaSource,
262261
textContent = textContent,
263-
type = AttachmentThumbnailType.File,
262+
type = AttachmentThumbnailType.Image,
264263
blurHash = event.content.blurhash,
265264
)
266265
)
@@ -272,7 +271,7 @@ private fun MessageSummary(event: TimelineItem.Event, modifier: Modifier = Modif
272271
AttachmentThumbnail(
273272
modifier = imageModifier,
274273
info = AttachmentThumbnailInfo(
275-
mediaSource = event.content.thumbnailSource,
274+
thumbnailSource = event.content.thumbnailSource,
276275
textContent = textContent,
277276
type = AttachmentThumbnailType.Video,
278277
blurHash = event.content.blurHash,
@@ -286,10 +285,21 @@ private fun MessageSummary(event: TimelineItem.Event, modifier: Modifier = Modif
286285
AttachmentThumbnail(
287286
modifier = imageModifier,
288287
info = AttachmentThumbnailInfo(
289-
mediaSource = null,
288+
thumbnailSource = event.content.thumbnailSource,
290289
textContent = textContent,
291290
type = AttachmentThumbnailType.File,
292-
blurHash = null
291+
)
292+
)
293+
}
294+
content = { ContentForBody(event.content.body) }
295+
}
296+
is TimelineItemAudioContent -> {
297+
icon = {
298+
AttachmentThumbnail(
299+
modifier = imageModifier,
300+
info = AttachmentThumbnailInfo(
301+
textContent = textContent,
302+
type = AttachmentThumbnailType.Audio,
293303
)
294304
)
295305
}

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/local/LocalMediaView.kt

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import androidx.compose.foundation.layout.size
3232
import androidx.compose.foundation.shape.CircleShape
3333
import androidx.compose.material.icons.Icons
3434
import androidx.compose.material.icons.outlined.Attachment
35+
import androidx.compose.material.icons.outlined.GraphicEq
3536
import androidx.compose.material3.MaterialTheme
3637
import androidx.compose.runtime.Composable
3738
import androidx.compose.runtime.LaunchedEffect
@@ -47,7 +48,6 @@ import androidx.compose.ui.res.painterResource
4748
import androidx.compose.ui.text.style.TextAlign
4849
import androidx.compose.ui.text.style.TextOverflow
4950
import androidx.compose.ui.unit.dp
50-
import androidx.compose.ui.unit.sp
5151
import androidx.compose.ui.viewinterop.AndroidView
5252
import androidx.lifecycle.Lifecycle
5353
import androidx.media3.common.MediaItem
@@ -59,7 +59,9 @@ import io.element.android.features.messages.impl.media.helper.formatFileExtensio
5959
import io.element.android.features.messages.impl.media.local.exoplayer.ExoPlayerWrapper
6060
import io.element.android.features.messages.impl.media.local.pdf.PdfViewer
6161
import io.element.android.features.messages.impl.media.local.pdf.rememberPdfViewerState
62+
import io.element.android.libraries.core.bool.orFalse
6263
import io.element.android.libraries.core.mimetype.MimeTypes
64+
import io.element.android.libraries.core.mimetype.MimeTypes.isMimeTypeAudio
6365
import io.element.android.libraries.core.mimetype.MimeTypes.isMimeTypeImage
6466
import io.element.android.libraries.core.mimetype.MimeTypes.isMimeTypeVideo
6567
import io.element.android.libraries.designsystem.R
@@ -103,6 +105,7 @@ fun LocalMediaView(
103105
zoomableState = zoomableState,
104106
modifier = modifier
105107
)
108+
//TODO handle audio with exoplayer
106109
else -> MediaFileView(
107110
localMediaViewState = localMediaViewState,
108111
uri = localMedia?.uri,
@@ -215,6 +218,7 @@ fun MediaFileView(
215218
info: MediaInfo?,
216219
modifier: Modifier = Modifier,
217220
) {
221+
val isAudio = info?.mimeType.isMimeTypeAudio().orFalse()
218222
localMediaViewState.isReady = uri != null
219223
Box(modifier = modifier.padding(horizontal = 8.dp), contentAlignment = Alignment.Center) {
220224
Column(horizontalAlignment = Alignment.CenterHorizontally) {
@@ -226,12 +230,12 @@ fun MediaFileView(
226230
contentAlignment = Alignment.Center,
227231
) {
228232
Icon(
229-
imageVector = Icons.Outlined.Attachment,
233+
imageVector = if (isAudio) Icons.Outlined.GraphicEq else Icons.Outlined.Attachment,
230234
contentDescription = null,
231235
tint = MaterialTheme.colorScheme.background,
232236
modifier = Modifier
233237
.size(32.dp)
234-
.rotate(-45f),
238+
.rotate(if (isAudio) 0f else -45f),
235239
)
236240
}
237241
if (info != null) {

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/local/MediaInfo.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ data class MediaInfo(
2929
) : Parcelable
3030

3131
fun anImageInfo(): MediaInfo = MediaInfo(
32-
"an image file.jpg", MimeTypes.Jpeg, "4MB","jpg"
32+
"an image file.jpg", MimeTypes.Jpeg, "4MB", "jpg"
3333
)
3434

3535
fun aVideoInfo(): MediaInfo = MediaInfo(
@@ -43,3 +43,7 @@ fun aPdfInfo(): MediaInfo = MediaInfo(
4343
fun aFileInfo(): MediaInfo = MediaInfo(
4444
"an apk file.apk", MimeTypes.Apk, "50MB", "apk"
4545
)
46+
47+
fun anAudioInfo(): MediaInfo = MediaInfo(
48+
"an audio file.mp3", MimeTypes.Mp3, "7MB", "mp3"
49+
)

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/viewer/MediaViewerStateProvider.kt

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import io.element.android.features.messages.impl.media.local.MediaInfo
2323
import io.element.android.features.messages.impl.media.local.aFileInfo
2424
import io.element.android.features.messages.impl.media.local.aPdfInfo
2525
import io.element.android.features.messages.impl.media.local.aVideoInfo
26+
import io.element.android.features.messages.impl.media.local.anAudioInfo
2627
import io.element.android.features.messages.impl.media.local.anImageInfo
2728
import io.element.android.libraries.architecture.Async
2829

@@ -59,7 +60,17 @@ open class MediaViewerStateProvider : PreviewParameterProvider<MediaViewerState>
5960
LocalMedia(Uri.EMPTY, aFileInfo())
6061
),
6162
aFileInfo(),
62-
)
63+
),
64+
aMediaViewerState(
65+
Async.Loading(),
66+
anAudioInfo(),
67+
),
68+
aMediaViewerState(
69+
Async.Success(
70+
LocalMedia(Uri.EMPTY, anAudioInfo())
71+
),
72+
anAudioInfo(),
73+
),
6374
)
6475
}
6576

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,6 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
5656
import androidx.compose.ui.unit.Dp
5757
import androidx.compose.ui.unit.IntOffset
5858
import androidx.compose.ui.unit.dp
59-
import androidx.compose.ui.unit.sp
6059
import androidx.compose.ui.zIndex
6160
import androidx.constraintlayout.compose.ConstrainScope
6261
import androidx.constraintlayout.compose.ConstraintLayout
@@ -85,6 +84,7 @@ import io.element.android.libraries.designsystem.text.toPx
8584
import io.element.android.libraries.designsystem.theme.components.Text
8685
import io.element.android.libraries.matrix.api.core.EventId
8786
import io.element.android.libraries.matrix.api.core.UserId
87+
import io.element.android.libraries.matrix.api.timeline.item.event.AudioMessageType
8888
import io.element.android.libraries.matrix.api.timeline.item.event.FileMessageType
8989
import io.element.android.libraries.matrix.api.timeline.item.event.ImageMessageType
9090
import io.element.android.libraries.matrix.api.timeline.item.event.InReplyTo
@@ -521,28 +521,29 @@ private fun ReplyToContent(
521521
private fun attachmentThumbnailInfoForInReplyTo(inReplyTo: InReplyTo.Ready) =
522522
when (val type = inReplyTo.content.type) {
523523
is ImageMessageType -> AttachmentThumbnailInfo(
524-
mediaSource = type.info?.thumbnailSource,
524+
thumbnailSource = type.info?.thumbnailSource,
525525
textContent = inReplyTo.content.body,
526526
type = AttachmentThumbnailType.Image,
527527
blurHash = type.info?.blurhash,
528528
)
529529
is VideoMessageType -> AttachmentThumbnailInfo(
530-
mediaSource = type.info?.thumbnailSource,
530+
thumbnailSource = type.info?.thumbnailSource,
531531
textContent = inReplyTo.content.body,
532532
type = AttachmentThumbnailType.Video,
533533
blurHash = type.info?.blurhash,
534534
)
535535
is FileMessageType -> AttachmentThumbnailInfo(
536-
mediaSource = type.info?.thumbnailSource,
536+
thumbnailSource = type.info?.thumbnailSource,
537537
textContent = inReplyTo.content.body,
538538
type = AttachmentThumbnailType.File,
539-
blurHash = null,
540539
)
541540
is LocationMessageType -> AttachmentThumbnailInfo(
542-
mediaSource = null,
543541
textContent = inReplyTo.content.body,
544542
type = AttachmentThumbnailType.Location,
545-
blurHash = null,
543+
)
544+
is AudioMessageType -> AttachmentThumbnailInfo(
545+
textContent = inReplyTo.content.body,
546+
type = AttachmentThumbnailType.Audio,
546547
)
547548
else -> null
548549
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/*
2+
* Copyright (c) 2023 New Vector Ltd
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.element.android.features.messages.impl.timeline.components.event
18+
19+
import androidx.compose.foundation.background
20+
import androidx.compose.foundation.layout.Box
21+
import androidx.compose.foundation.layout.Column
22+
import androidx.compose.foundation.layout.Row
23+
import androidx.compose.foundation.layout.Spacer
24+
import androidx.compose.foundation.layout.size
25+
import androidx.compose.foundation.layout.width
26+
import androidx.compose.foundation.shape.CircleShape
27+
import androidx.compose.material.icons.Icons
28+
import androidx.compose.material.icons.outlined.GraphicEq
29+
import androidx.compose.runtime.Composable
30+
import androidx.compose.ui.Alignment
31+
import androidx.compose.ui.Modifier
32+
import androidx.compose.ui.draw.clip
33+
import androidx.compose.ui.text.style.TextOverflow
34+
import androidx.compose.ui.tooling.preview.PreviewParameter
35+
import androidx.compose.ui.unit.dp
36+
import androidx.compose.ui.unit.sp
37+
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemAudioContent
38+
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemAudioContentProvider
39+
import io.element.android.libraries.designsystem.preview.DayNightPreviews
40+
import io.element.android.libraries.designsystem.preview.ElementPreview
41+
import io.element.android.libraries.designsystem.theme.components.Icon
42+
import io.element.android.libraries.designsystem.theme.components.Text
43+
import io.element.android.libraries.theme.ElementTheme
44+
45+
@Composable
46+
fun TimelineItemAudioView(
47+
content: TimelineItemAudioContent,
48+
extraPadding: ExtraPadding,
49+
modifier: Modifier = Modifier,
50+
) {
51+
Row(
52+
modifier = modifier,
53+
) {
54+
Box(
55+
modifier = Modifier
56+
.size(32.dp)
57+
.clip(CircleShape)
58+
.background(ElementTheme.materialColors.background),
59+
contentAlignment = Alignment.Center,
60+
) {
61+
Icon(
62+
imageVector = Icons.Outlined.GraphicEq,
63+
contentDescription = null,
64+
tint = ElementTheme.materialColors.primary,
65+
modifier = Modifier
66+
.size(16.dp),
67+
)
68+
}
69+
Spacer(Modifier.width(8.dp))
70+
Column {
71+
Text(
72+
text = content.body,
73+
color = ElementTheme.materialColors.primary,
74+
maxLines = 2,
75+
style = ElementTheme.typography.fontBodyLgRegular,
76+
overflow = TextOverflow.Ellipsis
77+
)
78+
Text(
79+
text = content.fileExtensionAndSize + extraPadding.getStr(12.sp),
80+
color = ElementTheme.materialColors.secondary,
81+
style = ElementTheme.typography.fontBodySmRegular,
82+
maxLines = 1,
83+
overflow = TextOverflow.Ellipsis,
84+
)
85+
}
86+
}
87+
}
88+
89+
@DayNightPreviews
90+
@Composable
91+
internal fun TimelineItemAudioViewPreview(@PreviewParameter(TimelineItemAudioContentProvider::class) content: TimelineItemAudioContent) =
92+
ElementPreview {
93+
TimelineItemAudioView(
94+
content,
95+
extraPadding = noExtraPadding,
96+
)
97+
}

0 commit comments

Comments
 (0)