Add Replies support from within a thread
This commit is contained in:
parent
4160688f83
commit
3d9350091e
|
@ -134,11 +134,15 @@ interface RelationService {
|
|||
* by the sdk into pills.
|
||||
* @param rootThreadEventId the root thread eventId
|
||||
* @param replyInThreadText the reply text
|
||||
* @param msgType the message type: MessageType.MSGTYPE_TEXT (default) or MessageType.MSGTYPE_EMOTE
|
||||
* @param formattedText The formatted body using MessageType#FORMAT_MATRIX_HTML
|
||||
* @param autoMarkdown If true, the SDK will generate a formatted HTML message from the body text if markdown syntax is present
|
||||
* @param eventReplied the event referenced by the reply within a thread
|
||||
*/
|
||||
fun replyInThread(rootThreadEventId: String,
|
||||
replyInThreadText: CharSequence,
|
||||
msgType: String = MessageType.MSGTYPE_TEXT,
|
||||
autoMarkdown: Boolean = false,
|
||||
formattedText: String? = null): Cancelable?
|
||||
formattedText: String? = null,
|
||||
eventReplied: TimelineEvent? = null): Cancelable?
|
||||
}
|
||||
|
|
|
@ -38,7 +38,6 @@ import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
|
|||
import org.matrix.android.sdk.internal.database.query.where
|
||||
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||
import org.matrix.android.sdk.internal.session.room.send.LocalEchoEventFactory
|
||||
import org.matrix.android.sdk.internal.session.room.send.TextContent
|
||||
import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProcessor
|
||||
import org.matrix.android.sdk.internal.task.TaskExecutor
|
||||
import org.matrix.android.sdk.internal.task.configureWith
|
||||
|
@ -133,7 +132,11 @@ internal class DefaultRelationService @AssistedInject constructor(
|
|||
}
|
||||
|
||||
override fun replyToMessage(eventReplied: TimelineEvent, replyText: CharSequence, autoMarkdown: Boolean): Cancelable? {
|
||||
val event = eventFactory.createReplyTextEvent(roomId, eventReplied, replyText, autoMarkdown)
|
||||
val event = eventFactory.createReplyTextEvent(
|
||||
roomId = roomId,
|
||||
eventReplied = eventReplied,
|
||||
replyText = replyText,
|
||||
autoMarkdown = autoMarkdown)
|
||||
?.also { saveLocalEcho(it) }
|
||||
?: return null
|
||||
|
||||
|
@ -159,15 +162,27 @@ internal class DefaultRelationService @AssistedInject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override fun replyInThread(rootThreadEventId: String, replyInThreadText: CharSequence, msgType: String, autoMarkdown: Boolean, formattedText: String?): Cancelable {
|
||||
val event = eventFactory.createThreadTextEvent(
|
||||
override fun replyInThread(
|
||||
rootThreadEventId: String,
|
||||
replyInThreadText: CharSequence,
|
||||
msgType: String,
|
||||
autoMarkdown: Boolean,
|
||||
formattedText: String?,
|
||||
eventReplied: TimelineEvent?): Cancelable {
|
||||
val event = eventReplied?.let {
|
||||
eventFactory.createReplyTextEvent(
|
||||
roomId = roomId,
|
||||
eventReplied = eventReplied,
|
||||
replyText = replyInThreadText,
|
||||
autoMarkdown = autoMarkdown,
|
||||
rootThreadEventId = rootThreadEventId)
|
||||
} ?: eventFactory.createThreadTextEvent(
|
||||
rootThreadEventId = rootThreadEventId,
|
||||
roomId = roomId,
|
||||
text = replyInThreadText.toString(),
|
||||
msgType = msgType,
|
||||
autoMarkdown = autoMarkdown,
|
||||
formattedText = formattedText
|
||||
)
|
||||
formattedText = formattedText)
|
||||
// .also {
|
||||
// saveLocalEcho(it)
|
||||
// }
|
||||
|
|
|
@ -67,7 +67,11 @@ internal class EventEditor @Inject constructor(private val eventSenderProcessor:
|
|||
val roomId = replyToEdit.roomId
|
||||
if (replyToEdit.root.sendState.hasFailed()) {
|
||||
// We create a new in memory event for the EventSenderProcessor but we keep the eventId of the failed event.
|
||||
val editedEvent = eventFactory.createReplyTextEvent(roomId, originalTimelineEvent, newBodyText, false)?.copy(
|
||||
val editedEvent = eventFactory.createReplyTextEvent(
|
||||
roomId = roomId,
|
||||
eventReplied = originalTimelineEvent,
|
||||
replyText = newBodyText,
|
||||
autoMarkdown = false)?.copy(
|
||||
eventId = replyToEdit.eventId
|
||||
) ?: return NoOpCancellable
|
||||
updateFailedEchoWithEvent(roomId, replyToEdit.eventId, editedEvent)
|
||||
|
|
|
@ -341,7 +341,7 @@ internal class LocalEchoEventFactory @Inject constructor(
|
|||
}
|
||||
|
||||
/**
|
||||
* Creates a thread event related to the already existing event
|
||||
* Creates a thread event related to the already existing root event
|
||||
*/
|
||||
fun createThreadTextEvent(
|
||||
rootThreadEventId: String,
|
||||
|
@ -363,10 +363,14 @@ internal class LocalEchoEventFactory @Inject constructor(
|
|||
return System.currentTimeMillis()
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a reply to a regular timeline Event or a thread Event if needed
|
||||
*/
|
||||
fun createReplyTextEvent(roomId: String,
|
||||
eventReplied: TimelineEvent,
|
||||
replyText: CharSequence,
|
||||
autoMarkdown: Boolean): Event? {
|
||||
autoMarkdown: Boolean,
|
||||
rootThreadEventId: String? = null): Event? {
|
||||
// Fallbacks and event representation
|
||||
// TODO Add error/warning logs when any of this is null
|
||||
val permalink = permalinkFactory.createPermalink(eventReplied.root, false) ?: return null
|
||||
|
@ -393,11 +397,22 @@ internal class LocalEchoEventFactory @Inject constructor(
|
|||
format = MessageFormat.FORMAT_MATRIX_HTML,
|
||||
body = replyFallback,
|
||||
formattedBody = replyFormatted,
|
||||
relatesTo = RelationDefaultContent(null, null, ReplyToContent(eventId))
|
||||
)
|
||||
relatesTo = generateReplyRelationContent(eventId = eventId, rootThreadEventId = rootThreadEventId))
|
||||
return createMessageEvent(roomId, content)
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the appropriate relatesTo object for a reply event.
|
||||
* It can either be a regular reply or a reply within a thread
|
||||
*/
|
||||
private fun generateReplyRelationContent(eventId: String, rootThreadEventId: String? = null): RelationDefaultContent =
|
||||
rootThreadEventId?.let {
|
||||
RelationDefaultContent(
|
||||
type = RelationType.THREAD,
|
||||
eventId = it,
|
||||
inReplyTo = ReplyToContent(eventId))
|
||||
} ?: RelationDefaultContent(null, null, ReplyToContent(eventId))
|
||||
|
||||
private fun buildReplyFallback(body: TextContent, originalSenderId: String?, newBodyText: String): String {
|
||||
return buildString {
|
||||
append("> <")
|
||||
|
|
|
@ -28,6 +28,4 @@ sealed class TextComposerAction : VectorViewModelAction {
|
|||
data class UserIsTyping(val isTyping: Boolean) : TextComposerAction()
|
||||
data class OnTextChanged(val text: CharSequence) : TextComposerAction()
|
||||
data class OnVoiceRecordingStateChanged(val isRecording: Boolean) : TextComposerAction()
|
||||
data class EnterReplyInThreadTimeline(val rootThreadEventId: String) : TextComposerAction()
|
||||
|
||||
}
|
||||
|
|
|
@ -276,7 +276,7 @@ class TextComposerViewModel @AssistedInject constructor(
|
|||
replyInThreadText = slashCommandResult.message,
|
||||
msgType = MessageType.MSGTYPE_EMOTE,
|
||||
formattedText = rainbowGenerator.generate(message))
|
||||
} ?: room.sendFormattedTextMessage(message, rainbowGenerator.generate(message),MessageType.MSGTYPE_EMOTE)
|
||||
} ?: room.sendFormattedTextMessage(message, rainbowGenerator.generate(message), MessageType.MSGTYPE_EMOTE)
|
||||
|
||||
_viewEvents.post(TextComposerViewEvents.SlashCommandHandled())
|
||||
popDraft()
|
||||
|
@ -465,20 +465,36 @@ class TextComposerViewModel @AssistedInject constructor(
|
|||
val document = parser.parse(finalText)
|
||||
val renderer = HtmlRenderer.builder().build()
|
||||
val htmlText = renderer.render(document)
|
||||
|
||||
if (finalText == htmlText) {
|
||||
room.sendTextMessage(finalText)
|
||||
state.rootThreadEventId?.let {
|
||||
room.replyInThread(
|
||||
rootThreadEventId = it,
|
||||
replyInThreadText = finalText)
|
||||
} ?: room.sendTextMessage(finalText)
|
||||
} else {
|
||||
room.sendFormattedTextMessage(finalText, htmlText)
|
||||
state.rootThreadEventId?.let {
|
||||
room.replyInThread(
|
||||
rootThreadEventId = it,
|
||||
replyInThreadText = finalText,
|
||||
formattedText = htmlText)
|
||||
} ?: room.sendFormattedTextMessage(finalText, htmlText)
|
||||
}
|
||||
_viewEvents.post(TextComposerViewEvents.MessageSent)
|
||||
popDraft()
|
||||
}
|
||||
is SendMode.REPLY -> {
|
||||
state.sendMode.timelineEvent.let {
|
||||
room.replyToMessage(it, action.text.toString(), action.autoMarkdown)
|
||||
_viewEvents.post(TextComposerViewEvents.MessageSent)
|
||||
popDraft()
|
||||
}
|
||||
val timelineEvent = state.sendMode.timelineEvent
|
||||
state.rootThreadEventId?.let { rootThreadEventId ->
|
||||
room.replyInThread(
|
||||
rootThreadEventId = rootThreadEventId,
|
||||
replyInThreadText = action.text.toString(),
|
||||
autoMarkdown = action.autoMarkdown,
|
||||
eventReplied = timelineEvent)
|
||||
} ?: room.replyToMessage(timelineEvent, action.text.toString(), action.autoMarkdown)
|
||||
|
||||
_viewEvents.post(TextComposerViewEvents.MessageSent)
|
||||
popDraft()
|
||||
}
|
||||
}.exhaustive
|
||||
}
|
||||
|
@ -705,7 +721,7 @@ class TextComposerViewModel @AssistedInject constructor(
|
|||
}
|
||||
rootThreadEventId?.let {
|
||||
room.replyInThread(it, sequence)
|
||||
}?: room.sendTextMessage(sequence)
|
||||
} ?: room.sendTextMessage(sequence)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in New Issue