forked from GitHub-Mirror/riotX-android
Merge pull request #322 from vector-im/feature/clean
Improve reply feature
This commit is contained in:
commit
98306e223b
@ -16,8 +16,8 @@
|
|||||||
package im.vector.matrix.android.api.session.room.model.relation
|
package im.vector.matrix.android.api.session.room.model.relation
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import im.vector.matrix.android.api.session.events.model.Event
|
|
||||||
import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary
|
import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary
|
||||||
|
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||||
import im.vector.matrix.android.api.util.Cancelable
|
import im.vector.matrix.android.api.util.Cancelable
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -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: TimelineEvent, replyText: String, autoMarkdown: Boolean = false): Cancelable?
|
||||||
|
|
||||||
fun getEventSummaryLive(eventId: String): LiveData<EventAnnotationsSummary>
|
fun getEventSummaryLive(eventId: String): LiveData<EventAnnotationsSummary>
|
||||||
}
|
}
|
@ -18,7 +18,9 @@ package im.vector.matrix.android.api.session.room.timeline
|
|||||||
|
|
||||||
import im.vector.matrix.android.api.session.events.model.Event
|
import im.vector.matrix.android.api.session.events.model.Event
|
||||||
import im.vector.matrix.android.api.session.events.model.EventType
|
import im.vector.matrix.android.api.session.events.model.EventType
|
||||||
|
import im.vector.matrix.android.api.session.events.model.toModel
|
||||||
import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary
|
import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary
|
||||||
|
import im.vector.matrix.android.api.session.room.model.message.MessageContent
|
||||||
import im.vector.matrix.android.api.session.room.send.SendState
|
import im.vector.matrix.android.api.session.room.send.SendState
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -80,3 +82,9 @@ data class TimelineEvent(
|
|||||||
return EventType.ENCRYPTED == root.type
|
return EventType.ENCRYPTED == root.type
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get last MessageContent, after a possible edition
|
||||||
|
*/
|
||||||
|
fun TimelineEvent.getLastMessageContent(): MessageContent? = annotations?.editSummary?.aggregatedContent?.toModel()
|
||||||
|
?: root.getClearContent().toModel()
|
||||||
|
@ -73,6 +73,7 @@ import im.vector.matrix.android.internal.session.room.membership.RoomMembers
|
|||||||
import im.vector.matrix.android.internal.session.sync.model.SyncResponse
|
import im.vector.matrix.android.internal.session.sync.model.SyncResponse
|
||||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||||
import im.vector.matrix.android.internal.task.configureWith
|
import im.vector.matrix.android.internal.task.configureWith
|
||||||
|
import im.vector.matrix.android.internal.task.toConfigurableTask
|
||||||
import im.vector.matrix.android.internal.util.JsonCanonicalizer
|
import im.vector.matrix.android.internal.util.JsonCanonicalizer
|
||||||
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||||
import im.vector.matrix.android.internal.util.fetchCopied
|
import im.vector.matrix.android.internal.util.fetchCopied
|
||||||
@ -197,7 +198,7 @@ internal class CryptoManager @Inject constructor(
|
|||||||
|
|
||||||
override fun getDevicesList(callback: MatrixCallback<DevicesListResponse>) {
|
override fun getDevicesList(callback: MatrixCallback<DevicesListResponse>) {
|
||||||
getDevicesTask
|
getDevicesTask
|
||||||
.configureWith(Unit)
|
.toConfigurableTask()
|
||||||
.dispatchTo(callback)
|
.dispatchTo(callback)
|
||||||
.executeBy(taskExecutor)
|
.executeBy(taskExecutor)
|
||||||
}
|
}
|
||||||
@ -1054,7 +1055,8 @@ internal class CryptoManager @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun clearCryptoCache(callback: MatrixCallback<Unit>) {
|
override fun clearCryptoCache(callback: MatrixCallback<Unit>) {
|
||||||
clearCryptoDataTask.configureWith(Unit)
|
clearCryptoDataTask
|
||||||
|
.toConfigurableTask()
|
||||||
.dispatchTo(callback)
|
.dispatchTo(callback)
|
||||||
.executeBy(taskExecutor)
|
.executeBy(taskExecutor)
|
||||||
}
|
}
|
||||||
|
@ -51,6 +51,7 @@ import im.vector.matrix.android.internal.crypto.store.db.model.KeysBackupDataEnt
|
|||||||
import im.vector.matrix.android.internal.di.MoshiProvider
|
import im.vector.matrix.android.internal.di.MoshiProvider
|
||||||
import im.vector.matrix.android.internal.extensions.foldToCallback
|
import im.vector.matrix.android.internal.extensions.foldToCallback
|
||||||
import im.vector.matrix.android.internal.session.SessionScope
|
import im.vector.matrix.android.internal.session.SessionScope
|
||||||
|
import im.vector.matrix.android.internal.task.*
|
||||||
import im.vector.matrix.android.internal.task.Task
|
import im.vector.matrix.android.internal.task.Task
|
||||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||||
import im.vector.matrix.android.internal.task.TaskThread
|
import im.vector.matrix.android.internal.task.TaskThread
|
||||||
@ -876,7 +877,7 @@ internal class KeysBackup @Inject constructor(
|
|||||||
|
|
||||||
override fun getCurrentVersion(callback: MatrixCallback<KeysVersionResult?>) {
|
override fun getCurrentVersion(callback: MatrixCallback<KeysVersionResult?>) {
|
||||||
getKeysBackupLastVersionTask
|
getKeysBackupLastVersionTask
|
||||||
.configureWith(Unit)
|
.toConfigurableTask()
|
||||||
.dispatchTo(object : MatrixCallback<KeysVersionResult> {
|
.dispatchTo(object : MatrixCallback<KeysVersionResult> {
|
||||||
override fun onSuccess(data: KeysVersionResult) {
|
override fun onSuccess(data: KeysVersionResult) {
|
||||||
callback.onSuccess(data)
|
callback.onSuccess(data)
|
||||||
|
@ -19,9 +19,8 @@ package im.vector.matrix.android.internal.session.cache
|
|||||||
import im.vector.matrix.android.api.MatrixCallback
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
import im.vector.matrix.android.api.session.cache.CacheService
|
import im.vector.matrix.android.api.session.cache.CacheService
|
||||||
import im.vector.matrix.android.internal.di.SessionDatabase
|
import im.vector.matrix.android.internal.di.SessionDatabase
|
||||||
import im.vector.matrix.android.internal.session.SessionScope
|
|
||||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||||
import im.vector.matrix.android.internal.task.configureWith
|
import im.vector.matrix.android.internal.task.toConfigurableTask
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal class DefaultCacheService @Inject constructor(@SessionDatabase private val clearCacheTask: ClearCacheTask,
|
internal class DefaultCacheService @Inject constructor(@SessionDatabase private val clearCacheTask: ClearCacheTask,
|
||||||
@ -29,7 +28,7 @@ internal class DefaultCacheService @Inject constructor(@SessionDatabase private
|
|||||||
|
|
||||||
override fun clearCache(callback: MatrixCallback<Unit>) {
|
override fun clearCache(callback: MatrixCallback<Unit>) {
|
||||||
clearCacheTask
|
clearCacheTask
|
||||||
.configureWith(Unit)
|
.toConfigurableTask()
|
||||||
.dispatchTo(callback)
|
.dispatchTo(callback)
|
||||||
.executeBy(taskExecutor)
|
.executeBy(taskExecutor)
|
||||||
}
|
}
|
||||||
|
@ -29,6 +29,7 @@ import im.vector.matrix.android.internal.database.model.PusherEntity
|
|||||||
import im.vector.matrix.android.internal.database.query.where
|
import im.vector.matrix.android.internal.database.query.where
|
||||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||||
import im.vector.matrix.android.internal.task.configureWith
|
import im.vector.matrix.android.internal.task.configureWith
|
||||||
|
import im.vector.matrix.android.internal.task.toConfigurableTask
|
||||||
import im.vector.matrix.android.internal.worker.WorkManagerUtil
|
import im.vector.matrix.android.internal.worker.WorkManagerUtil
|
||||||
import im.vector.matrix.android.internal.worker.WorkManagerUtil.matrixOneTimeWorkRequestBuilder
|
import im.vector.matrix.android.internal.worker.WorkManagerUtil.matrixOneTimeWorkRequestBuilder
|
||||||
import im.vector.matrix.android.internal.worker.WorkerParamsFactory
|
import im.vector.matrix.android.internal.worker.WorkerParamsFactory
|
||||||
@ -49,7 +50,7 @@ internal class DefaultPusherService @Inject constructor(
|
|||||||
|
|
||||||
override fun refreshPushers() {
|
override fun refreshPushers() {
|
||||||
getPusherTask
|
getPusherTask
|
||||||
.configureWith(Unit)
|
.toConfigurableTask()
|
||||||
.executeBy(taskExecutor)
|
.executeBy(taskExecutor)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,6 +27,7 @@ import im.vector.matrix.android.internal.session.room.directory.GetThirdPartyPro
|
|||||||
import im.vector.matrix.android.internal.session.room.membership.joining.JoinRoomTask
|
import im.vector.matrix.android.internal.session.room.membership.joining.JoinRoomTask
|
||||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||||
import im.vector.matrix.android.internal.task.configureWith
|
import im.vector.matrix.android.internal.task.configureWith
|
||||||
|
import im.vector.matrix.android.internal.task.toConfigurableTask
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal class DefaultRoomDirectoryService @Inject constructor(private val getPublicRoomTask: GetPublicRoomTask,
|
internal class DefaultRoomDirectoryService @Inject constructor(private val getPublicRoomTask: GetPublicRoomTask,
|
||||||
@ -52,7 +53,7 @@ internal class DefaultRoomDirectoryService @Inject constructor(private val getPu
|
|||||||
|
|
||||||
override fun getThirdPartyProtocol(callback: MatrixCallback<Map<String, ThirdPartyProtocol>>) {
|
override fun getThirdPartyProtocol(callback: MatrixCallback<Map<String, ThirdPartyProtocol>>) {
|
||||||
getThirdPartyProtocolsTask
|
getThirdPartyProtocolsTask
|
||||||
.configureWith(Unit)
|
.toConfigurableTask()
|
||||||
.dispatchTo(callback)
|
.dispatchTo(callback)
|
||||||
.executeBy(taskExecutor)
|
.executeBy(taskExecutor)
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,7 @@ import im.vector.matrix.android.api.session.events.model.Event
|
|||||||
import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary
|
import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary
|
||||||
import im.vector.matrix.android.api.session.room.model.message.MessageType
|
import im.vector.matrix.android.api.session.room.model.message.MessageType
|
||||||
import im.vector.matrix.android.api.session.room.model.relation.RelationService
|
import im.vector.matrix.android.api.session.room.model.relation.RelationService
|
||||||
|
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||||
import im.vector.matrix.android.api.util.Cancelable
|
import im.vector.matrix.android.api.util.Cancelable
|
||||||
import im.vector.matrix.android.internal.database.RealmLiveData
|
import im.vector.matrix.android.internal.database.RealmLiveData
|
||||||
import im.vector.matrix.android.internal.database.helper.addSendingEvent
|
import im.vector.matrix.android.internal.database.helper.addSendingEvent
|
||||||
@ -127,8 +128,8 @@ internal class DefaultRelationService @Inject constructor(private val context: C
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun replyToMessage(eventReplied: Event, replyText: String): Cancelable? {
|
override fun replyToMessage(eventReplied: TimelineEvent, 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
|
||||||
|
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,6 +28,8 @@ import im.vector.matrix.android.api.session.room.model.relation.ReactionContent
|
|||||||
import im.vector.matrix.android.api.session.room.model.relation.ReactionInfo
|
import im.vector.matrix.android.api.session.room.model.relation.ReactionInfo
|
||||||
import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultContent
|
import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultContent
|
||||||
import im.vector.matrix.android.api.session.room.model.relation.ReplyToContent
|
import im.vector.matrix.android.api.session.room.model.relation.ReplyToContent
|
||||||
|
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||||
|
import im.vector.matrix.android.api.session.room.timeline.getLastMessageContent
|
||||||
import im.vector.matrix.android.internal.database.helper.addSendingEvent
|
import im.vector.matrix.android.internal.database.helper.addSendingEvent
|
||||||
import im.vector.matrix.android.internal.database.model.RoomEntity
|
import im.vector.matrix.android.internal.database.model.RoomEntity
|
||||||
import im.vector.matrix.android.internal.database.query.where
|
import im.vector.matrix.android.internal.database.query.where
|
||||||
@ -52,68 +54,54 @@ 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(
|
return createEvent(roomId, textContent.toMessageTextContent())
|
||||||
type = MessageType.MSGTYPE_TEXT,
|
|
||||||
format = MessageType.FORMAT_MATRIX_HTML,
|
|
||||||
body = text,
|
|
||||||
formattedBody = formattedText
|
|
||||||
)
|
|
||||||
return createEvent(roomId, content)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun createReplaceTextEvent(roomId: String,
|
fun createReplaceTextEvent(roomId: String,
|
||||||
targetEventId: String,
|
targetEventId: String,
|
||||||
newBodyText: String,
|
newBodyText: String,
|
||||||
newBodyAutoMarkdown: Boolean,
|
newBodyAutoMarkdown: Boolean,
|
||||||
msgType: String,
|
msgType: String,
|
||||||
compatibilityText: String): Event {
|
compatibilityText: String): Event {
|
||||||
|
return createEvent(roomId,
|
||||||
var newContent = MessageTextContent(
|
MessageTextContent(
|
||||||
type = MessageType.MSGTYPE_TEXT,
|
|
||||||
body = newBodyText
|
|
||||||
)
|
|
||||||
if (newBodyAutoMarkdown) {
|
|
||||||
val parser = Parser.builder().build()
|
|
||||||
val document = parser.parse(newBodyText)
|
|
||||||
val renderer = HtmlRenderer.builder().build()
|
|
||||||
val htmlText = renderer.render(document)
|
|
||||||
if (isFormattedTextPertinent(newBodyText, htmlText)) {
|
|
||||||
newContent = MessageTextContent(
|
|
||||||
type = MessageType.MSGTYPE_TEXT,
|
|
||||||
format = MessageType.FORMAT_MATRIX_HTML,
|
|
||||||
body = newBodyText,
|
|
||||||
formattedBody = htmlText
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val content = MessageTextContent(
|
|
||||||
type = msgType,
|
type = msgType,
|
||||||
body = compatibilityText,
|
body = compatibilityText,
|
||||||
relatesTo = RelationDefaultContent(RelationType.REPLACE, targetEventId),
|
relatesTo = RelationDefaultContent(RelationType.REPLACE, targetEventId),
|
||||||
newContent = newContent.toContent()
|
newContent = createTextContent(newBodyText, newBodyAutoMarkdown)
|
||||||
)
|
.toMessageTextContent()
|
||||||
return createEvent(roomId, content)
|
.toContent()
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createMediaEvent(roomId: String, attachment: ContentAttachmentData): Event {
|
fun createMediaEvent(roomId: String, attachment: ContentAttachmentData): Event {
|
||||||
@ -202,7 +190,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 +203,8 @@ 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,11 +233,11 @@ 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: TimelineEvent, 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.root) ?: return null
|
||||||
val userId = eventReplied.senderId ?: return null
|
val userId = eventReplied.root.senderId ?: return null
|
||||||
val userLink = PermalinkFactory.createPermalink(userId) ?: return null
|
val userLink = PermalinkFactory.createPermalink(userId) ?: return null
|
||||||
// <mx-reply>
|
// <mx-reply>
|
||||||
// <blockquote>
|
// <blockquote>
|
||||||
@ -259,67 +248,66 @@ internal class LocalEchoEventFactory @Inject constructor(private val credentials
|
|||||||
// </blockquote>
|
// </blockquote>
|
||||||
// </mx-reply>
|
// </mx-reply>
|
||||||
// This is where the reply goes.
|
// This is where the reply goes.
|
||||||
val body = bodyForReply(eventReplied.getClearContent().toModel<MessageContent>())
|
val body = bodyForReply(eventReplied.getLastMessageContent())
|
||||||
val replyFallbackTemplateFormatted = """<mx-reply>
|
val replyFormatted = REPLY_PATTERN.format(
|
||||||
<blockquote>
|
permalink,
|
||||||
<a href="%s">${stringProvider.getString(R.string.message_reply_to_prefix)}</a>
|
stringProvider.getString(R.string.message_reply_to_prefix),
|
||||||
<a href="%s">%s</a>
|
userLink,
|
||||||
<br />
|
userId,
|
||||||
%s
|
body.takeFormatted(),
|
||||||
</blockquote>
|
createTextContent(replyText, autoMarkdown).takeFormatted()
|
||||||
</mx-reply>
|
)
|
||||||
%s""".trimIndent().format(permalink, userLink, userId, body.second ?: body.first, replyText)
|
|
||||||
//
|
//
|
||||||
// > <@alice:example.org> This is the original body
|
// > <@alice:example.org> This is the original body
|
||||||
//
|
//
|
||||||
// This is where the reply goes
|
val lines = body.text.split("\n")
|
||||||
val lines = body.first.split("\n")
|
val replyFallback = StringBuffer("><$userId>")
|
||||||
val plainTextBody = StringBuffer("><${userId}>")
|
|
||||||
lines.firstOrNull()?.also { plainTextBody.append(" $it") }
|
|
||||||
lines.forEachIndexed { index, s ->
|
lines.forEachIndexed { index, s ->
|
||||||
if (index > 0) {
|
if (index == 0) {
|
||||||
plainTextBody.append("\n>$s")
|
replyFallback.append(" $s")
|
||||||
|
} else {
|
||||||
|
replyFallback.append("\n>$s")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
plainTextBody.append("\n\n").append(replyText)
|
replyFallback.append("\n\n").append(replyText)
|
||||||
|
|
||||||
val eventId = eventReplied.eventId ?: return null
|
val eventId = eventReplied.root.eventId ?: return null
|
||||||
val content = MessageTextContent(
|
val content = MessageTextContent(
|
||||||
type = MessageType.MSGTYPE_TEXT,
|
type = MessageType.MSGTYPE_TEXT,
|
||||||
format = MessageType.FORMAT_MATRIX_HTML,
|
format = MessageType.FORMAT_MATRIX_HTML,
|
||||||
body = plainTextBody.toString(),
|
body = replyFallback.toString(),
|
||||||
formattedBody = replyFallbackTemplateFormatted,
|
formattedBody = replyFormatted,
|
||||||
relatesTo = RelationDefaultContent(null, null, ReplyToContent(eventId))
|
relatesTo = RelationDefaultContent(null, null, ReplyToContent(eventId))
|
||||||
)
|
)
|
||||||
return createEvent(roomId, content)
|
return createEvent(roomId, content)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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?): Pair<String, String?> {
|
private fun bodyForReply(content: MessageContent?): TextContent {
|
||||||
when (content?.type) {
|
when (content?.type) {
|
||||||
MessageType.MSGTYPE_EMOTE,
|
MessageType.MSGTYPE_EMOTE,
|
||||||
MessageType.MSGTYPE_TEXT,
|
MessageType.MSGTYPE_TEXT,
|
||||||
MessageType.MSGTYPE_NOTICE -> {
|
MessageType.MSGTYPE_NOTICE -> {
|
||||||
//If we already have formatted body, return it?
|
|
||||||
var formattedText: String? = null
|
var formattedText: String? = null
|
||||||
if (content is MessageTextContent) {
|
if (content is MessageTextContent) {
|
||||||
if (content.format == MessageType.FORMAT_MATRIX_HTML) {
|
if (content.format == MessageType.FORMAT_MATRIX_HTML) {
|
||||||
formattedText = content.formattedBody
|
formattedText = content.formattedBody
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return content.body to formattedText
|
val isReply = content.relatesTo?.inReplyTo?.eventId != null
|
||||||
|
return if (isReply)
|
||||||
|
TextContent(content.body, formattedText).removeInReplyFallbacks()
|
||||||
|
else
|
||||||
|
TextContent(content.body, formattedText)
|
||||||
}
|
}
|
||||||
MessageType.MSGTYPE_FILE -> return stringProvider.getString(R.string.reply_to_a_file) to null
|
MessageType.MSGTYPE_FILE -> return TextContent(stringProvider.getString(R.string.reply_to_a_file))
|
||||||
MessageType.MSGTYPE_AUDIO -> return stringProvider.getString(R.string.reply_to_an_audio_file) to null
|
MessageType.MSGTYPE_AUDIO -> return TextContent(stringProvider.getString(R.string.reply_to_an_audio_file))
|
||||||
MessageType.MSGTYPE_IMAGE -> return stringProvider.getString(R.string.reply_to_an_image) to null
|
MessageType.MSGTYPE_IMAGE -> return TextContent(stringProvider.getString(R.string.reply_to_an_image))
|
||||||
MessageType.MSGTYPE_VIDEO -> return stringProvider.getString(R.string.reply_to_a_video) to null
|
MessageType.MSGTYPE_VIDEO -> return TextContent(stringProvider.getString(R.string.reply_to_a_video))
|
||||||
else -> return (content?.body ?: "") to null
|
else -> return TextContent(content?.body ?: "")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -365,6 +353,9 @@ internal class LocalEchoEventFactory @Inject constructor(private val credentials
|
|||||||
companion object {
|
companion object {
|
||||||
const val LOCAL_ID_PREFIX = "local."
|
const val LOCAL_ID_PREFIX = "local."
|
||||||
|
|
||||||
|
// No whitespace
|
||||||
|
const val REPLY_PATTERN = """<mx-reply><blockquote><a href="%s">%s</a><a href="%s">%s</a><br />%s</blockquote></mx-reply>%s"""
|
||||||
|
|
||||||
fun isLocalEchoId(eventId: String): Boolean = eventId.startsWith(LOCAL_ID_PREFIX)
|
fun isLocalEchoId(eventId: String): Boolean = eventId.startsWith(LOCAL_ID_PREFIX)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,74 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 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.matrix.android.internal.session.room.send
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.session.room.model.message.MessageTextContent
|
||||||
|
import im.vector.matrix.android.api.session.room.model.message.MessageType
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contains a text and eventually a formatted text
|
||||||
|
*/
|
||||||
|
data class TextContent(
|
||||||
|
val text: String,
|
||||||
|
|
||||||
|
val formattedText: String? = null
|
||||||
|
) {
|
||||||
|
fun takeFormatted() = formattedText ?: text
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun TextContent.toMessageTextContent(): MessageTextContent {
|
||||||
|
return MessageTextContent(
|
||||||
|
type = MessageType.MSGTYPE_TEXT,
|
||||||
|
format = MessageType.FORMAT_MATRIX_HTML.takeIf { formattedText != null },
|
||||||
|
body = text,
|
||||||
|
formattedBody = formattedText
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun TextContent.removeInReplyFallbacks(): TextContent {
|
||||||
|
return copy(
|
||||||
|
text = extractUsefulTextFromReply(this.text),
|
||||||
|
formattedText = this.formattedText?.let { extractUsefulTextFromHtmlReply(it) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun extractUsefulTextFromReply(repliedBody: String): String {
|
||||||
|
val lines = repliedBody.lines()
|
||||||
|
var wellFormed = repliedBody.startsWith(">")
|
||||||
|
var endOfPreviousFound = false
|
||||||
|
val usefullines = ArrayList<String>()
|
||||||
|
lines.forEach {
|
||||||
|
if (it == "") {
|
||||||
|
endOfPreviousFound = true
|
||||||
|
return@forEach
|
||||||
|
}
|
||||||
|
if (!endOfPreviousFound) {
|
||||||
|
wellFormed = wellFormed && it.startsWith(">")
|
||||||
|
} else {
|
||||||
|
usefullines.add(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return usefullines.joinToString("\n").takeIf { wellFormed } ?: repliedBody
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun extractUsefulTextFromHtmlReply(repliedBody: String): String {
|
||||||
|
if (repliedBody.startsWith("<mx-reply>")) {
|
||||||
|
return repliedBody.substring(repliedBody.lastIndexOf("</mx-reply>") + "</mx-reply>".length).trim()
|
||||||
|
}
|
||||||
|
return repliedBody
|
||||||
|
}
|
@ -19,7 +19,7 @@ package im.vector.matrix.android.internal.session.signout
|
|||||||
import im.vector.matrix.android.api.MatrixCallback
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
import im.vector.matrix.android.api.session.signout.SignOutService
|
import im.vector.matrix.android.api.session.signout.SignOutService
|
||||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||||
import im.vector.matrix.android.internal.task.configureWith
|
import im.vector.matrix.android.internal.task.toConfigurableTask
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal class DefaultSignOutService @Inject constructor(private val signOutTask: SignOutTask,
|
internal class DefaultSignOutService @Inject constructor(private val signOutTask: SignOutTask,
|
||||||
@ -27,7 +27,7 @@ internal class DefaultSignOutService @Inject constructor(private val signOutTask
|
|||||||
|
|
||||||
override fun signOut(callback: MatrixCallback<Unit>) {
|
override fun signOut(callback: MatrixCallback<Unit>) {
|
||||||
signOutTask
|
signOutTask
|
||||||
.configureWith(Unit)
|
.toConfigurableTask()
|
||||||
.dispatchTo(callback)
|
.dispatchTo(callback)
|
||||||
.executeBy(taskExecutor)
|
.executeBy(taskExecutor)
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,13 @@ internal fun <PARAMS, RESULT> Task<PARAMS, RESULT>.configureWith(params: PARAMS)
|
|||||||
return ConfigurableTask(this, params)
|
return ConfigurableTask(this, params)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a Task to a ConfigurableTask without parameter
|
||||||
|
*/
|
||||||
|
internal fun <RESULT> Task<Unit, RESULT>.toConfigurableTask(): ConfigurableTask<Unit, RESULT> {
|
||||||
|
return ConfigurableTask(this, Unit)
|
||||||
|
}
|
||||||
|
|
||||||
internal data class ConfigurableTask<PARAMS, RESULT>(
|
internal data class ConfigurableTask<PARAMS, RESULT>(
|
||||||
val task: Task<PARAMS, RESULT>,
|
val task: Task<PARAMS, RESULT>,
|
||||||
val params: PARAMS,
|
val params: PARAMS,
|
||||||
|
@ -38,7 +38,6 @@ import org.junit.Test
|
|||||||
|
|
||||||
class PushrulesConditionTest {
|
class PushrulesConditionTest {
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun test_eventmatch_type_condition() {
|
fun test_eventmatch_type_condition() {
|
||||||
val condition = EventMatchCondition("type", "m.room.message")
|
val condition = EventMatchCondition("type", "m.room.message")
|
||||||
@ -286,7 +285,7 @@ class PushrulesConditionTest {
|
|||||||
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
|
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun replyToMessage(eventReplied: Event, replyText: String): Cancelable? {
|
override fun replyToMessage(eventReplied: TimelineEvent, replyText: String, autoMarkdown: Boolean): Cancelable? {
|
||||||
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
|
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,6 +33,7 @@ import android.view.View
|
|||||||
import android.view.inputmethod.InputMethodManager
|
import android.view.inputmethod.InputMethodManager
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import androidx.annotation.DrawableRes
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.lifecycle.ViewModelProviders
|
import androidx.lifecycle.ViewModelProviders
|
||||||
@ -53,11 +54,11 @@ import com.otaliastudios.autocomplete.AutocompleteCallback
|
|||||||
import com.otaliastudios.autocomplete.CharPolicy
|
import com.otaliastudios.autocomplete.CharPolicy
|
||||||
import im.vector.matrix.android.api.permalinks.PermalinkFactory
|
import im.vector.matrix.android.api.permalinks.PermalinkFactory
|
||||||
import im.vector.matrix.android.api.session.Session
|
import im.vector.matrix.android.api.session.Session
|
||||||
import im.vector.matrix.android.api.session.events.model.toModel
|
|
||||||
import im.vector.matrix.android.api.session.room.model.EditAggregatedSummary
|
import im.vector.matrix.android.api.session.room.model.EditAggregatedSummary
|
||||||
import im.vector.matrix.android.api.session.room.model.Membership
|
import im.vector.matrix.android.api.session.room.model.Membership
|
||||||
import im.vector.matrix.android.api.session.room.model.message.*
|
import im.vector.matrix.android.api.session.room.model.message.*
|
||||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||||
|
import im.vector.matrix.android.api.session.room.timeline.getLastMessageContent
|
||||||
import im.vector.matrix.android.api.session.user.model.User
|
import im.vector.matrix.android.api.session.user.model.User
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
import im.vector.riotx.core.di.ScreenComponent
|
import im.vector.riotx.core.di.ScreenComponent
|
||||||
@ -90,6 +91,7 @@ import im.vector.riotx.features.home.room.detail.timeline.action.MessageMenuView
|
|||||||
import im.vector.riotx.features.home.room.detail.timeline.action.ViewReactionBottomSheet
|
import im.vector.riotx.features.home.room.detail.timeline.action.ViewReactionBottomSheet
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.helper.EndlessRecyclerViewScrollListener
|
import im.vector.riotx.features.home.room.detail.timeline.helper.EndlessRecyclerViewScrollListener
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData
|
import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData
|
||||||
|
import im.vector.riotx.features.html.EventHtmlRenderer
|
||||||
import im.vector.riotx.features.html.PillImageSpan
|
import im.vector.riotx.features.html.PillImageSpan
|
||||||
import im.vector.riotx.features.invite.VectorInviteView
|
import im.vector.riotx.features.invite.VectorInviteView
|
||||||
import im.vector.riotx.features.media.ImageContentRenderer
|
import im.vector.riotx.features.media.ImageContentRenderer
|
||||||
@ -104,8 +106,6 @@ import kotlinx.android.parcel.Parcelize
|
|||||||
import kotlinx.android.synthetic.main.fragment_room_detail.*
|
import kotlinx.android.synthetic.main.fragment_room_detail.*
|
||||||
import kotlinx.android.synthetic.main.merge_composer_layout.view.*
|
import kotlinx.android.synthetic.main.merge_composer_layout.view.*
|
||||||
import org.commonmark.parser.Parser
|
import org.commonmark.parser.Parser
|
||||||
import ru.noties.markwon.Markwon
|
|
||||||
import ru.noties.markwon.html.HtmlPlugin
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@ -178,6 +178,7 @@ class RoomDetailFragment :
|
|||||||
@Inject lateinit var errorFormatter: ErrorFormatter
|
@Inject lateinit var errorFormatter: ErrorFormatter
|
||||||
private lateinit var scrollOnNewMessageCallback: ScrollOnNewMessageCallback
|
private lateinit var scrollOnNewMessageCallback: ScrollOnNewMessageCallback
|
||||||
private lateinit var scrollOnHighlightedEventCallback: ScrollOnHighlightedEventCallback
|
private lateinit var scrollOnHighlightedEventCallback: ScrollOnHighlightedEventCallback
|
||||||
|
@Inject lateinit var eventHtmlRenderer: EventHtmlRenderer
|
||||||
|
|
||||||
|
|
||||||
override fun getLayoutResId() = R.layout.fragment_room_detail
|
override fun getLayoutResId() = R.layout.fragment_room_detail
|
||||||
@ -225,61 +226,43 @@ class RoomDetailFragment :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
roomDetailViewModel.selectSubscribe(
|
roomDetailViewModel.selectSubscribe(RoomDetailViewState::sendMode) { mode ->
|
||||||
RoomDetailViewState::sendMode,
|
|
||||||
RoomDetailViewState::selectedEvent,
|
|
||||||
RoomDetailViewState::roomId) { mode, event, roomId ->
|
|
||||||
when (mode) {
|
when (mode) {
|
||||||
SendMode.REGULAR -> {
|
SendMode.REGULAR -> exitSpecialMode()
|
||||||
|
is SendMode.EDIT -> enterSpecialMode(mode.timelineEvent, R.drawable.ic_edit, true)
|
||||||
|
is SendMode.QUOTE -> enterSpecialMode(mode.timelineEvent, R.drawable.ic_quote, false)
|
||||||
|
is SendMode.REPLY -> enterSpecialMode(mode.timelineEvent, R.drawable.ic_reply, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun exitSpecialMode() {
|
||||||
commandAutocompletePolicy.enabled = true
|
commandAutocompletePolicy.enabled = true
|
||||||
val uid = session.sessionParams.credentials.userId
|
|
||||||
val meMember = session.getRoom(roomId)?.getRoomMember(uid)
|
|
||||||
avatarRenderer.render(meMember?.avatarUrl, uid, meMember?.displayName, composerLayout.composerAvatarImageView)
|
|
||||||
composerLayout.collapse()
|
composerLayout.collapse()
|
||||||
}
|
}
|
||||||
SendMode.EDIT,
|
|
||||||
SendMode.QUOTE,
|
private fun enterSpecialMode(event: TimelineEvent, @DrawableRes iconRes: Int, useText: Boolean) {
|
||||||
SendMode.REPLY -> {
|
|
||||||
commandAutocompletePolicy.enabled = false
|
commandAutocompletePolicy.enabled = false
|
||||||
if (event == null) {
|
|
||||||
//we should ignore? can this happen?
|
|
||||||
Timber.e("Enter edit mode with no event selected")
|
|
||||||
return@selectSubscribe
|
|
||||||
}
|
|
||||||
//switch to expanded bar
|
//switch to expanded bar
|
||||||
composerLayout.composerRelatedMessageTitle.apply {
|
composerLayout.composerRelatedMessageTitle.apply {
|
||||||
text = event.getDisambiguatedDisplayName()
|
text = event.getDisambiguatedDisplayName()
|
||||||
setTextColor(ContextCompat.getColor(requireContext(), getColorFromUserId(event.root.senderId)))
|
setTextColor(ContextCompat.getColor(requireContext(), getColorFromUserId(event.root.senderId)))
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO this is used at several places, find way to refactor?
|
val messageContent: MessageContent? = event.getLastMessageContent()
|
||||||
val messageContent: MessageContent? =
|
|
||||||
event.annotations?.editSummary?.aggregatedContent?.toModel()
|
|
||||||
?: event.root.getClearContent().toModel()
|
|
||||||
val nonFormattedBody = messageContent?.body ?: ""
|
val nonFormattedBody = messageContent?.body ?: ""
|
||||||
var formattedBody: CharSequence? = null
|
var formattedBody: CharSequence? = null
|
||||||
if (messageContent is MessageTextContent && messageContent.format == MessageType.FORMAT_MATRIX_HTML) {
|
if (messageContent is MessageTextContent && messageContent.format == MessageType.FORMAT_MATRIX_HTML) {
|
||||||
val parser = Parser.builder().build()
|
val parser = Parser.builder().build()
|
||||||
val document = parser.parse(messageContent.formattedBody
|
val document = parser.parse(messageContent.formattedBody
|
||||||
?: messageContent.body)
|
?: messageContent.body)
|
||||||
formattedBody = Markwon.builder(requireContext())
|
formattedBody = eventHtmlRenderer.render(document)
|
||||||
.usePlugin(HtmlPlugin.create()).build().render(document)
|
|
||||||
}
|
}
|
||||||
composerLayout.composerRelatedMessageContent.text = formattedBody
|
composerLayout.composerRelatedMessageContent.text = formattedBody
|
||||||
?: nonFormattedBody
|
?: nonFormattedBody
|
||||||
|
|
||||||
|
composerLayout.composerEditText.setText(if (useText) nonFormattedBody else "")
|
||||||
if (mode == SendMode.EDIT) {
|
composerLayout.composerRelatedMessageActionIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), iconRes))
|
||||||
//TODO if it's a reply we should trim the top part of message
|
|
||||||
composerLayout.composerEditText.setText(nonFormattedBody)
|
|
||||||
composerLayout.composerRelatedMessageActionIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_edit))
|
|
||||||
} else if (mode == SendMode.QUOTE) {
|
|
||||||
composerLayout.composerEditText.setText("")
|
|
||||||
composerLayout.composerRelatedMessageActionIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_quote))
|
|
||||||
} else if (mode == SendMode.REPLY) {
|
|
||||||
composerLayout.composerEditText.setText("")
|
|
||||||
composerLayout.composerRelatedMessageActionIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_reply))
|
|
||||||
}
|
|
||||||
|
|
||||||
avatarRenderer.render(event.senderAvatar, event.root.senderId
|
avatarRenderer.render(event.senderAvatar, event.root.senderId
|
||||||
?: "", event.senderName, composerLayout.composerRelatedMessageAvatar)
|
?: "", event.senderName, composerLayout.composerRelatedMessageAvatar)
|
||||||
@ -288,14 +271,6 @@ class RoomDetailFragment :
|
|||||||
composerLayout.expand {
|
composerLayout.expand {
|
||||||
focusComposerAndShowKeyboard()
|
focusComposerAndShowKeyboard()
|
||||||
}
|
}
|
||||||
composerLayout.composerRelatedMessageCloseButton.setOnClickListener {
|
|
||||||
composerLayout.composerEditText.setText("")
|
|
||||||
roomDetailViewModel.resetSendMode()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
@ -422,6 +397,10 @@ class RoomDetailFragment :
|
|||||||
roomDetailViewModel.process(RoomDetailActions.SendMessage(textMessage, VectorPreferences.isMarkdownEnabled(requireContext())))
|
roomDetailViewModel.process(RoomDetailActions.SendMessage(textMessage, VectorPreferences.isMarkdownEnabled(requireContext())))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
composerLayout.composerRelatedMessageCloseButton.setOnClickListener {
|
||||||
|
composerLayout.composerEditText.setText("")
|
||||||
|
roomDetailViewModel.resetSendMode()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupAttachmentButton() {
|
private fun setupAttachmentButton() {
|
||||||
|
@ -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
|
||||||
@ -126,11 +124,10 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun enterEditMode(event: TimelineEvent) {
|
private fun enterEditMode(event: TimelineEvent) {
|
||||||
setState {
|
setState {
|
||||||
copy(
|
copy(
|
||||||
sendMode = SendMode.EDIT,
|
sendMode = SendMode.EDIT(event)
|
||||||
selectedEvent = event
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -138,8 +135,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
|||||||
fun resetSendMode() {
|
fun resetSendMode() {
|
||||||
setState {
|
setState {
|
||||||
copy(
|
copy(
|
||||||
sendMode = SendMode.REGULAR,
|
sendMode = SendMode.REGULAR
|
||||||
selectedEvent = null
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -233,21 +229,29 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SendMode.EDIT -> {
|
is SendMode.EDIT -> {
|
||||||
room.editTextMessage(state.selectedEvent?.root?.eventId
|
val messageContent: MessageContent? =
|
||||||
|
state.sendMode.timelineEvent.annotations?.editSummary?.aggregatedContent.toModel()
|
||||||
|
?: state.sendMode.timelineEvent.root.getClearContent().toModel()
|
||||||
|
val nonFormattedBody = messageContent?.body ?: ""
|
||||||
|
|
||||||
|
if (nonFormattedBody != action.text) {
|
||||||
|
room.editTextMessage(state.sendMode.timelineEvent.root.eventId
|
||||||
?: "", action.text, action.autoMarkdown)
|
?: "", action.text, action.autoMarkdown)
|
||||||
|
} else {
|
||||||
|
Timber.w("Same message content, do not send edition")
|
||||||
|
}
|
||||||
setState {
|
setState {
|
||||||
copy(
|
copy(
|
||||||
sendMode = SendMode.REGULAR,
|
sendMode = SendMode.REGULAR
|
||||||
selectedEvent = null
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.MessageSent))
|
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.MessageSent))
|
||||||
}
|
}
|
||||||
SendMode.QUOTE -> {
|
is SendMode.QUOTE -> {
|
||||||
val messageContent: MessageContent? =
|
val messageContent: MessageContent? =
|
||||||
state.selectedEvent?.annotations?.editSummary?.aggregatedContent?.toModel()
|
state.sendMode.timelineEvent.annotations?.editSummary?.aggregatedContent.toModel()
|
||||||
?: state.selectedEvent?.root?.getClearContent().toModel()
|
?: state.sendMode.timelineEvent.root.getClearContent().toModel()
|
||||||
val textMsg = messageContent?.body
|
val textMsg = messageContent?.body
|
||||||
|
|
||||||
val finalText = legacyRiotQuoteText(textMsg, action.text)
|
val finalText = legacyRiotQuoteText(textMsg, action.text)
|
||||||
@ -264,19 +268,17 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
|||||||
}
|
}
|
||||||
setState {
|
setState {
|
||||||
copy(
|
copy(
|
||||||
sendMode = SendMode.REGULAR,
|
sendMode = SendMode.REGULAR
|
||||||
selectedEvent = null
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.MessageSent))
|
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.MessageSent))
|
||||||
}
|
}
|
||||||
SendMode.REPLY -> {
|
is SendMode.REPLY -> {
|
||||||
state.selectedEvent?.let {
|
state.sendMode.timelineEvent.let {
|
||||||
room.replyToMessage(it.root, action.text)
|
room.replyToMessage(it, action.text, action.autoMarkdown)
|
||||||
setState {
|
setState {
|
||||||
copy(
|
copy(
|
||||||
sendMode = SendMode.REGULAR,
|
sendMode = SendMode.REGULAR
|
||||||
selectedEvent = null
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.MessageSent))
|
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.MessageSent))
|
||||||
@ -427,8 +429,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
|||||||
room.getTimeLineEvent(action.eventId)?.let {
|
room.getTimeLineEvent(action.eventId)?.let {
|
||||||
setState {
|
setState {
|
||||||
copy(
|
copy(
|
||||||
sendMode = SendMode.QUOTE,
|
sendMode = SendMode.QUOTE(it)
|
||||||
selectedEvent = it
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -438,8 +439,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
|||||||
room.getTimeLineEvent(action.eventId)?.let {
|
room.getTimeLineEvent(action.eventId)?.let {
|
||||||
setState {
|
setState {
|
||||||
copy(
|
copy(
|
||||||
sendMode = SendMode.REPLY,
|
sendMode = SendMode.REPLY(it)
|
||||||
selectedEvent = it
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,11 +32,11 @@ import im.vector.matrix.android.api.session.user.model.User
|
|||||||
*
|
*
|
||||||
* Depending on the state the bottom toolbar will change (icons/preview/actions...)
|
* Depending on the state the bottom toolbar will change (icons/preview/actions...)
|
||||||
*/
|
*/
|
||||||
enum class SendMode {
|
sealed class SendMode {
|
||||||
REGULAR,
|
object REGULAR : SendMode()
|
||||||
QUOTE,
|
data class QUOTE(val timelineEvent: TimelineEvent) : SendMode()
|
||||||
EDIT,
|
data class EDIT(val timelineEvent: TimelineEvent) : SendMode()
|
||||||
REPLY
|
data class REPLY(val timelineEvent: TimelineEvent) : SendMode()
|
||||||
}
|
}
|
||||||
|
|
||||||
data class RoomDetailViewState(
|
data class RoomDetailViewState(
|
||||||
@ -46,7 +46,6 @@ data class RoomDetailViewState(
|
|||||||
val asyncInviter: Async<User> = Uninitialized,
|
val asyncInviter: Async<User> = Uninitialized,
|
||||||
val asyncRoomSummary: Async<RoomSummary> = Uninitialized,
|
val asyncRoomSummary: Async<RoomSummary> = Uninitialized,
|
||||||
val sendMode: SendMode = SendMode.REGULAR,
|
val sendMode: SendMode = SendMode.REGULAR,
|
||||||
val selectedEvent: TimelineEvent? = null,
|
|
||||||
val isEncrypted: Boolean = false
|
val isEncrypted: Boolean = false
|
||||||
) : MvRxState {
|
) : MvRxState {
|
||||||
|
|
||||||
|
@ -21,11 +21,11 @@ import com.squareup.inject.assisted.AssistedInject
|
|||||||
import dagger.Lazy
|
import dagger.Lazy
|
||||||
import im.vector.matrix.android.api.session.Session
|
import im.vector.matrix.android.api.session.Session
|
||||||
import im.vector.matrix.android.api.session.events.model.EventType
|
import im.vector.matrix.android.api.session.events.model.EventType
|
||||||
import im.vector.matrix.android.api.session.events.model.toModel
|
|
||||||
import im.vector.matrix.android.api.session.room.model.message.MessageContent
|
import im.vector.matrix.android.api.session.room.model.message.MessageContent
|
||||||
import im.vector.matrix.android.api.session.room.model.message.MessageTextContent
|
import im.vector.matrix.android.api.session.room.model.message.MessageTextContent
|
||||||
import im.vector.matrix.android.api.session.room.model.message.MessageType
|
import im.vector.matrix.android.api.session.room.model.message.MessageType
|
||||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||||
|
import im.vector.matrix.android.api.session.room.timeline.getLastMessageContent
|
||||||
import im.vector.matrix.rx.RxRoom
|
import im.vector.matrix.rx.RxRoom
|
||||||
import im.vector.riotx.core.extensions.canReact
|
import im.vector.riotx.core.extensions.canReact
|
||||||
import im.vector.riotx.core.platform.VectorViewModel
|
import im.vector.riotx.core.platform.VectorViewModel
|
||||||
@ -58,8 +58,7 @@ data class MessageActionState(
|
|||||||
fun messageBody(eventHtmlRenderer: EventHtmlRenderer?, noticeEventFormatter: NoticeEventFormatter?): CharSequence? {
|
fun messageBody(eventHtmlRenderer: EventHtmlRenderer?, noticeEventFormatter: NoticeEventFormatter?): CharSequence? {
|
||||||
return when (timelineEvent()?.root?.getClearType()) {
|
return when (timelineEvent()?.root?.getClearType()) {
|
||||||
EventType.MESSAGE -> {
|
EventType.MESSAGE -> {
|
||||||
val messageContent: MessageContent? = timelineEvent()?.annotations?.editSummary?.aggregatedContent?.toModel()
|
val messageContent: MessageContent? = timelineEvent()?.getLastMessageContent()
|
||||||
?: timelineEvent()?.root?.getClearContent().toModel()
|
|
||||||
if (messageContent is MessageTextContent && messageContent.format == MessageType.FORMAT_MATRIX_HTML) {
|
if (messageContent is MessageTextContent && messageContent.format == MessageType.FORMAT_MATRIX_HTML) {
|
||||||
eventHtmlRenderer?.render(messageContent.formattedBody
|
eventHtmlRenderer?.render(messageContent.formattedBody
|
||||||
?: messageContent.body)
|
?: messageContent.body)
|
||||||
|
@ -27,11 +27,11 @@ import dagger.Lazy
|
|||||||
import im.vector.matrix.android.api.permalinks.MatrixLinkify
|
import im.vector.matrix.android.api.permalinks.MatrixLinkify
|
||||||
import im.vector.matrix.android.api.permalinks.MatrixPermalinkSpan
|
import im.vector.matrix.android.api.permalinks.MatrixPermalinkSpan
|
||||||
import im.vector.matrix.android.api.session.events.model.RelationType
|
import im.vector.matrix.android.api.session.events.model.RelationType
|
||||||
import im.vector.matrix.android.api.session.events.model.toModel
|
|
||||||
import im.vector.matrix.android.api.session.room.model.EditAggregatedSummary
|
import im.vector.matrix.android.api.session.room.model.EditAggregatedSummary
|
||||||
import im.vector.matrix.android.api.session.room.model.message.*
|
import im.vector.matrix.android.api.session.room.model.message.*
|
||||||
import im.vector.matrix.android.api.session.room.send.SendState
|
import im.vector.matrix.android.api.session.room.send.SendState
|
||||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||||
|
import im.vector.matrix.android.api.session.room.timeline.getLastMessageContent
|
||||||
import im.vector.matrix.android.internal.crypto.attachments.toElementToDecrypt
|
import im.vector.matrix.android.internal.crypto.attachments.toElementToDecrypt
|
||||||
import im.vector.riotx.EmojiCompatFontProvider
|
import im.vector.riotx.EmojiCompatFontProvider
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
@ -79,8 +79,7 @@ class MessageItemFactory @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val messageContent: MessageContent =
|
val messageContent: MessageContent =
|
||||||
event.annotations?.editSummary?.aggregatedContent?.toModel()
|
event.getLastMessageContent()
|
||||||
?: event.root.getClearContent().toModel()
|
|
||||||
?: //Malformed content, we should echo something on screen
|
?: //Malformed content, we should echo something on screen
|
||||||
return DefaultItem_().text(stringProvider.getString(R.string.malformed_message))
|
return DefaultItem_().text(stringProvider.getString(R.string.malformed_message))
|
||||||
|
|
||||||
@ -311,8 +310,7 @@ class MessageItemFactory @Inject constructor(
|
|||||||
editSummary: EditAggregatedSummary?): SpannableStringBuilder {
|
editSummary: EditAggregatedSummary?): SpannableStringBuilder {
|
||||||
val spannable = SpannableStringBuilder()
|
val spannable = SpannableStringBuilder()
|
||||||
spannable.append(linkifiedBody)
|
spannable.append(linkifiedBody)
|
||||||
// TODO i18n
|
val editedSuffix = stringProvider.getString(R.string.edited_suffix)
|
||||||
val editedSuffix = "(edited)"
|
|
||||||
spannable.append(" ").append(editedSuffix)
|
spannable.append(" ").append(editedSuffix)
|
||||||
val color = colorProvider.getColorFromAttribute(R.attr.vctr_list_header_secondary_text_color)
|
val color = colorProvider.getColorFromAttribute(R.attr.vctr_list_header_secondary_text_color)
|
||||||
val editStart = spannable.indexOf(editedSuffix)
|
val editStart = spannable.indexOf(editedSuffix)
|
||||||
|
@ -50,6 +50,10 @@ class EventHtmlRenderer @Inject constructor(context: Context,
|
|||||||
return markwon.toMarkdown(text)
|
return markwon.toMarkdown(text)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun render(node: Node) : CharSequence {
|
||||||
|
return markwon.render(node)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class MatrixPlugin private constructor(private val glideRequests: GlideRequests,
|
private class MatrixPlugin private constructor(private val glideRequests: GlideRequests,
|
||||||
|
@ -18,15 +18,14 @@ package im.vector.riotx.features.notifications
|
|||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import im.vector.matrix.android.api.session.Session
|
import im.vector.matrix.android.api.session.Session
|
||||||
import im.vector.matrix.android.api.session.content.ContentUrlResolver
|
import im.vector.matrix.android.api.session.content.ContentUrlResolver
|
||||||
import im.vector.matrix.android.api.session.crypto.CryptoService
|
|
||||||
import im.vector.matrix.android.api.session.crypto.MXCryptoError
|
import im.vector.matrix.android.api.session.crypto.MXCryptoError
|
||||||
import im.vector.matrix.android.api.session.events.model.Event
|
import im.vector.matrix.android.api.session.events.model.Event
|
||||||
import im.vector.matrix.android.api.session.events.model.EventType
|
import im.vector.matrix.android.api.session.events.model.EventType
|
||||||
import im.vector.matrix.android.api.session.events.model.toModel
|
import im.vector.matrix.android.api.session.events.model.toModel
|
||||||
import im.vector.matrix.android.api.session.room.model.Membership
|
import im.vector.matrix.android.api.session.room.model.Membership
|
||||||
import im.vector.matrix.android.api.session.room.model.RoomMember
|
import im.vector.matrix.android.api.session.room.model.RoomMember
|
||||||
import im.vector.matrix.android.api.session.room.model.message.MessageContent
|
|
||||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||||
|
import im.vector.matrix.android.api.session.room.timeline.getLastMessageContent
|
||||||
import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult
|
import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult
|
||||||
import im.vector.riotx.BuildConfig
|
import im.vector.riotx.BuildConfig
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
@ -94,8 +93,8 @@ class NotifiableEventResolver @Inject constructor(private val stringProvider: St
|
|||||||
Timber.e("## Unable to resolve room for eventId [${event}]")
|
Timber.e("## Unable to resolve room for eventId [${event}]")
|
||||||
// Ok room is not known in store, but we can still display something
|
// Ok room is not known in store, but we can still display something
|
||||||
val body =
|
val body =
|
||||||
event.annotations?.editSummary?.aggregatedContent?.toModel<MessageContent>()?.body
|
event.getLastMessageContent()
|
||||||
?: event.root.getClearContent().toModel<MessageContent>()?.body
|
?.body
|
||||||
?: stringProvider.getString(R.string.notification_unknown_new_event)
|
?: stringProvider.getString(R.string.notification_unknown_new_event)
|
||||||
val roomName = stringProvider.getString(R.string.notification_unknown_room_name)
|
val roomName = stringProvider.getString(R.string.notification_unknown_room_name)
|
||||||
val senderDisplayName = event.senderName ?: event.root.senderId
|
val senderDisplayName = event.senderName ?: event.root.senderId
|
||||||
@ -129,8 +128,8 @@ class NotifiableEventResolver @Inject constructor(private val stringProvider: St
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val body = event.annotations?.editSummary?.aggregatedContent?.toModel<MessageContent>()?.body
|
val body = event.getLastMessageContent()
|
||||||
?: event.root.getClearContent().toModel<MessageContent>()?.body
|
?.body
|
||||||
?: stringProvider.getString(R.string.notification_unknown_new_event)
|
?: stringProvider.getString(R.string.notification_unknown_new_event)
|
||||||
val roomName = room.roomSummary()?.displayName ?: ""
|
val roomName = room.roomSummary()?.displayName ?: ""
|
||||||
val senderDisplayName = event.senderName ?: event.root.senderId
|
val senderDisplayName = event.senderName ?: event.root.senderId
|
||||||
|
@ -14,5 +14,7 @@
|
|||||||
<string name="downloading_file">Downloading file %1$s…</string>
|
<string name="downloading_file">Downloading file %1$s…</string>
|
||||||
<string name="downloaded_file">File %1$s has been downloaded!</string>
|
<string name="downloaded_file">File %1$s has been downloaded!</string>
|
||||||
|
|
||||||
|
<string name="edited_suffix">"(edited)"</string>
|
||||||
|
|
||||||
|
|
||||||
</resources>
|
</resources>
|
Loading…
Reference in New Issue
Block a user