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:
parent
f1b98e8746
commit
d05dd11485
7 changed files with 76 additions and 55 deletions
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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(
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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(
|
||||||
|
|
|
||||||
|
|
@ -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())
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue