Split VoiceMessageType from AudioMessageType (#1664)

Currently, for compatibility reasons, we implement MSC3245v1 which puts the voice data inside an audio message type. Though at times it seems impractical to deal with a single message type which effectively represents 2 different kinds of messages.

This PR creates a new message type called `VoiceMessageType` which is used whenever we receive an event with `"msgtype": "m.audio"` which also has the `"org.matrix.msc3245.voice": {}` field. This makes it easier to process voice messages as different entities throughout the rest of the codebase.
This commit is contained in:
Marco Romano 2023-10-27 16:02:16 +02:00 committed by GitHub
parent 473c8abc82
commit a07286ace9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 76 additions and 55 deletions

View file

@ -100,6 +100,7 @@ import io.element.android.libraries.matrix.api.timeline.item.event.LocationMessa
import io.element.android.libraries.matrix.api.timeline.item.event.MessageContent
import io.element.android.libraries.matrix.api.timeline.item.event.TextMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.VideoMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.VoiceMessageType
import io.element.android.libraries.matrix.ui.components.AttachmentThumbnail
import io.element.android.libraries.matrix.ui.components.AttachmentThumbnailInfo
import io.element.android.libraries.matrix.ui.components.AttachmentThumbnailType
@ -612,18 +613,14 @@ private fun attachmentThumbnailInfoForInReplyTo(inReplyTo: InReplyTo.Ready): Att
textContent = messageContent.body,
type = AttachmentThumbnailType.Location,
)
is AudioMessageType -> {
when (type.isVoiceMessage) {
true -> AttachmentThumbnailInfo(
textContent = messageContent.body,
type = AttachmentThumbnailType.Voice,
)
false -> AttachmentThumbnailInfo(
textContent = messageContent.body,
type = AttachmentThumbnailType.Audio,
)
}
}
is AudioMessageType -> AttachmentThumbnailInfo(
textContent = messageContent.body,
type = AttachmentThumbnailType.Audio,
)
is VoiceMessageType -> AttachmentThumbnailInfo(
textContent = messageContent.body,
type = AttachmentThumbnailType.Voice,
)
else -> null
}
}

View file

@ -45,6 +45,7 @@ import io.element.android.libraries.matrix.api.timeline.item.event.OtherMessageT
import io.element.android.libraries.matrix.api.timeline.item.event.TextMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.UnknownMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.VideoMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.VoiceMessageType
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
import java.time.Duration
@ -110,16 +111,8 @@ class TimelineItemContentMessageFactory @Inject constructor(
fileExtension = fileExtensionExtractor.extractFromName(messageType.body)
)
}
is AudioMessageType -> when {
featureFlagService.isFeatureEnabled(FeatureFlags.VoiceMessages) && messageType.isVoiceMessage -> TimelineItemVoiceContent(
eventId = eventId,
body = messageType.body,
mediaSource = messageType.source,
duration = messageType.info?.duration ?: Duration.ZERO,
mimeType = messageType.info?.mimetype ?: MimeTypes.OctetStream,
waveform = messageType.details?.waveform?.toImmutableList() ?: persistentListOf(),
)
else -> TimelineItemAudioContent(
is AudioMessageType -> {
TimelineItemAudioContent(
body = messageType.body,
mediaSource = messageType.source,
duration = messageType.info?.duration ?: Duration.ZERO,
@ -128,6 +121,30 @@ class TimelineItemContentMessageFactory @Inject constructor(
fileExtension = fileExtensionExtractor.extractFromName(messageType.body),
)
}
is VoiceMessageType -> {
when (featureFlagService.isFeatureEnabled(FeatureFlags.VoiceMessages)) {
true -> {
TimelineItemVoiceContent(
eventId = eventId,
body = messageType.body,
mediaSource = messageType.source,
duration = messageType.info?.duration ?: Duration.ZERO,
mimeType = messageType.info?.mimetype ?: MimeTypes.OctetStream,
waveform = messageType.details?.waveform?.toImmutableList() ?: persistentListOf(),
)
}
false -> {
TimelineItemAudioContent(
body = messageType.body,
mediaSource = messageType.source,
duration = messageType.info?.duration ?: Duration.ZERO,
mimeType = messageType.info?.mimetype ?: MimeTypes.OctetStream,
formattedFileSize = fileSizeFormatter.format(messageType.info?.size ?: 0),
fileExtension = fileExtensionExtractor.extractFromName(messageType.body),
)
}
}
}
is FileMessageType -> {
val fileExtension = fileExtensionExtractor.extractFromName(messageType.body)
TimelineItemFileContent(

View file

@ -50,6 +50,7 @@ import io.element.android.libraries.matrix.api.timeline.item.event.UnableToDecry
import io.element.android.libraries.matrix.api.timeline.item.event.UnknownContent
import io.element.android.libraries.matrix.api.timeline.item.event.UnknownMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.VideoMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.VoiceMessageType
import io.element.android.libraries.ui.strings.CommonStrings
import io.element.android.services.toolbox.api.strings.StringProvider
import javax.inject.Inject
@ -128,11 +129,10 @@ class DefaultRoomLastMessageFormatter @Inject constructor(
sp.getString(CommonStrings.common_file)
}
is AudioMessageType -> {
if (messageType.isVoiceMessage) {
sp.getString(CommonStrings.common_voice_message)
} else {
sp.getString(CommonStrings.common_audio)
}
sp.getString(CommonStrings.common_audio)
}
is VoiceMessageType -> {
sp.getString(CommonStrings.common_voice_message)
}
is OtherMessageType -> {
messageType.body

View file

@ -47,6 +47,7 @@ import io.element.android.libraries.matrix.api.timeline.item.event.UnableToDecry
import io.element.android.libraries.matrix.api.timeline.item.event.UnknownContent
import io.element.android.libraries.matrix.api.timeline.item.event.UnknownMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.VideoMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.VoiceMessageType
import io.element.android.libraries.matrix.test.A_USER_ID
import io.element.android.libraries.matrix.test.FakeMatrixClient
import io.element.android.libraries.matrix.test.room.aPollContent
@ -162,8 +163,8 @@ class DefaultRoomLastMessageFormatterTest {
val sharedContentMessagesTypes = arrayOf(
TextMessageType(body, null),
VideoMessageType(body, MediaSource("url"), null),
AudioMessageType(body, MediaSource("url"), null, null, false),
AudioMessageType(body, MediaSource("url"), null, null, true),
AudioMessageType(body, MediaSource("url"), null),
VoiceMessageType(body, MediaSource("url"), null, null),
ImageMessageType(body, MediaSource("url"), null),
FileMessageType(body, MediaSource("url"), null),
LocationMessageType(body, "geo:1,2", null),
@ -199,12 +200,8 @@ class DefaultRoomLastMessageFormatterTest {
for ((type, result) in resultsInDm) {
val expectedResult = when (type) {
is VideoMessageType -> "Video"
is AudioMessageType -> {
when (type.isVoiceMessage) {
true -> "Voice message"
false -> "Audio"
}
}
is AudioMessageType -> "Audio"
is VoiceMessageType -> "Voice message"
is ImageMessageType -> "Image"
is FileMessageType -> "File"
is LocationMessageType -> "Shared location"
@ -222,12 +219,8 @@ class DefaultRoomLastMessageFormatterTest {
val string = result.toString()
val expectedResult = when (type) {
is VideoMessageType -> "$senderName: Video"
is AudioMessageType -> {
when (type.isVoiceMessage) {
true -> "$senderName: Voice message"
false -> "$senderName: Audio"
}
}
is AudioMessageType -> "$senderName: Audio"
is VoiceMessageType -> "$senderName: Voice message"
is ImageMessageType -> "$senderName: Image"
is FileMessageType -> "$senderName: File"
is LocationMessageType -> "$senderName: Shared location"
@ -239,12 +232,8 @@ class DefaultRoomLastMessageFormatterTest {
}
val shouldCreateAnnotatedString = when (type) {
is VideoMessageType -> true
is AudioMessageType -> {
when (type.isVoiceMessage) {
true -> true
false -> true
}
}
is AudioMessageType -> true
is VoiceMessageType -> true
is ImageMessageType -> true
is FileMessageType -> true
is LocationMessageType -> false

View file

@ -48,8 +48,13 @@ data class AudioMessageType(
val body: String,
val source: MediaSource,
val info: AudioInfo?,
) : MessageType
data class VoiceMessageType(
val body: String,
val source: MediaSource,
val info: AudioInfo?,
val details: AudioDetails?,
val isVoiceMessage: Boolean,
) : MessageType
data class VideoMessageType(

View file

@ -32,6 +32,7 @@ import io.element.android.libraries.matrix.api.timeline.item.event.OtherMessageT
import io.element.android.libraries.matrix.api.timeline.item.event.TextMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.UnknownMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.VideoMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.VoiceMessageType
import io.element.android.libraries.matrix.impl.media.map
import org.matrix.rustcomponents.sdk.Message
import org.matrix.rustcomponents.sdk.MessageType
@ -77,13 +78,23 @@ class EventMessageMapper {
fun mapMessageType(type: RustMessageType?) = when (type) {
is RustMessageType.Audio -> {
AudioMessageType(
body = type.content.body,
source = type.content.source.map(),
info = type.content.info?.map(),
details = type.content.audio?.map(),
isVoiceMessage = type.content.voice != null,
)
when (type.content.voice) {
null -> {
AudioMessageType(
body = type.content.body,
source = type.content.source.map(),
info = type.content.info?.map(),
)
}
else -> {
VoiceMessageType(
body = type.content.body,
source = type.content.source.map(),
info = type.content.info?.map(),
details = type.content.audio?.map(),
)
}
}
}
is RustMessageType.File -> {
FileMessageType(type.content.body, type.content.source.map(), type.content.info?.map())

View file

@ -36,6 +36,7 @@ import io.element.android.libraries.matrix.api.timeline.item.event.OtherMessageT
import io.element.android.libraries.matrix.api.timeline.item.event.TextMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.UnknownMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.VideoMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.VoiceMessageType
import io.element.android.libraries.push.impl.R
import io.element.android.libraries.push.impl.notifications.model.FallbackNotifiableEvent
import io.element.android.libraries.push.impl.notifications.model.InviteNotifiableEvent
@ -210,6 +211,7 @@ class NotifiableEventResolver @Inject constructor(
): String {
return when (val messageType = content.messageType) {
is AudioMessageType -> messageType.body
is VoiceMessageType -> messageType.body
is EmoteMessageType -> "* $senderDisplayName ${messageType.body}"
is FileMessageType -> messageType.body
is ImageMessageType -> messageType.body