Reply with formatted content

This commit is contained in:
Benoit Marty 2019-07-10 11:29:47 +02:00
parent 92e3a02389
commit 1918302297
5 changed files with 35 additions and 26 deletions

View file

@ -77,8 +77,9 @@ interface RelationService {
* https://matrix.org/docs/spec/client_server/r0.4.0.html#id350 * https://matrix.org/docs/spec/client_server/r0.4.0.html#id350
* @param eventReplied the event referenced by the reply * @param eventReplied the event referenced by the reply
* @param replyText the reply text * @param replyText the reply text
* @param autoMarkdown If true, the SDK will generate a formatted HTML message from the body text if markdown syntax is present
*/ */
fun replyToMessage(eventReplied: Event, replyText: String): Cancelable? fun replyToMessage(eventReplied: Event, replyText: String, autoMarkdown: Boolean = false): Cancelable?
fun getEventSummaryLive(eventId: String): LiveData<EventAnnotationsSummary> fun getEventSummaryLive(eventId: String): LiveData<EventAnnotationsSummary>
} }

View file

@ -127,8 +127,8 @@ internal class DefaultRelationService @Inject constructor(private val context: C
} }
override fun replyToMessage(eventReplied: Event, replyText: String): Cancelable? { override fun replyToMessage(eventReplied: Event, replyText: String, autoMarkdown: Boolean): Cancelable? {
val event = eventFactory.createReplyTextEvent(roomId, eventReplied, replyText)?.also { val event = eventFactory.createReplyTextEvent(roomId, eventReplied, replyText, autoMarkdown)?.also {
saveLocalEcho(it) saveLocalEcho(it)
} ?: return null } ?: return null

View file

@ -59,7 +59,7 @@ internal class DefaultSendService @Inject constructor(private val context: Conte
} }
override fun sendFormattedTextMessage(text: String, formattedText: String): Cancelable { override fun sendFormattedTextMessage(text: String, formattedText: String): Cancelable {
val event = localEchoEventFactory.createFormattedTextEvent(roomId, text, formattedText).also { val event = localEchoEventFactory.createFormattedTextEvent(roomId, TextContent(text, formattedText)).also {
saveLocalEcho(it) saveLocalEcho(it)
} }

View file

@ -52,30 +52,41 @@ import javax.inject.Inject
internal class LocalEchoEventFactory @Inject constructor(private val credentials: Credentials, internal class LocalEchoEventFactory @Inject constructor(private val credentials: Credentials,
private val stringProvider: StringProvider, private val stringProvider: StringProvider,
private val roomSummaryUpdater: RoomSummaryUpdater) { private val roomSummaryUpdater: RoomSummaryUpdater) {
// TODO Inject
private val parser = Parser.builder().build()
// TODO Inject
private val renderer = HtmlRenderer.builder().build()
fun createTextEvent(roomId: String, msgType: String, text: String, autoMarkdown: Boolean): Event { fun createTextEvent(roomId: String, msgType: String, text: String, autoMarkdown: Boolean): Event {
if (autoMarkdown && msgType == MessageType.MSGTYPE_TEXT) { if (msgType == MessageType.MSGTYPE_TEXT) {
val parser = Parser.builder().build() return createFormattedTextEvent(roomId, createTextContent(text, autoMarkdown))
val document = parser.parse(text)
val renderer = HtmlRenderer.builder().build()
val htmlText = renderer.render(document)
if (isFormattedTextPertinent(text, htmlText)) { //FIXME
return createFormattedTextEvent(roomId, text, htmlText)
}
} }
val content = MessageTextContent(type = msgType, body = text) val content = MessageTextContent(type = msgType, body = text)
return createEvent(roomId, content) return createEvent(roomId, content)
} }
private fun createTextContent(text: String, autoMarkdown: Boolean): TextContent {
if (autoMarkdown) {
val document = parser.parse(text)
val htmlText = renderer.render(document)
if (isFormattedTextPertinent(text, htmlText)) {
return TextContent(text, htmlText)
}
}
return TextContent(text)
}
private fun isFormattedTextPertinent(text: String, htmlText: String?) = private fun isFormattedTextPertinent(text: String, htmlText: String?) =
text != htmlText && htmlText != "<p>${text.trim()}</p>\n" text != htmlText && htmlText != "<p>${text.trim()}</p>\n"
fun createFormattedTextEvent(roomId: String, text: String, formattedText: String): Event { fun createFormattedTextEvent(roomId: String, textContent: TextContent): Event {
val content = MessageTextContent( val content = MessageTextContent(
type = MessageType.MSGTYPE_TEXT, type = MessageType.MSGTYPE_TEXT,
format = MessageType.FORMAT_MATRIX_HTML, format = if (textContent.formattedText == null) MessageType.FORMAT_MATRIX_HTML else null,
body = text, body = textContent.text,
formattedBody = formattedText formattedBody = textContent.formattedText
) )
return createEvent(roomId, content) return createEvent(roomId, content)
} }
@ -87,7 +98,7 @@ internal class LocalEchoEventFactory @Inject constructor(private val credentials
newBodyAutoMarkdown: Boolean, newBodyAutoMarkdown: Boolean,
msgType: String, msgType: String,
compatibilityText: String): Event { compatibilityText: String): Event {
// TODO Format newBodyText
var newContent = MessageTextContent( var newContent = MessageTextContent(
type = MessageType.MSGTYPE_TEXT, type = MessageType.MSGTYPE_TEXT,
body = newBodyText body = newBodyText
@ -202,7 +213,7 @@ internal class LocalEchoEventFactory @Inject constructor(private val credentials
type = MessageType.MSGTYPE_AUDIO, type = MessageType.MSGTYPE_AUDIO,
body = attachment.name ?: "audio", body = attachment.name ?: "audio",
audioInfo = AudioInfo( audioInfo = AudioInfo(
mimeType = attachment.mimeType ?: "audio/mpeg", mimeType = attachment.mimeType.takeIf { it.isNotBlank() } ?: "audio/mpeg",
size = attachment.size size = attachment.size
), ),
url = attachment.path url = attachment.path
@ -215,7 +226,7 @@ internal class LocalEchoEventFactory @Inject constructor(private val credentials
type = MessageType.MSGTYPE_FILE, type = MessageType.MSGTYPE_FILE,
body = attachment.name ?: "file", body = attachment.name ?: "file",
info = FileInfo( info = FileInfo(
mimeType = attachment.mimeType ?: "application/octet-stream", mimeType = attachment.mimeType.takeIf { it.isNotBlank() } ?: "application/octet-stream",
size = attachment.size size = attachment.size
), ),
url = attachment.path url = attachment.path
@ -244,7 +255,7 @@ internal class LocalEchoEventFactory @Inject constructor(private val credentials
return "$LOCAL_ID_PREFIX${UUID.randomUUID()}" return "$LOCAL_ID_PREFIX${UUID.randomUUID()}"
} }
fun createReplyTextEvent(roomId: String, eventReplied: Event, replyText: String): Event? { fun createReplyTextEvent(roomId: String, eventReplied: Event, replyText: String, autoMarkdown: Boolean): Event? {
//Fallbacks and event representation //Fallbacks and event representation
//TODO Add error/warning logs when any of this is null //TODO Add error/warning logs when any of this is null
val permalink = PermalinkFactory.createPermalink(eventReplied) ?: return null val permalink = PermalinkFactory.createPermalink(eventReplied) ?: return null
@ -266,7 +277,7 @@ internal class LocalEchoEventFactory @Inject constructor(private val credentials
userLink, userLink,
userId, userId,
body.takeFormatted(), body.takeFormatted(),
replyText createTextContent(replyText, autoMarkdown).takeFormatted()
) )
// //
// > <@alice:example.org> This is the original body // > <@alice:example.org> This is the original body
@ -294,8 +305,7 @@ internal class LocalEchoEventFactory @Inject constructor(private val credentials
} }
/** /**
* Returns a pair of <Plain Text, Formatted Text?> used for the fallback event representation * Returns a TextContent used for the fallback event representation in a reply message.
* in a reply message.
*/ */
private fun bodyForReply(content: MessageContent?): TextContent { private fun bodyForReply(content: MessageContent?): TextContent {
when (content?.type) { when (content?.type) {

View file

@ -47,8 +47,6 @@ import im.vector.riotx.core.utils.LiveEvent
import im.vector.riotx.features.command.CommandParser import im.vector.riotx.features.command.CommandParser
import im.vector.riotx.features.command.ParsedCommand import im.vector.riotx.features.command.ParsedCommand
import im.vector.riotx.features.home.room.detail.timeline.helper.TimelineDisplayableEvents import im.vector.riotx.features.home.room.detail.timeline.helper.TimelineDisplayableEvents
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.rxkotlin.subscribeBy import io.reactivex.rxkotlin.subscribeBy
import org.commonmark.parser.Parser import org.commonmark.parser.Parser
import org.commonmark.renderer.html.HtmlRenderer import org.commonmark.renderer.html.HtmlRenderer
@ -272,7 +270,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
} }
SendMode.REPLY -> { SendMode.REPLY -> {
state.selectedEvent?.let { state.selectedEvent?.let {
room.replyToMessage(it.root, action.text) room.replyToMessage(it.root, action.text, action.autoMarkdown)
setState { setState {
copy( copy(
sendMode = SendMode.REGULAR, sendMode = SendMode.REGULAR,