feat: support sending voice messages as replies

This commit is contained in:
Gianluca Iavicoli 2026-03-25 00:53:57 +01:00
parent aa5b1f5a07
commit bf7ab31517
No known key found for this signature in database
GPG key ID: 72FDD0FCF146C74C
2 changed files with 57 additions and 12 deletions

View file

@ -34,10 +34,12 @@ import io.element.android.libraries.audio.api.AudioFocus
import io.element.android.libraries.audio.api.AudioFocusRequester
import io.element.android.libraries.di.RoomScope
import io.element.android.libraries.di.annotations.SessionCoroutineScope
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.timeline.Timeline
import io.element.android.libraries.mediaupload.api.MediaSenderFactory
import io.element.android.libraries.permissions.api.PermissionsEvent
import io.element.android.libraries.permissions.api.PermissionsPresenter
import io.element.android.libraries.textcomposer.model.MessageComposerMode
import io.element.android.libraries.textcomposer.model.VoiceMessagePlayerEvent
import io.element.android.libraries.textcomposer.model.VoiceMessageRecorderEvent
import io.element.android.libraries.textcomposer.model.VoiceMessageState
@ -151,7 +153,7 @@ class DefaultVoiceMessageComposerPresenter(
}
}
fun sendVoiceMessage() {
fun sendVoiceMessage(inReplyToEventId: EventId?, composerEvent: Composer) {
val finishedState = recorderState as? VoiceRecorderState.Finished
if (finishedState == null) {
val exception = VoiceMessageException.FileException("No file to send")
@ -164,12 +166,13 @@ class DefaultVoiceMessageComposerPresenter(
}
isSending = true
player.pause()
analyticsService.captureComposerEvent()
analyticsService.capture(composerEvent)
sessionCoroutineScope.launch {
val result = sendMessage(
file = finishedState.file,
mimeType = finishedState.mimeType,
waveform = finishedState.waveform,
inReplyToEventId = inReplyToEventId,
)
if (result.isFailure) {
showSendFailureDialog = true
@ -183,8 +186,14 @@ class DefaultVoiceMessageComposerPresenter(
when (event) {
is VoiceMessageComposerEvent.RecorderEvent -> handleVoiceMessageRecorderEvent(event.recorderEvent)
is VoiceMessageComposerEvent.PlayerEvent -> handleVoiceMessagePlayerEvent(event.playerEvent)
is VoiceMessageComposerEvent.SendVoiceMessage -> localCoroutineScope.launch {
sendVoiceMessage()
is VoiceMessageComposerEvent.SendVoiceMessage -> {
// Capture mode eagerly before any coroutine dispatch, since CloseSpecialMode
// may reset it before the coroutine runs.
val inReplyToEventId = (messageComposerContext.composerMode as? MessageComposerMode.Reply)?.eventId
val composerEvent = buildComposerEvent()
localCoroutineScope.launch {
sendVoiceMessage(inReplyToEventId, composerEvent)
}
}
VoiceMessageComposerEvent.DeleteVoiceMessage -> {
player.pause()
@ -280,11 +289,13 @@ class DefaultVoiceMessageComposerPresenter(
file: File,
mimeType: String,
waveform: List<Float>,
inReplyToEventId: EventId? = null,
): Result<Unit> {
val result = mediaSender.sendVoiceMessage(
uri = file.toUri(),
mimeType = mimeType,
waveForm = waveform,
inReplyToEventId = inReplyToEventId,
)
if (result.isFailure) {
@ -297,14 +308,12 @@ class DefaultVoiceMessageComposerPresenter(
return result
}
private fun AnalyticsService.captureComposerEvent() =
capture(
Composer(
inThread = messageComposerContext.composerMode.inThread,
isEditing = messageComposerContext.composerMode.isEditing,
isReply = messageComposerContext.composerMode.isReply,
messageType = Composer.MessageType.VoiceMessage,
)
private fun buildComposerEvent() =
Composer(
inThread = messageComposerContext.composerMode.inThread,
isEditing = messageComposerContext.composerMode.isEditing,
isReply = messageComposerContext.composerMode.isReply,
messageType = Composer.MessageType.VoiceMessage,
)
}