Media: render audio content

This commit is contained in:
ganfra 2023-07-13 18:09:58 +02:00
parent be6b8c825b
commit 7e5d339922
15 changed files with 257 additions and 35 deletions

View file

@ -43,6 +43,7 @@ import io.element.android.features.messages.impl.timeline.TimelinePresenter
import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionPresenter import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionPresenter
import io.element.android.features.messages.impl.timeline.components.retrysendmenu.RetrySendMenuPresenter import io.element.android.features.messages.impl.timeline.components.retrysendmenu.RetrySendMenuPresenter
import io.element.android.features.messages.impl.timeline.model.TimelineItem 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.TimelineItemEncryptedContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEncryptedContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemFileContent 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.TimelineItemImageContent
@ -108,10 +109,10 @@ class MessagesPresenter @AssistedInject constructor(
val syncUpdateFlow = room.syncUpdateFlow.collectAsState() val syncUpdateFlow = room.syncUpdateFlow.collectAsState()
val userHasPermissionToSendMessage by room.canSendEventAsState(type = MessageEventType.ROOM_MESSAGE, updateKey = syncUpdateFlow.value) val userHasPermissionToSendMessage by room.canSendEventAsState(type = MessageEventType.ROOM_MESSAGE, updateKey = syncUpdateFlow.value)
val roomName by produceState(initialValue = room.displayName, key1 = syncUpdateFlow.value){ val roomName by produceState(initialValue = room.displayName, key1 = syncUpdateFlow.value) {
value = room.displayName value = room.displayName
} }
val roomAvatar by produceState(initialValue = room.avatarData(), key1 = syncUpdateFlow.value){ val roomAvatar by produceState(initialValue = room.avatarData(), key1 = syncUpdateFlow.value) {
value = room.avatarData() value = room.avatarData()
} }
var hasDismissedInviteDialog by rememberSaveable { var hasDismissedInviteDialog by rememberSaveable {
@ -250,28 +251,28 @@ class MessagesPresenter @AssistedInject constructor(
val textContent = messageSummaryFormatter.format(targetEvent) val textContent = messageSummaryFormatter.format(targetEvent)
val attachmentThumbnailInfo = when (targetEvent.content) { val attachmentThumbnailInfo = when (targetEvent.content) {
is TimelineItemImageContent -> AttachmentThumbnailInfo( is TimelineItemImageContent -> AttachmentThumbnailInfo(
mediaSource = targetEvent.content.mediaSource, thumbnailSource = targetEvent.content.thumbnailSource,
textContent = targetEvent.content.body, textContent = targetEvent.content.body,
type = AttachmentThumbnailType.Image, type = AttachmentThumbnailType.Image,
blurHash = targetEvent.content.blurhash, blurHash = targetEvent.content.blurhash,
) )
is TimelineItemVideoContent -> AttachmentThumbnailInfo( is TimelineItemVideoContent -> AttachmentThumbnailInfo(
mediaSource = targetEvent.content.thumbnailSource, thumbnailSource = targetEvent.content.thumbnailSource,
textContent = targetEvent.content.body, textContent = targetEvent.content.body,
type = AttachmentThumbnailType.Video, type = AttachmentThumbnailType.Video,
blurHash = targetEvent.content.blurHash, blurHash = targetEvent.content.blurHash,
) )
is TimelineItemFileContent -> AttachmentThumbnailInfo( is TimelineItemFileContent -> AttachmentThumbnailInfo(
mediaSource = targetEvent.content.thumbnailSource, thumbnailSource = targetEvent.content.thumbnailSource,
textContent = targetEvent.content.body, textContent = targetEvent.content.body,
type = AttachmentThumbnailType.File, type = AttachmentThumbnailType.File,
blurHash = null, )
is TimelineItemAudioContent -> AttachmentThumbnailInfo(
textContent = targetEvent.content.body,
type = AttachmentThumbnailType.Audio,
) )
is TimelineItemLocationContent -> AttachmentThumbnailInfo( is TimelineItemLocationContent -> AttachmentThumbnailInfo(
mediaSource = null,
textContent = null,
type = AttachmentThumbnailType.Location, type = AttachmentThumbnailType.Location,
blurHash = null,
) )
is TimelineItemTextBasedContent, is TimelineItemTextBasedContent,
is TimelineItemRedactedContent, is TimelineItemRedactedContent,

View file

@ -56,6 +56,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction
import io.element.android.features.messages.impl.timeline.model.TimelineItem 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.TimelineItemEncryptedContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEncryptedContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemFileContent 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.TimelineItemImageContent
@ -246,7 +247,7 @@ private fun MessageSummary(event: TimelineItem.Event, modifier: Modifier = Modif
info = AttachmentThumbnailInfo( info = AttachmentThumbnailInfo(
type = AttachmentThumbnailType.Location, type = AttachmentThumbnailType.Location,
textContent = stringResource(CommonStrings.common_shared_location), textContent = stringResource(CommonStrings.common_shared_location),
mediaSource = null, thumbnailSource = null,
blurHash = null, blurHash = null,
) )
) )
@ -258,7 +259,7 @@ private fun MessageSummary(event: TimelineItem.Event, modifier: Modifier = Modif
AttachmentThumbnail( AttachmentThumbnail(
modifier = imageModifier, modifier = imageModifier,
info = AttachmentThumbnailInfo( info = AttachmentThumbnailInfo(
mediaSource = event.content.mediaSource, thumbnailSource = event.content.mediaSource,
textContent = textContent, textContent = textContent,
type = AttachmentThumbnailType.File, type = AttachmentThumbnailType.File,
blurHash = event.content.blurhash, blurHash = event.content.blurhash,
@ -272,7 +273,7 @@ private fun MessageSummary(event: TimelineItem.Event, modifier: Modifier = Modif
AttachmentThumbnail( AttachmentThumbnail(
modifier = imageModifier, modifier = imageModifier,
info = AttachmentThumbnailInfo( info = AttachmentThumbnailInfo(
mediaSource = event.content.thumbnailSource, thumbnailSource = event.content.thumbnailSource,
textContent = textContent, textContent = textContent,
type = AttachmentThumbnailType.Video, type = AttachmentThumbnailType.Video,
blurHash = event.content.blurHash, blurHash = event.content.blurHash,
@ -286,7 +287,7 @@ private fun MessageSummary(event: TimelineItem.Event, modifier: Modifier = Modif
AttachmentThumbnail( AttachmentThumbnail(
modifier = imageModifier, modifier = imageModifier,
info = AttachmentThumbnailInfo( info = AttachmentThumbnailInfo(
mediaSource = null, thumbnailSource = event.content.thumbnailSource,
textContent = textContent, textContent = textContent,
type = AttachmentThumbnailType.File, type = AttachmentThumbnailType.File,
blurHash = null blurHash = null
@ -295,6 +296,18 @@ private fun MessageSummary(event: TimelineItem.Event, modifier: Modifier = Modif
} }
content = { ContentForBody(event.content.body) } content = { ContentForBody(event.content.body) }
} }
is TimelineItemAudioContent -> {
icon = {
AttachmentThumbnail(
modifier = imageModifier,
info = AttachmentThumbnailInfo(
textContent = textContent,
type = AttachmentThumbnailType.Audio,
)
)
}
content = { ContentForBody(event.content.body) }
}
} }
Row(modifier = modifier) { Row(modifier = modifier) {
icon() icon()

View file

@ -56,7 +56,6 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.zIndex import androidx.compose.ui.zIndex
import androidx.constraintlayout.compose.ConstrainScope import androidx.constraintlayout.compose.ConstrainScope
import androidx.constraintlayout.compose.ConstraintLayout import androidx.constraintlayout.compose.ConstraintLayout
@ -85,6 +84,7 @@ import io.element.android.libraries.designsystem.text.toPx
import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.timeline.item.event.AudioMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.FileMessageType import io.element.android.libraries.matrix.api.timeline.item.event.FileMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.ImageMessageType import io.element.android.libraries.matrix.api.timeline.item.event.ImageMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.InReplyTo import io.element.android.libraries.matrix.api.timeline.item.event.InReplyTo
@ -521,28 +521,29 @@ private fun ReplyToContent(
private fun attachmentThumbnailInfoForInReplyTo(inReplyTo: InReplyTo.Ready) = private fun attachmentThumbnailInfoForInReplyTo(inReplyTo: InReplyTo.Ready) =
when (val type = inReplyTo.content.type) { when (val type = inReplyTo.content.type) {
is ImageMessageType -> AttachmentThumbnailInfo( is ImageMessageType -> AttachmentThumbnailInfo(
mediaSource = type.info?.thumbnailSource, thumbnailSource = type.info?.thumbnailSource,
textContent = inReplyTo.content.body, textContent = inReplyTo.content.body,
type = AttachmentThumbnailType.Image, type = AttachmentThumbnailType.Image,
blurHash = type.info?.blurhash, blurHash = type.info?.blurhash,
) )
is VideoMessageType -> AttachmentThumbnailInfo( is VideoMessageType -> AttachmentThumbnailInfo(
mediaSource = type.info?.thumbnailSource, thumbnailSource = type.info?.thumbnailSource,
textContent = inReplyTo.content.body, textContent = inReplyTo.content.body,
type = AttachmentThumbnailType.Video, type = AttachmentThumbnailType.Video,
blurHash = type.info?.blurhash, blurHash = type.info?.blurhash,
) )
is FileMessageType -> AttachmentThumbnailInfo( is FileMessageType -> AttachmentThumbnailInfo(
mediaSource = type.info?.thumbnailSource, thumbnailSource = type.info?.thumbnailSource,
textContent = inReplyTo.content.body, textContent = inReplyTo.content.body,
type = AttachmentThumbnailType.File, type = AttachmentThumbnailType.File,
blurHash = null,
) )
is LocationMessageType -> AttachmentThumbnailInfo( is LocationMessageType -> AttachmentThumbnailInfo(
mediaSource = null,
textContent = inReplyTo.content.body, textContent = inReplyTo.content.body,
type = AttachmentThumbnailType.Location, type = AttachmentThumbnailType.Location,
blurHash = null, )
is AudioMessageType -> AttachmentThumbnailInfo(
textContent = inReplyTo.content.body,
type = AttachmentThumbnailType.Audio,
) )
else -> null else -> null
} }

View file

@ -0,0 +1,108 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.messages.impl.timeline.components.event
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.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Attachment
import androidx.compose.material.icons.outlined.GraphicEq
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.rotate
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 androidx.compose.ui.unit.sp
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemAudioContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemAudioContentProvider
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.theme.components.Icon
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.theme.ElementTheme
@Composable
fun TimelineItemAudioView(
content: TimelineItemAudioContent,
extraPadding: ExtraPadding,
modifier: Modifier = Modifier,
) {
Row(
modifier = modifier,
) {
Box(
modifier = Modifier
.size(32.dp)
.clip(CircleShape)
.background(ElementTheme.materialColors.background),
contentAlignment = Alignment.Center,
) {
Icon(
imageVector = Icons.Outlined.GraphicEq,
contentDescription = null,
tint = ElementTheme.materialColors.primary,
modifier = Modifier
.size(16.dp),
)
}
Spacer(Modifier.width(8.dp))
Column {
Text(
text = content.body,
color = ElementTheme.materialColors.primary,
maxLines = 2,
style = ElementTheme.typography.fontBodyLgRegular,
overflow = TextOverflow.Ellipsis
)
Text(
text = content.fileExtensionAndSize + extraPadding.getStr(12.sp),
color = ElementTheme.materialColors.secondary,
style = ElementTheme.typography.fontBodySmRegular,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}
}
}
@Preview
@Composable
internal fun TimelineItemAudioViewLightPreview(@PreviewParameter(TimelineItemAudioContentProvider::class) content: TimelineItemAudioContent) =
ElementPreviewLight { ContentToPreview(content) }
@Preview
@Composable
internal fun TimelineItemAudioViewDarkPreview(@PreviewParameter(TimelineItemAudioContentProvider::class) content: TimelineItemAudioContent) =
ElementPreviewDark { ContentToPreview(content) }
@Composable
private fun ContentToPreview(content: TimelineItemAudioContent) {
TimelineItemAudioView(
content,
extraPadding = noExtraPadding,
)
}

View file

@ -19,6 +19,7 @@ package io.element.android.features.messages.impl.timeline.components.event
import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemAudioContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEncryptedContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEncryptedContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemFileContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemFileContent
@ -80,6 +81,11 @@ fun TimelineItemEventContentView(
extraPadding = extraPadding, extraPadding = extraPadding,
modifier = modifier modifier = modifier
) )
is TimelineItemAudioContent -> TimelineItemAudioView(
content = content,
extraPadding = extraPadding,
modifier = modifier
)
is TimelineItemStateContent -> TimelineItemStateView( is TimelineItemStateContent -> TimelineItemStateView(
content = content, content = content,
modifier = modifier modifier = modifier

View file

@ -17,6 +17,7 @@
package io.element.android.features.messages.impl.timeline.factories.event package io.element.android.features.messages.impl.timeline.factories.event
import io.element.android.features.location.api.Location import io.element.android.features.location.api.Location
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemAudioContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEmoteContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEmoteContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemFileContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemFileContent
@ -30,6 +31,7 @@ import io.element.android.features.messages.impl.timeline.util.FileExtensionExtr
import io.element.android.features.messages.impl.timeline.util.toHtmlDocument import io.element.android.features.messages.impl.timeline.util.toHtmlDocument
import io.element.android.libraries.androidutils.filesize.FileSizeFormatter import io.element.android.libraries.androidutils.filesize.FileSizeFormatter
import io.element.android.libraries.core.mimetype.MimeTypes import io.element.android.libraries.core.mimetype.MimeTypes
import io.element.android.libraries.matrix.api.timeline.item.event.AudioMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.EmoteMessageType import io.element.android.libraries.matrix.api.timeline.item.event.EmoteMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.FileMessageType import io.element.android.libraries.matrix.api.timeline.item.event.FileMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.ImageMessageType import io.element.android.libraries.matrix.api.timeline.item.event.ImageMessageType
@ -99,6 +101,14 @@ class TimelineItemContentMessageFactory @Inject constructor(
fileExtension = fileExtensionExtractor.extractFromName(messageType.body) fileExtension = fileExtensionExtractor.extractFromName(messageType.body)
) )
} }
is AudioMessageType -> TimelineItemAudioContent(
body = messageType.body,
audioSource = messageType.source,
duration = messageType.info?.duration?.toMillis() ?: 0L,
mimeType = messageType.info?.mimetype ?: MimeTypes.OctetStream,
formattedFileSize = fileSizeFormatter.format(messageType.info?.size ?: 0),
fileExtension = fileExtensionExtractor.extractFromName(messageType.body)
)
is FileMessageType -> TimelineItemFileContent( is FileMessageType -> TimelineItemFileContent(
body = messageType.body, body = messageType.body,
thumbnailSource = messageType.info?.thumbnailSource, thumbnailSource = messageType.info?.thumbnailSource,

View file

@ -17,6 +17,7 @@
package io.element.android.features.messages.impl.timeline.groups package io.element.android.features.messages.impl.timeline.groups
import io.element.android.features.messages.impl.timeline.model.TimelineItem 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.TimelineItemEncryptedContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEncryptedContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemFileContent 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.TimelineItemImageContent
@ -52,6 +53,7 @@ internal fun TimelineItem.Event.canBeGrouped(): Boolean {
is TimelineItemImageContent, is TimelineItemImageContent,
is TimelineItemFileContent, is TimelineItemFileContent,
is TimelineItemVideoContent, is TimelineItemVideoContent,
is TimelineItemAudioContent,
is TimelineItemLocationContent, is TimelineItemLocationContent,
TimelineItemRedactedContent, TimelineItemRedactedContent,
TimelineItemUnknownContent -> false TimelineItemUnknownContent -> false

View file

@ -0,0 +1,33 @@
/*
* Copyright (c) 2022 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.messages.impl.timeline.model.event
import io.element.android.features.messages.impl.media.helper.formatFileExtensionAndSize
import io.element.android.libraries.matrix.api.media.MediaSource
data class TimelineItemAudioContent(
val body: String,
val duration: Long,
val audioSource: MediaSource,
val mimeType: String,
val formattedFileSize: String,
val fileExtension: String,
) : TimelineItemEventContent {
val fileExtensionAndSize = formatFileExtensionAndSize(fileExtension, formattedFileSize)
override val type: String = "TimelineItemAudioContent"
}

View file

@ -0,0 +1,39 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.messages.impl.timeline.model.event
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.libraries.core.mimetype.MimeTypes
import io.element.android.libraries.matrix.api.media.MediaSource
open class TimelineItemAudioContentProvider : PreviewParameterProvider<TimelineItemAudioContent> {
override val values: Sequence<TimelineItemAudioContent>
get() = sequenceOf(
aTimelineItemAudioContent("A sound.mp3"),
aTimelineItemAudioContent("A bigger name sound.mp3"),
aTimelineItemAudioContent("An even bigger bigger bigger bigger bigger bigger bigger sound name which doesn't fit .mp3"),
)
}
fun aTimelineItemAudioContent(fileName: String = "A sound.mp3") = TimelineItemAudioContent(
body = fileName,
mimeType = MimeTypes.Pdf,
formattedFileSize = "100kB",
fileExtension = "mp3",
duration = 100,
audioSource = MediaSource(""),
)

View file

@ -19,6 +19,7 @@ package io.element.android.features.messages.impl.utils.messagesummary
import android.content.Context import android.content.Context
import com.squareup.anvil.annotations.ContributesBinding import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.features.messages.impl.timeline.model.TimelineItem 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.TimelineItemEncryptedContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEncryptedContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemFileContent 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.TimelineItemImageContent
@ -50,6 +51,7 @@ class MessageSummaryFormatterImpl @Inject constructor(
is TimelineItemImageContent -> context.getString(CommonStrings.common_image) is TimelineItemImageContent -> context.getString(CommonStrings.common_image)
is TimelineItemVideoContent -> context.getString(CommonStrings.common_video) is TimelineItemVideoContent -> context.getString(CommonStrings.common_video)
is TimelineItemFileContent -> context.getString(CommonStrings.common_file) is TimelineItemFileContent -> context.getString(CommonStrings.common_file)
is TimelineItemAudioContent -> context.getString(CommonStrings.common_audio)
} }
} }
} }

View file

@ -21,5 +21,5 @@ import java.time.Duration
data class AudioInfo( data class AudioInfo(
val duration: Duration?, val duration: Duration?,
val size: Long?, val size: Long?,
val mimeType: String?, val mimetype: String?,
) )

View file

@ -22,11 +22,11 @@ import org.matrix.rustcomponents.sdk.AudioInfo as RustAudioInfo
fun RustAudioInfo.map(): AudioInfo = AudioInfo( fun RustAudioInfo.map(): AudioInfo = AudioInfo(
duration = duration, duration = duration,
size = size?.toLong(), size = size?.toLong(),
mimeType = mimetype mimetype = mimetype
) )
fun AudioInfo.map(): RustAudioInfo = RustAudioInfo( fun AudioInfo.map(): RustAudioInfo = RustAudioInfo(
duration = duration, duration = duration,
size = size?.toULong(), size = size?.toULong(),
mimetype = mimeType, mimetype = mimetype,
) )

View file

@ -22,6 +22,7 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Attachment import androidx.compose.material.icons.outlined.Attachment
import androidx.compose.material.icons.outlined.GraphicEq
import androidx.compose.material.icons.outlined.VideoCameraBack import androidx.compose.material.icons.outlined.VideoCameraBack
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@ -44,9 +45,9 @@ fun AttachmentThumbnail(
thumbnailSize: Long = 32L, thumbnailSize: Long = 32L,
backgroundColor: Color = MaterialTheme.colorScheme.surface, backgroundColor: Color = MaterialTheme.colorScheme.surface,
) { ) {
if (info.mediaSource != null) { if (info.thumbnailSource != null) {
val mediaRequestData = MediaRequestData( val mediaRequestData = MediaRequestData(
source = info.mediaSource, source = info.thumbnailSource,
kind = MediaRequestData.Kind.Thumbnail(thumbnailSize), kind = MediaRequestData.Kind.Thumbnail(thumbnailSize),
) )
BlurHashAsyncImage( BlurHashAsyncImage(
@ -68,6 +69,12 @@ fun AttachmentThumbnail(
contentDescription = info.textContent, contentDescription = info.textContent,
) )
} }
AttachmentThumbnailType.Audio -> {
Icon(
imageVector = Icons.Outlined.GraphicEq,
contentDescription = info.textContent,
)
}
AttachmentThumbnailType.File -> { AttachmentThumbnailType.File -> {
Icon( Icon(
imageVector = Icons.Outlined.Attachment, imageVector = Icons.Outlined.Attachment,
@ -88,13 +95,13 @@ fun AttachmentThumbnail(
@Parcelize @Parcelize
enum class AttachmentThumbnailType: Parcelable { enum class AttachmentThumbnailType: Parcelable {
Image, Video, File, Location Image, Video, File, Audio, Location
} }
@Parcelize @Parcelize
data class AttachmentThumbnailInfo( data class AttachmentThumbnailInfo(
val mediaSource: MediaSource?, val type: AttachmentThumbnailType,
val textContent: String?, val thumbnailSource: MediaSource? = null,
val type: AttachmentThumbnailType?, val textContent: String? = null,
val blurHash: String?, val blurHash: String? = null,
): Parcelable ): Parcelable

View file

@ -196,7 +196,7 @@ class AndroidMediaPreProcessor @Inject constructor(
val info = AudioInfo( val info = AudioInfo(
duration = extractDuration(), duration = extractDuration(),
size = file.length(), size = file.length(),
mimeType = mimeType, mimetype = mimeType,
) )
MediaUploadInfo.Audio(file, info) MediaUploadInfo.Audio(file, info)

View file

@ -482,7 +482,7 @@ fun TextComposerReplyPreview() = ElementPreview {
senderName = "Alice", senderName = "Alice",
eventId = EventId("$1234"), eventId = EventId("$1234"),
attachmentThumbnailInfo = AttachmentThumbnailInfo( attachmentThumbnailInfo = AttachmentThumbnailInfo(
mediaSource = MediaSource("https://domain.com/image.jpg"), thumbnailSource = MediaSource("https://domain.com/image.jpg"),
textContent = "image.jpg", textContent = "image.jpg",
type = AttachmentThumbnailType.Image, type = AttachmentThumbnailType.Image,
blurHash = "TQF5:I_NtRE4kXt7Z#MwkCIARPjr", blurHash = "TQF5:I_NtRE4kXt7Z#MwkCIARPjr",
@ -500,7 +500,7 @@ fun TextComposerReplyPreview() = ElementPreview {
senderName = "Alice", senderName = "Alice",
eventId = EventId("$1234"), eventId = EventId("$1234"),
attachmentThumbnailInfo = AttachmentThumbnailInfo( attachmentThumbnailInfo = AttachmentThumbnailInfo(
mediaSource = MediaSource("https://domain.com/video.mp4"), thumbnailSource = MediaSource("https://domain.com/video.mp4"),
textContent = "video.mp4", textContent = "video.mp4",
type = AttachmentThumbnailType.Video, type = AttachmentThumbnailType.Video,
blurHash = "TQF5:I_NtRE4kXt7Z#MwkCIARPjr", blurHash = "TQF5:I_NtRE4kXt7Z#MwkCIARPjr",
@ -518,7 +518,7 @@ fun TextComposerReplyPreview() = ElementPreview {
senderName = "Alice", senderName = "Alice",
eventId = EventId("$1234"), eventId = EventId("$1234"),
attachmentThumbnailInfo = AttachmentThumbnailInfo( attachmentThumbnailInfo = AttachmentThumbnailInfo(
mediaSource = null, thumbnailSource = null,
textContent = "logs.txt", textContent = "logs.txt",
type = AttachmentThumbnailType.File, type = AttachmentThumbnailType.File,
blurHash = null, blurHash = null,
@ -536,7 +536,7 @@ fun TextComposerReplyPreview() = ElementPreview {
senderName = "Alice", senderName = "Alice",
eventId = EventId("$1234"), eventId = EventId("$1234"),
attachmentThumbnailInfo = AttachmentThumbnailInfo( attachmentThumbnailInfo = AttachmentThumbnailInfo(
mediaSource = null, thumbnailSource = null,
textContent = null, textContent = null,
type = AttachmentThumbnailType.Location, type = AttachmentThumbnailType.Location,
blurHash = null, blurHash = null,