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 f1b98e8746
commit d05dd11485
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.MessageContent
import io.element.android.libraries.matrix.api.timeline.item.event.TextMessageType 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.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.AttachmentThumbnail
import io.element.android.libraries.matrix.ui.components.AttachmentThumbnailInfo import io.element.android.libraries.matrix.ui.components.AttachmentThumbnailInfo
import io.element.android.libraries.matrix.ui.components.AttachmentThumbnailType import io.element.android.libraries.matrix.ui.components.AttachmentThumbnailType
@ -612,18 +613,14 @@ private fun attachmentThumbnailInfoForInReplyTo(inReplyTo: InReplyTo.Ready): Att
textContent = messageContent.body, textContent = messageContent.body,
type = AttachmentThumbnailType.Location, type = AttachmentThumbnailType.Location,
) )
is AudioMessageType -> { is AudioMessageType -> AttachmentThumbnailInfo(
when (type.isVoiceMessage) { textContent = messageContent.body,
true -> AttachmentThumbnailInfo( type = AttachmentThumbnailType.Audio,
textContent = messageContent.body, )
type = AttachmentThumbnailType.Voice, is VoiceMessageType -> AttachmentThumbnailInfo(
) textContent = messageContent.body,
false -> AttachmentThumbnailInfo( type = AttachmentThumbnailType.Voice,
textContent = messageContent.body, )
type = AttachmentThumbnailType.Audio,
)
}
}
else -> null 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.TextMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.UnknownMessageType 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.VideoMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.VoiceMessageType
import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableList
import java.time.Duration import java.time.Duration
@ -110,16 +111,8 @@ class TimelineItemContentMessageFactory @Inject constructor(
fileExtension = fileExtensionExtractor.extractFromName(messageType.body) fileExtension = fileExtensionExtractor.extractFromName(messageType.body)
) )
} }
is AudioMessageType -> when { is AudioMessageType -> {
featureFlagService.isFeatureEnabled(FeatureFlags.VoiceMessages) && messageType.isVoiceMessage -> TimelineItemVoiceContent( TimelineItemAudioContent(
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(
body = messageType.body, body = messageType.body,
mediaSource = messageType.source, mediaSource = messageType.source,
duration = messageType.info?.duration ?: Duration.ZERO, duration = messageType.info?.duration ?: Duration.ZERO,
@ -128,6 +121,30 @@ class TimelineItemContentMessageFactory @Inject constructor(
fileExtension = fileExtensionExtractor.extractFromName(messageType.body), 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 -> { is FileMessageType -> {
val fileExtension = fileExtensionExtractor.extractFromName(messageType.body) val fileExtension = fileExtensionExtractor.extractFromName(messageType.body)
TimelineItemFileContent( 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.UnknownContent
import io.element.android.libraries.matrix.api.timeline.item.event.UnknownMessageType 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.VideoMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.VoiceMessageType
import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.libraries.ui.strings.CommonStrings
import io.element.android.services.toolbox.api.strings.StringProvider import io.element.android.services.toolbox.api.strings.StringProvider
import javax.inject.Inject import javax.inject.Inject
@ -128,11 +129,10 @@ class DefaultRoomLastMessageFormatter @Inject constructor(
sp.getString(CommonStrings.common_file) sp.getString(CommonStrings.common_file)
} }
is AudioMessageType -> { is AudioMessageType -> {
if (messageType.isVoiceMessage) { sp.getString(CommonStrings.common_audio)
sp.getString(CommonStrings.common_voice_message) }
} else { is VoiceMessageType -> {
sp.getString(CommonStrings.common_audio) sp.getString(CommonStrings.common_voice_message)
}
} }
is OtherMessageType -> { is OtherMessageType -> {
messageType.body 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.UnknownContent
import io.element.android.libraries.matrix.api.timeline.item.event.UnknownMessageType 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.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.A_USER_ID
import io.element.android.libraries.matrix.test.FakeMatrixClient import io.element.android.libraries.matrix.test.FakeMatrixClient
import io.element.android.libraries.matrix.test.room.aPollContent import io.element.android.libraries.matrix.test.room.aPollContent
@ -162,8 +163,8 @@ class DefaultRoomLastMessageFormatterTest {
val sharedContentMessagesTypes = arrayOf( val sharedContentMessagesTypes = arrayOf(
TextMessageType(body, null), TextMessageType(body, null),
VideoMessageType(body, MediaSource("url"), null), VideoMessageType(body, MediaSource("url"), null),
AudioMessageType(body, MediaSource("url"), null, null, false), AudioMessageType(body, MediaSource("url"), null),
AudioMessageType(body, MediaSource("url"), null, null, true), VoiceMessageType(body, MediaSource("url"), null, null),
ImageMessageType(body, MediaSource("url"), null), ImageMessageType(body, MediaSource("url"), null),
FileMessageType(body, MediaSource("url"), null), FileMessageType(body, MediaSource("url"), null),
LocationMessageType(body, "geo:1,2", null), LocationMessageType(body, "geo:1,2", null),
@ -199,12 +200,8 @@ class DefaultRoomLastMessageFormatterTest {
for ((type, result) in resultsInDm) { for ((type, result) in resultsInDm) {
val expectedResult = when (type) { val expectedResult = when (type) {
is VideoMessageType -> "Video" is VideoMessageType -> "Video"
is AudioMessageType -> { is AudioMessageType -> "Audio"
when (type.isVoiceMessage) { is VoiceMessageType -> "Voice message"
true -> "Voice message"
false -> "Audio"
}
}
is ImageMessageType -> "Image" is ImageMessageType -> "Image"
is FileMessageType -> "File" is FileMessageType -> "File"
is LocationMessageType -> "Shared location" is LocationMessageType -> "Shared location"
@ -222,12 +219,8 @@ class DefaultRoomLastMessageFormatterTest {
val string = result.toString() val string = result.toString()
val expectedResult = when (type) { val expectedResult = when (type) {
is VideoMessageType -> "$senderName: Video" is VideoMessageType -> "$senderName: Video"
is AudioMessageType -> { is AudioMessageType -> "$senderName: Audio"
when (type.isVoiceMessage) { is VoiceMessageType -> "$senderName: Voice message"
true -> "$senderName: Voice message"
false -> "$senderName: Audio"
}
}
is ImageMessageType -> "$senderName: Image" is ImageMessageType -> "$senderName: Image"
is FileMessageType -> "$senderName: File" is FileMessageType -> "$senderName: File"
is LocationMessageType -> "$senderName: Shared location" is LocationMessageType -> "$senderName: Shared location"
@ -239,12 +232,8 @@ class DefaultRoomLastMessageFormatterTest {
} }
val shouldCreateAnnotatedString = when (type) { val shouldCreateAnnotatedString = when (type) {
is VideoMessageType -> true is VideoMessageType -> true
is AudioMessageType -> { is AudioMessageType -> true
when (type.isVoiceMessage) { is VoiceMessageType -> true
true -> true
false -> true
}
}
is ImageMessageType -> true is ImageMessageType -> true
is FileMessageType -> true is FileMessageType -> true
is LocationMessageType -> false is LocationMessageType -> false

View file

@ -48,8 +48,13 @@ data class AudioMessageType(
val body: String, val body: String,
val source: MediaSource, val source: MediaSource,
val info: AudioInfo?, val info: AudioInfo?,
) : MessageType
data class VoiceMessageType(
val body: String,
val source: MediaSource,
val info: AudioInfo?,
val details: AudioDetails?, val details: AudioDetails?,
val isVoiceMessage: Boolean,
) : MessageType ) : MessageType
data class VideoMessageType( 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.TextMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.UnknownMessageType 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.VideoMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.VoiceMessageType
import io.element.android.libraries.matrix.impl.media.map import io.element.android.libraries.matrix.impl.media.map
import org.matrix.rustcomponents.sdk.Message import org.matrix.rustcomponents.sdk.Message
import org.matrix.rustcomponents.sdk.MessageType import org.matrix.rustcomponents.sdk.MessageType
@ -77,13 +78,23 @@ class EventMessageMapper {
fun mapMessageType(type: RustMessageType?) = when (type) { fun mapMessageType(type: RustMessageType?) = when (type) {
is RustMessageType.Audio -> { is RustMessageType.Audio -> {
AudioMessageType( when (type.content.voice) {
body = type.content.body, null -> {
source = type.content.source.map(), AudioMessageType(
info = type.content.info?.map(), body = type.content.body,
details = type.content.audio?.map(), source = type.content.source.map(),
isVoiceMessage = type.content.voice != null, 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 -> { is RustMessageType.File -> {
FileMessageType(type.content.body, type.content.source.map(), type.content.info?.map()) 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.TextMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.UnknownMessageType 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.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.R
import io.element.android.libraries.push.impl.notifications.model.FallbackNotifiableEvent import io.element.android.libraries.push.impl.notifications.model.FallbackNotifiableEvent
import io.element.android.libraries.push.impl.notifications.model.InviteNotifiableEvent import io.element.android.libraries.push.impl.notifications.model.InviteNotifiableEvent
@ -210,6 +211,7 @@ class NotifiableEventResolver @Inject constructor(
): String { ): String {
return when (val messageType = content.messageType) { return when (val messageType = content.messageType) {
is AudioMessageType -> messageType.body is AudioMessageType -> messageType.body
is VoiceMessageType -> messageType.body
is EmoteMessageType -> "* $senderDisplayName ${messageType.body}" is EmoteMessageType -> "* $senderDisplayName ${messageType.body}"
is FileMessageType -> messageType.body is FileMessageType -> messageType.body
is ImageMessageType -> messageType.body is ImageMessageType -> messageType.body