Merge pull request #7397 from vector-im/feature/fre/voice_broadcast_additional_content

Add additional data in voice broadcast events
This commit is contained in:
Florian Renaud 2022-10-19 07:51:01 +02:00 committed by GitHub
commit 13972661e0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 176 additions and 69 deletions

1
changelog.d/7397.wip Normal file
View file

@ -0,0 +1 @@
[Voice Broadcast] Add additional data in events

View file

@ -45,18 +45,30 @@ interface SendService {
* @param text the text message to send
* @param msgType the message type: MessageType.MSGTYPE_TEXT (default) or MessageType.MSGTYPE_EMOTE
* @param autoMarkdown If true, the SDK will generate a formatted HTML message from the body text if markdown syntax is present
* @param additionalContent additional content to put in the event content
* @return a [Cancelable]
*/
fun sendTextMessage(text: CharSequence, msgType: String = MessageType.MSGTYPE_TEXT, autoMarkdown: Boolean = false): Cancelable
fun sendTextMessage(
text: CharSequence,
msgType: String = MessageType.MSGTYPE_TEXT,
autoMarkdown: Boolean = false,
additionalContent: Content? = null,
): Cancelable
/**
* Method to send a text message with a formatted body.
* @param text the text message to send
* @param formattedText The formatted body using MessageType#FORMAT_MATRIX_HTML
* @param msgType the message type: MessageType.MSGTYPE_TEXT (default) or MessageType.MSGTYPE_EMOTE
* @param additionalContent additional content to put in the event content
* @return a [Cancelable]
*/
fun sendFormattedTextMessage(text: String, formattedText: String, msgType: String = MessageType.MSGTYPE_TEXT): Cancelable
fun sendFormattedTextMessage(
text: String,
formattedText: String,
msgType: String = MessageType.MSGTYPE_TEXT,
additionalContent: Content? = null,
): Cancelable
/**
* Method to quote an events content.
@ -65,6 +77,7 @@ interface SendService {
* @param formattedText the formatted text message to send
* @param autoMarkdown If true, the SDK will generate a formatted HTML message from the body text if markdown syntax is present
* @param rootThreadEventId when this param is not null, the message will be sent in this specific thread
* @param additionalContent additional content to put in the event content
* @return a [Cancelable]
*/
fun sendQuotedTextMessage(
@ -73,6 +86,7 @@ interface SendService {
formattedText: String? = null,
autoMarkdown: Boolean,
rootThreadEventId: String? = null,
additionalContent: Content? = null,
): Cancelable
/**
@ -83,6 +97,7 @@ interface SendService {
* 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
* @param additionalContent additional content to put in the event content
* @return a [Cancelable]
*/
fun sendMedia(
@ -91,6 +106,7 @@ interface SendService {
roomIds: Set<String>,
rootThreadEventId: String? = null,
relatesTo: RelationDefaultContent? = null,
additionalContent: Content? = null,
): Cancelable
/**
@ -100,6 +116,7 @@ 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, all the Media will be sent in this specific thread
* @param additionalContent additional content to put in the event content
* @return a [Cancelable]
*/
fun sendMedias(
@ -107,6 +124,7 @@ interface SendService {
compressBeforeSending: Boolean,
roomIds: Set<String>,
rootThreadEventId: String? = null,
additionalContent: Content? = null,
): Cancelable
/**
@ -114,31 +132,35 @@ interface SendService {
* @param pollType indicates open or closed polls
* @param question the question
* @param options list of options
* @param additionalContent additional content to put in the event content
* @return a [Cancelable]
*/
fun sendPoll(pollType: PollType, question: String, options: List<String>): Cancelable
fun sendPoll(pollType: PollType, question: String, options: List<String>, additionalContent: Content? = null): Cancelable
/**
* Method to send a poll response.
* @param pollEventId the poll currently replied to
* @param answerId The id of the answer
* @param additionalContent additional content to put in the event content
* @return a [Cancelable]
*/
fun voteToPoll(pollEventId: String, answerId: String): Cancelable
fun voteToPoll(pollEventId: String, answerId: String, additionalContent: Content? = null): Cancelable
/**
* End a poll in the room.
* @param pollEventId event id of the poll
* @param additionalContent additional content to put in the event content
* @return a [Cancelable]
*/
fun endPoll(pollEventId: String): Cancelable
fun endPoll(pollEventId: String, additionalContent: Content? = null): Cancelable
/**
* Redact (delete) the given event.
* @param event The event to redact
* @param reason Optional reason string
* @param additionalContent additional content to put in the event content
*/
fun redactEvent(event: Event, reason: String?): Cancelable
fun redactEvent(event: Event, reason: String?, additionalContent: Content? = null): Cancelable
/**
* Schedule this message to be resent.

View file

@ -26,6 +26,7 @@ import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.listeners.ProgressListener
import org.matrix.android.sdk.api.session.content.ContentAttachmentData
import org.matrix.android.sdk.api.session.crypto.model.EncryptedFileInfo
import org.matrix.android.sdk.api.session.events.model.Content
import org.matrix.android.sdk.api.session.events.model.toContent
import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.room.model.message.MessageAudioContent
@ -407,7 +408,10 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
newAttachmentAttributes: NewAttachmentAttributes
) {
localEchoRepository.updateEcho(eventId) { _, event ->
val messageContent: MessageContent? = event.asDomain().content.toModel()
val content: Content? = event.asDomain().content
val messageContent: MessageContent? = content.toModel()
// Retrieve potential additional content from the original event
val additionalContent = content.orEmpty() - messageContent?.toContent().orEmpty().keys
val updatedContent = when (messageContent) {
is MessageImageContent -> messageContent.update(url, encryptedFileInfo, newAttachmentAttributes)
is MessageVideoContent -> messageContent.update(url, encryptedFileInfo, thumbnailUrl, thumbnailEncryptedFileInfo, newAttachmentAttributes)
@ -415,7 +419,7 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
is MessageAudioContent -> messageContent.update(url, encryptedFileInfo, newAttachmentAttributes.newFileSize)
else -> messageContent
}
event.content = ContentMapper.map(updatedContent.toContent())
event.content = ContentMapper.map(updatedContent.toContent().plus(additionalContent))
}
}

View file

@ -27,6 +27,7 @@ import dagger.assisted.AssistedInject
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.MatrixUrls.isMxcUrl
import org.matrix.android.sdk.api.session.content.ContentAttachmentData
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.events.model.isAttachmentMessage
import org.matrix.android.sdk.api.session.events.model.isTextMessage
@ -88,14 +89,14 @@ internal class DefaultSendService @AssistedInject constructor(
.let { sendEvent(it) }
}
override fun sendTextMessage(text: CharSequence, msgType: String, autoMarkdown: Boolean): Cancelable {
return localEchoEventFactory.createTextEvent(roomId, msgType, text, autoMarkdown)
override fun sendTextMessage(text: CharSequence, msgType: String, autoMarkdown: Boolean, additionalContent: Content?): Cancelable {
return localEchoEventFactory.createTextEvent(roomId, msgType, text, autoMarkdown, additionalContent)
.also { createLocalEcho(it) }
.let { sendEvent(it) }
}
override fun sendFormattedTextMessage(text: String, formattedText: String, msgType: String): Cancelable {
return localEchoEventFactory.createFormattedTextEvent(roomId, TextContent(text, formattedText), msgType)
override fun sendFormattedTextMessage(text: String, formattedText: String, msgType: String, additionalContent: Content?): Cancelable {
return localEchoEventFactory.createFormattedTextEvent(roomId, TextContent(text, formattedText), msgType, additionalContent)
.also { createLocalEcho(it) }
.let { sendEvent(it) }
}
@ -105,7 +106,8 @@ internal class DefaultSendService @AssistedInject constructor(
text: String,
formattedText: String?,
autoMarkdown: Boolean,
rootThreadEventId: String?
rootThreadEventId: String?,
additionalContent: Content?,
): Cancelable {
return localEchoEventFactory.createQuotedTextEvent(
roomId = roomId,
@ -113,33 +115,34 @@ internal class DefaultSendService @AssistedInject constructor(
text = text,
formattedText = formattedText,
autoMarkdown = autoMarkdown,
rootThreadEventId = rootThreadEventId
rootThreadEventId = rootThreadEventId,
additionalContent = additionalContent,
)
.also { createLocalEcho(it) }
.let { sendEvent(it) }
}
override fun sendPoll(pollType: PollType, question: String, options: List<String>): Cancelable {
return localEchoEventFactory.createPollEvent(roomId, pollType, question, options)
override fun sendPoll(pollType: PollType, question: String, options: List<String>, additionalContent: Content?): Cancelable {
return localEchoEventFactory.createPollEvent(roomId, pollType, question, options, additionalContent)
.also { createLocalEcho(it) }
.let { sendEvent(it) }
}
override fun voteToPoll(pollEventId: String, answerId: String): Cancelable {
return localEchoEventFactory.createPollReplyEvent(roomId, pollEventId, answerId)
override fun voteToPoll(pollEventId: String, answerId: String, additionalContent: Content?): Cancelable {
return localEchoEventFactory.createPollReplyEvent(roomId, pollEventId, answerId, additionalContent)
.also { createLocalEcho(it) }
.let { sendEvent(it) }
}
override fun endPoll(pollEventId: String): Cancelable {
return localEchoEventFactory.createEndPollEvent(roomId, pollEventId)
override fun endPoll(pollEventId: String, additionalContent: Content?): Cancelable {
return localEchoEventFactory.createEndPollEvent(roomId, pollEventId, additionalContent)
.also { createLocalEcho(it) }
.let { sendEvent(it) }
}
override fun redactEvent(event: Event, reason: String?): Cancelable {
override fun redactEvent(event: Event, reason: String?, additionalContent: Content?): Cancelable {
// TODO manage media/attachements?
val redactionEcho = localEchoEventFactory.createRedactEvent(roomId, event.eventId!!, reason)
val redactionEcho = localEchoEventFactory.createRedactEvent(roomId, event.eventId!!, reason, additionalContent)
.also { createLocalEcho(it) }
return eventSenderProcessor.postRedaction(redactionEcho, reason)
}
@ -265,7 +268,8 @@ internal class DefaultSendService @AssistedInject constructor(
attachments: List<ContentAttachmentData>,
compressBeforeSending: Boolean,
roomIds: Set<String>,
rootThreadEventId: String?
rootThreadEventId: String?,
additionalContent: Content?,
): Cancelable {
return attachments.mapTo(CancelableBag()) {
sendMedia(
@ -283,6 +287,7 @@ internal class DefaultSendService @AssistedInject constructor(
roomIds: Set<String>,
rootThreadEventId: String?,
relatesTo: RelationDefaultContent?,
additionalContent: Content?,
): Cancelable {
// Ensure that the event will not be send in a thread if we are a different flow.
// Like sending files to multiple rooms
@ -299,6 +304,7 @@ internal class DefaultSendService @AssistedInject constructor(
attachment = attachment,
rootThreadEventId = rootThreadId,
relatesTo,
additionalContent,
).also { event ->
createLocalEcho(event)
}

View file

@ -95,12 +95,12 @@ internal class LocalEchoEventFactory @Inject constructor(
private val permalinkFactory: PermalinkFactory,
private val clock: Clock,
) {
fun createTextEvent(roomId: String, msgType: String, text: CharSequence, autoMarkdown: Boolean): Event {
fun createTextEvent(roomId: String, msgType: String, text: CharSequence, autoMarkdown: Boolean, additionalContent: Content? = null): Event {
if (msgType == MessageType.MSGTYPE_TEXT || msgType == MessageType.MSGTYPE_EMOTE) {
return createFormattedTextEvent(roomId, createTextContent(text, autoMarkdown), msgType)
return createFormattedTextEvent(roomId, createTextContent(text, autoMarkdown), msgType, additionalContent)
}
val content = MessageTextContent(msgType = msgType, body = text.toString())
return createMessageEvent(roomId, content)
return createMessageEvent(roomId, content, additionalContent)
}
private fun createTextContent(text: CharSequence, autoMarkdown: Boolean): TextContent {
@ -116,8 +116,8 @@ internal class LocalEchoEventFactory @Inject constructor(
return TextContent(text.toString())
}
fun createFormattedTextEvent(roomId: String, textContent: TextContent, msgType: String): Event {
return createMessageEvent(roomId, textContent.toMessageTextContent(msgType))
fun createFormattedTextEvent(roomId: String, textContent: TextContent, msgType: String, additionalContent: Content? = null): Event {
return createMessageEvent(roomId, textContent.toMessageTextContent(msgType), additionalContent)
}
fun createReplaceTextEvent(
@ -128,6 +128,7 @@ internal class LocalEchoEventFactory @Inject constructor(
newBodyAutoMarkdown: Boolean,
msgType: String,
compatibilityText: String,
additionalContent: Content? = null,
): Event {
val content = if (newBodyFormattedText != null) {
TextContent(newBodyText.toString(), newBodyFormattedText.toString()).toMessageTextContent(msgType)
@ -141,7 +142,8 @@ internal class LocalEchoEventFactory @Inject constructor(
body = compatibilityText,
relatesTo = RelationDefaultContent(RelationType.REPLACE, targetEventId),
newContent = content,
)
),
additionalContent,
)
}
@ -167,6 +169,7 @@ internal class LocalEchoEventFactory @Inject constructor(
targetEventId: String,
question: String,
options: List<String>,
additionalContent: Content? = null,
): Event {
val newContent = MessagePollContent(
relatesTo = RelationDefaultContent(RelationType.REPLACE, targetEventId),
@ -179,7 +182,7 @@ internal class LocalEchoEventFactory @Inject constructor(
senderId = userId,
eventId = localId,
type = EventType.POLL_START.first(),
content = newContent.toContent()
content = newContent.toContent().plus(additionalContent.orEmpty())
)
}
@ -187,6 +190,7 @@ internal class LocalEchoEventFactory @Inject constructor(
roomId: String,
pollEventId: String,
answerId: String,
additionalContent: Content? = null,
): Event {
val content = MessagePollResponseContent(
body = answerId,
@ -203,7 +207,7 @@ internal class LocalEchoEventFactory @Inject constructor(
senderId = userId,
eventId = localId,
type = EventType.POLL_RESPONSE.first(),
content = content.toContent(),
content = content.toContent().plus(additionalContent.orEmpty()),
unsignedData = UnsignedData(age = null, transactionId = localId)
)
}
@ -213,6 +217,7 @@ internal class LocalEchoEventFactory @Inject constructor(
pollType: PollType,
question: String,
options: List<String>,
additionalContent: Content? = null,
): Event {
val content = createPollContent(question, options, pollType)
val localId = LocalEcho.createLocalEchoId()
@ -222,7 +227,7 @@ internal class LocalEchoEventFactory @Inject constructor(
senderId = userId,
eventId = localId,
type = EventType.POLL_START.first(),
content = content.toContent(),
content = content.toContent().plus(additionalContent.orEmpty()),
unsignedData = UnsignedData(age = null, transactionId = localId)
)
}
@ -230,6 +235,7 @@ internal class LocalEchoEventFactory @Inject constructor(
fun createEndPollEvent(
roomId: String,
eventId: String,
additionalContent: Content? = null,
): Event {
val content = MessageEndPollContent(
relatesTo = RelationDefaultContent(
@ -244,7 +250,7 @@ internal class LocalEchoEventFactory @Inject constructor(
senderId = userId,
eventId = localId,
type = EventType.POLL_END.first(),
content = content.toContent(),
content = content.toContent().plus(additionalContent.orEmpty()),
unsignedData = UnsignedData(age = null, transactionId = localId)
)
}
@ -255,6 +261,7 @@ internal class LocalEchoEventFactory @Inject constructor(
longitude: Double,
uncertainty: Double?,
isUserLocation: Boolean,
additionalContent: Content? = null,
): Event {
val geoUri = buildGeoUri(latitude, longitude, uncertainty)
val assetType = if (isUserLocation) LocationAssetType.SELF else LocationAssetType.PIN
@ -266,7 +273,7 @@ internal class LocalEchoEventFactory @Inject constructor(
unstableTimestampMillis = clock.epochMillis(),
unstableText = geoUri
)
return createMessageEvent(roomId, content)
return createMessageEvent(roomId, content, additionalContent)
}
fun createLiveLocationEvent(
@ -275,6 +282,7 @@ internal class LocalEchoEventFactory @Inject constructor(
latitude: Double,
longitude: Double,
uncertainty: Double?,
additionalContent: Content? = null,
): Event {
val geoUri = buildGeoUri(latitude, longitude, uncertainty)
val content = MessageBeaconLocationDataContent(
@ -293,7 +301,7 @@ internal class LocalEchoEventFactory @Inject constructor(
senderId = userId,
eventId = localId,
type = EventType.BEACON_LOCATION_DATA.first(),
content = content.toContent(),
content = content.toContent().plus(additionalContent.orEmpty()),
unsignedData = UnsignedData(age = null, transactionId = localId)
)
}
@ -306,6 +314,7 @@ internal class LocalEchoEventFactory @Inject constructor(
autoMarkdown: Boolean,
msgType: String,
compatibilityText: String,
additionalContent: Content? = null,
): Event {
val permalink = permalinkFactory.createPermalink(roomId, originalEvent.root.eventId ?: "", false)
val userLink = originalEvent.root.senderId?.let { permalinkFactory.createPermalink(it, false) } ?: ""
@ -340,7 +349,8 @@ internal class LocalEchoEventFactory @Inject constructor(
formattedBody = replyFormatted
)
.toContent()
)
),
additionalContent,
)
}
@ -349,23 +359,32 @@ internal class LocalEchoEventFactory @Inject constructor(
attachment: ContentAttachmentData,
rootThreadEventId: String?,
relatesTo: RelationDefaultContent?,
additionalContent: Content? = null,
): Event {
return when (attachment.type) {
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.IMAGE -> createImageEvent(roomId, attachment, rootThreadEventId, relatesTo, additionalContent)
ContentAttachmentData.Type.VIDEO -> createVideoEvent(roomId, attachment, rootThreadEventId, relatesTo, additionalContent)
ContentAttachmentData.Type.AUDIO -> createAudioEvent(
roomId,
attachment,
isVoiceMessage = false,
rootThreadEventId = rootThreadEventId,
relatesTo,
additionalContent
)
ContentAttachmentData.Type.VOICE_MESSAGE -> createAudioEvent(
roomId,
attachment,
isVoiceMessage = true,
rootThreadEventId = rootThreadEventId,
relatesTo,
additionalContent,
)
ContentAttachmentData.Type.FILE -> createFileEvent(roomId, attachment, rootThreadEventId, relatesTo)
ContentAttachmentData.Type.FILE -> createFileEvent(roomId, attachment, rootThreadEventId, relatesTo, additionalContent)
}
}
fun createReactionEvent(roomId: String, targetEventId: String, reaction: String): Event {
fun createReactionEvent(roomId: String, targetEventId: String, reaction: String, additionalContent: Content? = null): Event {
val content = ReactionContent(
ReactionInfo(
RelationType.ANNOTATION,
@ -380,7 +399,7 @@ internal class LocalEchoEventFactory @Inject constructor(
senderId = userId,
eventId = localId,
type = EventType.REACTION,
content = content.toContent(),
content = content.toContent().plus(additionalContent.orEmpty()),
unsignedData = UnsignedData(age = null, transactionId = localId)
)
}
@ -390,6 +409,7 @@ internal class LocalEchoEventFactory @Inject constructor(
attachment: ContentAttachmentData,
rootThreadEventId: String?,
relatesTo: RelationDefaultContent?,
additionalContent: Content?,
): Event {
var width = attachment.width
var height = attachment.height
@ -417,7 +437,7 @@ internal class LocalEchoEventFactory @Inject constructor(
url = attachment.queryUri.toString(),
relatesTo = relatesTo ?: rootThreadEventId?.let { generateThreadRelationContent(it) }
)
return createMessageEvent(roomId, content)
return createMessageEvent(roomId, content, additionalContent)
}
private fun createVideoEvent(
@ -425,6 +445,7 @@ internal class LocalEchoEventFactory @Inject constructor(
attachment: ContentAttachmentData,
rootThreadEventId: String?,
relatesTo: RelationDefaultContent?,
additionalContent: Content?,
): Event {
val mediaDataRetriever = MediaMetadataRetriever()
mediaDataRetriever.setDataSource(context, attachment.queryUri)
@ -459,7 +480,7 @@ internal class LocalEchoEventFactory @Inject constructor(
url = attachment.queryUri.toString(),
relatesTo = relatesTo ?: rootThreadEventId?.let { generateThreadRelationContent(it) }
)
return createMessageEvent(roomId, content)
return createMessageEvent(roomId, content, additionalContent)
}
private fun createAudioEvent(
@ -468,6 +489,7 @@ internal class LocalEchoEventFactory @Inject constructor(
isVoiceMessage: Boolean,
rootThreadEventId: String?,
relatesTo: RelationDefaultContent?,
additionalContent: Content?
): Event {
val content = MessageAudioContent(
msgType = MessageType.MSGTYPE_AUDIO,
@ -485,7 +507,7 @@ internal class LocalEchoEventFactory @Inject constructor(
voiceMessageIndicator = if (!isVoiceMessage) null else emptyMap(),
relatesTo = relatesTo ?: rootThreadEventId?.let { generateThreadRelationContent(it) }
)
return createMessageEvent(roomId, content)
return createMessageEvent(roomId, content, additionalContent)
}
private fun createFileEvent(
@ -493,6 +515,7 @@ internal class LocalEchoEventFactory @Inject constructor(
attachment: ContentAttachmentData,
rootThreadEventId: String?,
relatesTo: RelationDefaultContent?,
additionalContent: Content?
): Event {
val content = MessageFileContent(
msgType = MessageType.MSGTYPE_FILE,
@ -504,15 +527,16 @@ internal class LocalEchoEventFactory @Inject constructor(
url = attachment.queryUri.toString(),
relatesTo = relatesTo ?: rootThreadEventId?.let { generateThreadRelationContent(it) }
)
return createMessageEvent(roomId, content)
return createMessageEvent(roomId, content, additionalContent)
}
private fun createMessageEvent(roomId: String, content: MessageContent? = null): Event {
return createEvent(roomId, EventType.MESSAGE, content.toContent())
private fun createMessageEvent(roomId: String, content: MessageContent, additionalContent: Content?): Event {
return createEvent(roomId, EventType.MESSAGE, content.toContent(), additionalContent)
}
fun createEvent(roomId: String, type: String, content: Content?): Event {
fun createEvent(roomId: String, type: String, content: Content?, additionalContent: Content? = null): Event {
val newContent = enhanceStickerIfNeeded(type, content) ?: content
val updatedNewContent = newContent?.plus(additionalContent.orEmpty()) ?: additionalContent
val localId = LocalEcho.createLocalEchoId()
return Event(
roomId = roomId,
@ -520,7 +544,7 @@ internal class LocalEchoEventFactory @Inject constructor(
senderId = userId,
eventId = localId,
type = type,
content = newContent,
content = updatedNewContent,
unsignedData = UnsignedData(age = null, transactionId = localId)
)
}
@ -555,6 +579,7 @@ internal class LocalEchoEventFactory @Inject constructor(
msgType: String,
autoMarkdown: Boolean,
formattedText: String?,
additionalContent: Content? = null,
): Event {
val content = formattedText?.let { TextContent(text.toString(), it) } ?: createTextContent(text, autoMarkdown)
return createEvent(
@ -564,8 +589,7 @@ internal class LocalEchoEventFactory @Inject constructor(
rootThreadEventId = rootThreadEventId,
latestThreadEventId = localEchoRepository.getLatestThreadEvent(rootThreadEventId),
msgType = msgType
)
.toContent()
).toContent().plus(additionalContent.orEmpty())
)
}
@ -584,6 +608,7 @@ internal class LocalEchoEventFactory @Inject constructor(
autoMarkdown: Boolean,
rootThreadEventId: String? = null,
showInThread: Boolean,
additionalContent: Content? = null
): Event? {
// Fallbacks and event representation
// TODO Add error/warning logs when any of this is null
@ -621,7 +646,7 @@ internal class LocalEchoEventFactory @Inject constructor(
showInThread = showInThread
)
)
return createMessageEvent(roomId, content)
return createMessageEvent(roomId, content, additionalContent)
}
private fun generateThreadRelationContent(rootThreadEventId: String) =
@ -750,7 +775,7 @@ internal class LocalEchoEventFactory @Inject constructor(
}
}
*/
fun createRedactEvent(roomId: String, eventId: String, reason: String?): Event {
fun createRedactEvent(roomId: String, eventId: String, reason: String?, additionalContent: Content? = null): Event {
val localId = LocalEcho.createLocalEchoId()
return Event(
roomId = roomId,
@ -759,7 +784,7 @@ internal class LocalEchoEventFactory @Inject constructor(
eventId = localId,
type = EventType.REDACTION,
redacts = eventId,
content = reason?.let { mapOf("reason" to it).toContent() },
content = reason?.let { mapOf("reason" to it).toContent().plus(additionalContent.orEmpty()) } ?: additionalContent,
unsignedData = UnsignedData(age = null, transactionId = localId)
)
}
@ -776,9 +801,14 @@ internal class LocalEchoEventFactory @Inject constructor(
formattedText: String?,
autoMarkdown: Boolean,
rootThreadEventId: String?,
additionalContent: Content? = null,
): Event {
val messageContent = quotedEvent.getLastMessageContent()
val textMsg = if (messageContent is MessageContentWithFormattedBody) { messageContent.formattedBody } else { messageContent?.body }
val textMsg = if (messageContent is MessageContentWithFormattedBody) {
messageContent.formattedBody
} else {
messageContent?.body
}
val quoteText = legacyRiotQuoteText(textMsg, text)
val quoteFormattedText = "<blockquote>$textMsg</blockquote>$formattedText"
@ -791,13 +821,15 @@ internal class LocalEchoEventFactory @Inject constructor(
rootThreadEventId = rootThreadEventId,
latestThreadEventId = localEchoRepository.getLatestThreadEvent(rootThreadEventId),
msgType = MessageType.MSGTYPE_TEXT
)
),
additionalContent,
)
} else {
createFormattedTextEvent(
roomId,
markdownParser.parse(quoteText, force = true, advanced = autoMarkdown).copy(formattedText = quoteFormattedText),
MessageType.MSGTYPE_TEXT
MessageType.MSGTYPE_TEXT,
additionalContent,
)
}
}

View file

@ -16,11 +16,16 @@
package im.vector.app.features.voicebroadcast
import org.matrix.android.sdk.api.session.room.model.message.MessageAudioContent
object VoiceBroadcastConstants {
/** Voice Broadcast State Event. */
const val STATE_ROOM_VOICE_BROADCAST_INFO = "io.element.voice_broadcast_info"
/** Custom key passed to the [MessageAudioContent] with Voice Broadcast information. */
const val VOICE_BROADCAST_CHUNK_KEY = "io.element.voice_broadcast_chunk"
/** Default voice broadcast chunk duration, in seconds. */
const val DEFAULT_CHUNK_LENGTH_IN_SECONDS = 120
}

View file

@ -16,14 +16,19 @@
package im.vector.app.features.voicebroadcast
import org.matrix.android.sdk.api.session.events.model.RelationType
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastChunk
import org.matrix.android.sdk.api.session.events.model.Content
import org.matrix.android.sdk.api.session.events.model.getRelationContent
import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.room.model.message.MessageAudioEvent
fun MessageAudioEvent?.isVoiceBroadcast() = this?.getVoiceBroadcastEventId() != null
fun MessageAudioEvent?.isVoiceBroadcast() = this?.root?.getClearContent()?.get(VoiceBroadcastConstants.VOICE_BROADCAST_CHUNK_KEY) != null
fun MessageAudioEvent.getVoiceBroadcastEventId(): String? =
// TODO Improve this condition by checking the referenced event type
root.takeIf { content.voiceMessageIndicator != null }
?.getRelationContent()?.takeIf { it.type == RelationType.REFERENCE }
?.eventId
fun MessageAudioEvent.getVoiceBroadcastEventId(): String? = if (isVoiceBroadcast()) root.getRelationContent()?.eventId else null
fun MessageAudioEvent.getVoiceBroadcastChunk(): VoiceBroadcastChunk? {
@Suppress("UNCHECKED_CAST")
return (root.getClearContent()?.get(VoiceBroadcastConstants.VOICE_BROADCAST_CHUNK_KEY) as? Content).toModel<VoiceBroadcastChunk>()
}
val MessageAudioEvent.sequence: Int? get() = getVoiceBroadcastChunk()?.sequence

View file

@ -85,7 +85,7 @@ class VoiceBroadcastPlayer @Inject constructor(
private fun updatePlaylist(room: Room, eventId: String) {
val timelineEvents = room.timelineService().getTimelineEventsRelatedTo(RelationType.REFERENCE, eventId)
val audioEvents = timelineEvents.mapNotNull { it.root.asMessageAudioEvent() }
playlist = audioEvents.sortedBy { it.root.originServerTs }
playlist = audioEvents.sortedBy { it.getVoiceBroadcastChunk()?.sequence?.toLong() ?: it.root.originServerTs }
}
private fun startPlayback() {

View file

@ -38,6 +38,8 @@ data class MessageVoiceBroadcastInfoContent(
@Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
@Json(name = "m.new_content") override val newContent: Content? = null,
/** The device from which the broadcast has been started. */
@Json(name = "device_id") val deviceId: String? = null,
/** The [VoiceBroadcastState] value. **/
@Json(name = "state") val voiceBroadcastStateStr: String = "",
/** The length of the voice chunks in seconds. **/

View file

@ -0,0 +1,25 @@
/*
* 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.features.voicebroadcast.model
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class VoiceBroadcastChunk(
@Json(name = "sequence") val sequence: Int? = null
)

View file

@ -23,6 +23,7 @@ import im.vector.app.features.attachments.toContentAttachmentData
import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants
import im.vector.app.features.voicebroadcast.VoiceBroadcastRecorder
import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastChunk
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent
import im.vector.lib.multipicker.utils.toMultiPickerAudioType
@ -70,6 +71,7 @@ class StartVoiceBroadcastUseCase @Inject constructor(
eventType = VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO,
stateKey = session.myUserId,
body = MessageVoiceBroadcastInfoContent(
deviceId = session.sessionParams.deviceId,
voiceBroadcastStateStr = VoiceBroadcastState.STARTED.value,
chunkLength = chunkLength,
).toContent()
@ -93,12 +95,14 @@ class StartVoiceBroadcastUseCase @Inject constructor(
"Voice Broadcast Part ($sequence).${voiceMessageFile.extension}"
)
val audioType = outputFileUri.toMultiPickerAudioType(context) ?: return
// TODO put sequence in event content
room.sendService().sendMedia(
attachment = audioType.toContentAttachmentData(isVoiceMessage = true),
compressBeforeSending = false,
roomIds = emptySet(),
relatesTo = RelationDefaultContent(RelationType.REFERENCE, referenceEventId)
relatesTo = RelationDefaultContent(RelationType.REFERENCE, referenceEventId),
additionalContent = mapOf(
VoiceBroadcastConstants.VOICE_BROADCAST_CHUNK_KEY to VoiceBroadcastChunk(sequence = sequence).toContent()
)
)
}
}

View file

@ -17,6 +17,7 @@
package im.vector.app.test.fakes
import io.mockk.mockk
import org.matrix.android.sdk.api.session.events.model.Content
import org.matrix.android.sdk.api.session.room.model.message.PollType
import org.matrix.android.sdk.api.session.room.send.SendService
import org.matrix.android.sdk.api.util.Cancelable
@ -25,5 +26,5 @@ class FakeSendService : SendService by mockk() {
private val cancelable = mockk<Cancelable>()
override fun sendPoll(pollType: PollType, question: String, options: List<String>): Cancelable = cancelable
override fun sendPoll(pollType: PollType, question: String, options: List<String>, additionalContent: Content?): Cancelable = cancelable
}