Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/7363.wip
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[Voice Broadcast] Record and send not aggregated voice messages to the room
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import org.matrix.android.sdk.api.session.events.model.Content
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.room.model.message.MessageType
import org.matrix.android.sdk.api.session.room.model.message.PollType
import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
import org.matrix.android.sdk.api.util.Cancelable

Expand Down Expand Up @@ -71,7 +72,7 @@ interface SendService {
text: String,
formattedText: String? = null,
autoMarkdown: Boolean,
rootThreadEventId: String? = null
rootThreadEventId: String? = null,
): Cancelable

/**
Expand All @@ -81,13 +82,15 @@ interface SendService {
* @param roomIds set of roomIds to where the media will be sent. The current roomId will be add to this set if not present.
* It can be useful to send media to multiple room. It's safe to include the current roomId in this set
* @param rootThreadEventId when this param is not null, the Media will be sent in this specific thread
* @param relatesTo add a relation content to the media event
* @return a [Cancelable]
*/
fun sendMedia(
attachment: ContentAttachmentData,
compressBeforeSending: Boolean,
roomIds: Set<String>,
rootThreadEventId: String? = null
rootThreadEventId: String? = null,
relatesTo: RelationDefaultContent? = null,
): Cancelable

/**
Expand All @@ -103,7 +106,7 @@ interface SendService {
attachments: List<ContentAttachmentData>,
compressBeforeSending: Boolean,
roomIds: Set<String>,
rootThreadEventId: String? = null
rootThreadEventId: String? = null,
): Cancelable

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageVideoContent
import org.matrix.android.sdk.api.session.room.model.message.MessageWithAttachmentContent
import org.matrix.android.sdk.api.session.room.model.message.PollType
import org.matrix.android.sdk.api.session.room.model.message.getFileUrl
import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent
import org.matrix.android.sdk.api.session.room.send.SendService
import org.matrix.android.sdk.api.session.room.send.SendState
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
Expand Down Expand Up @@ -280,7 +281,8 @@ internal class DefaultSendService @AssistedInject constructor(
attachment: ContentAttachmentData,
compressBeforeSending: Boolean,
roomIds: Set<String>,
rootThreadEventId: String?
rootThreadEventId: String?,
relatesTo: RelationDefaultContent?,
): Cancelable {
// Ensure that the event will not be send in a thread if we are a different flow.
// Like sending files to multiple rooms
Expand All @@ -295,7 +297,8 @@ internal class DefaultSendService @AssistedInject constructor(
localEchoEventFactory.createMediaEvent(
roomId = it,
attachment = attachment,
rootThreadEventId = rootThreadId
rootThreadEventId = rootThreadId,
relatesTo,
).also { event ->
createLocalEcho(event)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ internal class LocalEchoEventFactory @Inject constructor(
newBodyFormattedText: CharSequence?,
newBodyAutoMarkdown: Boolean,
msgType: String,
compatibilityText: String
compatibilityText: String,
): Event {
val content = if (newBodyFormattedText != null) {
TextContent(newBodyText.toString(), newBodyFormattedText.toString()).toMessageTextContent(msgType)
Expand All @@ -148,7 +148,7 @@ internal class LocalEchoEventFactory @Inject constructor(
private fun createPollContent(
question: String,
options: List<String>,
pollType: PollType
pollType: PollType,
): MessagePollContent {
return MessagePollContent(
unstablePollCreationInfo = PollCreationInfo(
Expand All @@ -166,7 +166,7 @@ internal class LocalEchoEventFactory @Inject constructor(
pollType: PollType,
targetEventId: String,
question: String,
options: List<String>
options: List<String>,
): Event {
val newContent = MessagePollContent(
relatesTo = RelationDefaultContent(RelationType.REPLACE, targetEventId),
Expand All @@ -186,7 +186,7 @@ internal class LocalEchoEventFactory @Inject constructor(
fun createPollReplyEvent(
roomId: String,
pollEventId: String,
answerId: String
answerId: String,
): Event {
val content = MessagePollResponseContent(
body = answerId,
Expand All @@ -212,7 +212,7 @@ internal class LocalEchoEventFactory @Inject constructor(
roomId: String,
pollType: PollType,
question: String,
options: List<String>
options: List<String>,
): Event {
val content = createPollContent(question, options, pollType)
val localId = LocalEcho.createLocalEchoId()
Expand All @@ -229,7 +229,7 @@ internal class LocalEchoEventFactory @Inject constructor(

fun createEndPollEvent(
roomId: String,
eventId: String
eventId: String,
): Event {
val content = MessageEndPollContent(
relatesTo = RelationDefaultContent(
Expand All @@ -254,7 +254,7 @@ internal class LocalEchoEventFactory @Inject constructor(
latitude: Double,
longitude: Double,
uncertainty: Double?,
isUserLocation: Boolean
isUserLocation: Boolean,
): Event {
val geoUri = buildGeoUri(latitude, longitude, uncertainty)
val assetType = if (isUserLocation) LocationAssetType.SELF else LocationAssetType.PIN
Expand All @@ -274,7 +274,7 @@ internal class LocalEchoEventFactory @Inject constructor(
roomId: String,
latitude: Double,
longitude: Double,
uncertainty: Double?
uncertainty: Double?,
): Event {
val geoUri = buildGeoUri(latitude, longitude, uncertainty)
val content = MessageBeaconLocationDataContent(
Expand Down Expand Up @@ -305,7 +305,7 @@ internal class LocalEchoEventFactory @Inject constructor(
newBodyText: String,
autoMarkdown: Boolean,
msgType: String,
compatibilityText: String
compatibilityText: String,
): Event {
val permalink = permalinkFactory.createPermalink(roomId, originalEvent.root.eventId ?: "", false)
val userLink = originalEvent.root.senderId?.let { permalinkFactory.createPermalink(it, false) } ?: ""
Expand Down Expand Up @@ -347,14 +347,21 @@ internal class LocalEchoEventFactory @Inject constructor(
fun createMediaEvent(
roomId: String,
attachment: ContentAttachmentData,
rootThreadEventId: String?
rootThreadEventId: String?,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not blocking but I am wondering if we should remove the rootThreadEventId param and build the relatesTo param in case of threads at an upper level?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I started considering this point but I am not sure it would make sense to build the relatesTo related to a thread in an upper level, IMO it makes sense to do it in the factory 😕

relatesTo: RelationDefaultContent?,
): Event {
return when (attachment.type) {
ContentAttachmentData.Type.IMAGE -> createImageEvent(roomId, attachment, rootThreadEventId)
ContentAttachmentData.Type.VIDEO -> createVideoEvent(roomId, attachment, rootThreadEventId)
ContentAttachmentData.Type.AUDIO -> createAudioEvent(roomId, attachment, isVoiceMessage = false, rootThreadEventId = rootThreadEventId)
ContentAttachmentData.Type.VOICE_MESSAGE -> createAudioEvent(roomId, attachment, isVoiceMessage = true, rootThreadEventId = rootThreadEventId)
ContentAttachmentData.Type.FILE -> createFileEvent(roomId, attachment, rootThreadEventId)
ContentAttachmentData.Type.IMAGE -> createImageEvent(roomId, attachment, rootThreadEventId, relatesTo)
ContentAttachmentData.Type.VIDEO -> createVideoEvent(roomId, attachment, rootThreadEventId, relatesTo)
ContentAttachmentData.Type.AUDIO -> createAudioEvent(roomId, attachment, isVoiceMessage = false, rootThreadEventId = rootThreadEventId, relatesTo)
ContentAttachmentData.Type.VOICE_MESSAGE -> createAudioEvent(
roomId,
attachment,
isVoiceMessage = true,
rootThreadEventId = rootThreadEventId,
relatesTo,
)
ContentAttachmentData.Type.FILE -> createFileEvent(roomId, attachment, rootThreadEventId, relatesTo)
}
}

Expand All @@ -378,7 +385,12 @@ internal class LocalEchoEventFactory @Inject constructor(
)
}

private fun createImageEvent(roomId: String, attachment: ContentAttachmentData, rootThreadEventId: String?): Event {
private fun createImageEvent(
roomId: String,
attachment: ContentAttachmentData,
rootThreadEventId: String?,
relatesTo: RelationDefaultContent?,
): Event {
var width = attachment.width
var height = attachment.height

Expand All @@ -403,19 +415,17 @@ internal class LocalEchoEventFactory @Inject constructor(
size = attachment.size
),
url = attachment.queryUri.toString(),
relatesTo = rootThreadEventId?.let {
RelationDefaultContent(
type = RelationType.THREAD,
eventId = it,
isFallingBack = true,
inReplyTo = ReplyToContent(eventId = localEchoRepository.getLatestThreadEvent(it))
)
}
relatesTo = relatesTo ?: rootThreadEventId?.let { generateThreadRelationContent(it) }
)
return createMessageEvent(roomId, content)
}

private fun createVideoEvent(roomId: String, attachment: ContentAttachmentData, rootThreadEventId: String?): Event {
private fun createVideoEvent(
roomId: String,
attachment: ContentAttachmentData,
rootThreadEventId: String?,
relatesTo: RelationDefaultContent?,
): Event {
val mediaDataRetriever = MediaMetadataRetriever()
mediaDataRetriever.setDataSource(context, attachment.queryUri)

Expand Down Expand Up @@ -447,14 +457,7 @@ internal class LocalEchoEventFactory @Inject constructor(
thumbnailInfo = thumbnailInfo
),
url = attachment.queryUri.toString(),
relatesTo = rootThreadEventId?.let {
RelationDefaultContent(
type = RelationType.THREAD,
eventId = it,
isFallingBack = true,
inReplyTo = ReplyToContent(eventId = localEchoRepository.getLatestThreadEvent(it))
)
}
relatesTo = relatesTo ?: rootThreadEventId?.let { generateThreadRelationContent(it) }
)
return createMessageEvent(roomId, content)
}
Expand All @@ -463,7 +466,8 @@ internal class LocalEchoEventFactory @Inject constructor(
roomId: String,
attachment: ContentAttachmentData,
isVoiceMessage: Boolean,
rootThreadEventId: String?
rootThreadEventId: String?,
relatesTo: RelationDefaultContent?,
): Event {
val content = MessageAudioContent(
msgType = MessageType.MSGTYPE_AUDIO,
Expand All @@ -479,19 +483,17 @@ internal class LocalEchoEventFactory @Inject constructor(
waveform = waveformSanitizer.sanitize(attachment.waveform)
),
voiceMessageIndicator = if (!isVoiceMessage) null else emptyMap(),
relatesTo = rootThreadEventId?.let {
RelationDefaultContent(
type = RelationType.THREAD,
eventId = it,
isFallingBack = true,
inReplyTo = ReplyToContent(eventId = localEchoRepository.getLatestThreadEvent(it))
)
}
relatesTo = relatesTo ?: rootThreadEventId?.let { generateThreadRelationContent(it) }
)
return createMessageEvent(roomId, content)
}

private fun createFileEvent(roomId: String, attachment: ContentAttachmentData, rootThreadEventId: String?): Event {
private fun createFileEvent(
roomId: String,
attachment: ContentAttachmentData,
rootThreadEventId: String?,
relatesTo: RelationDefaultContent?,
): Event {
val content = MessageFileContent(
msgType = MessageType.MSGTYPE_FILE,
body = attachment.name ?: "file",
Expand All @@ -500,14 +502,7 @@ internal class LocalEchoEventFactory @Inject constructor(
size = attachment.size
),
url = attachment.queryUri.toString(),
relatesTo = rootThreadEventId?.let {
RelationDefaultContent(
type = RelationType.THREAD,
eventId = it,
isFallingBack = true,
inReplyTo = ReplyToContent(eventId = localEchoRepository.getLatestThreadEvent(it))
)
}
relatesTo = relatesTo ?: rootThreadEventId?.let { generateThreadRelationContent(it) }
)
return createMessageEvent(roomId, content)
}
Expand Down Expand Up @@ -559,7 +554,7 @@ internal class LocalEchoEventFactory @Inject constructor(
text: CharSequence,
msgType: String,
autoMarkdown: Boolean,
formattedText: String?
formattedText: String?,
): Event {
val content = formattedText?.let { TextContent(text.toString(), it) } ?: createTextContent(text, autoMarkdown)
return createEvent(
Expand Down Expand Up @@ -588,7 +583,7 @@ internal class LocalEchoEventFactory @Inject constructor(
replyTextFormatted: CharSequence?,
autoMarkdown: Boolean,
rootThreadEventId: String? = null,
showInThread: Boolean
showInThread: Boolean,
): Event? {
// Fallbacks and event representation
// TODO Add error/warning logs when any of this is null
Expand Down Expand Up @@ -629,6 +624,14 @@ internal class LocalEchoEventFactory @Inject constructor(
return createMessageEvent(roomId, content)
}

private fun generateThreadRelationContent(rootThreadEventId: String) =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for mutualizing this code!

RelationDefaultContent(
type = RelationType.THREAD,
eventId = rootThreadEventId,
isFallingBack = true,
inReplyTo = ReplyToContent(eventId = localEchoRepository.getLatestThreadEvent(rootThreadEventId)),
)

/**
* Generates the appropriate relatesTo object for a reply event.
* It can either be a regular reply or a reply within a thread
Expand Down Expand Up @@ -772,7 +775,7 @@ internal class LocalEchoEventFactory @Inject constructor(
text: String,
formattedText: String?,
autoMarkdown: Boolean,
rootThreadEventId: String?
rootThreadEventId: String?,
): Event {
val messageContent = quotedEvent.getLastMessageContent()
val textMsg = if (messageContent is MessageContentWithFormattedBody) { messageContent.formattedBody } else { messageContent?.body }
Expand Down
41 changes: 41 additions & 0 deletions vector/src/main/java/im/vector/app/core/di/VoiceModule.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* 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 im.vector.app.core.di

import android.content.Context
import android.os.Build
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import im.vector.app.features.voicebroadcast.VoiceBroadcastRecorder
import im.vector.app.features.voicebroadcast.VoiceBroadcastRecorderQ
import javax.inject.Singleton

@Module
@InstallIn(SingletonComponent::class)
object VoiceModule {
@Provides
@Singleton
fun providesVoiceBroadcastRecorder(context: Context): VoiceBroadcastRecorder? {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
VoiceBroadcastRecorderQ(context)
} else {
null
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

package im.vector.app.core.extensions

import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO
import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants
import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.toModel
Expand All @@ -39,7 +39,9 @@ fun TimelineEvent.canReact(): Boolean {
fun TimelineEvent.getVectorLastMessageContent(): MessageContent? {
// Iterate on event types which are not part of the matrix sdk, otherwise fallback to the sdk method
return when (root.getClearType()) {
STATE_ROOM_VOICE_BROADCAST_INFO -> (annotations?.editSummary?.latestContent ?: root.getClearContent()).toModel<MessageVoiceBroadcastInfoContent>()
VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO -> {
(annotations?.editSummary?.latestContent ?: root.getClearContent()).toModel<MessageVoiceBroadcastInfoContent>()
}
else -> getLastMessageContent()
}
}
Loading