mirror of
https://github.com/element-hq/element-android
synced 2024-11-27 20:06:51 +03:00
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:
commit
13972661e0
12 changed files with 176 additions and 69 deletions
1
changelog.d/7397.wip
Normal file
1
changelog.d/7397.wip
Normal file
|
@ -0,0 +1 @@
|
|||
[Voice Broadcast] Add additional data in events
|
|
@ -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.
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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. **/
|
||||
|
|
|
@ -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
|
||||
)
|
|
@ -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()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue