adding support for sending replies
This commit is contained in:
parent
418fa46346
commit
aa277615e5
|
@ -109,7 +109,7 @@ internal class MessengerViewModel(
|
|||
when (val composerState = state.composerState) {
|
||||
is ComposerState.Text -> {
|
||||
val copy = composerState.copy()
|
||||
updateState { copy(composerState = composerState.copy(value = "")) }
|
||||
updateState { copy(composerState = composerState.copy(value = "", reply = null)) }
|
||||
|
||||
state.roomState.takeIfContent()?.let { content ->
|
||||
val roomState = content.roomState
|
||||
|
@ -121,6 +121,18 @@ internal class MessengerViewModel(
|
|||
sendEncrypted = roomState.roomOverview.isEncrypted,
|
||||
localId = localIdFactory.create(),
|
||||
timestampUtc = clock.millis(),
|
||||
reply = copy.reply?.let {
|
||||
MessageService.Message.TextMessage.Reply(
|
||||
authorId = it.author.id,
|
||||
originalMessage = when (it) {
|
||||
is RoomEvent.Image -> TODO()
|
||||
is RoomEvent.Reply -> TODO()
|
||||
is RoomEvent.Message -> it.content
|
||||
},
|
||||
copy.value,
|
||||
it.eventId
|
||||
)
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,11 +1,7 @@
|
|||
package app.dapk.st.matrix.message
|
||||
|
||||
import app.dapk.st.core.Base64
|
||||
import app.dapk.st.matrix.*
|
||||
import app.dapk.st.matrix.common.AlgorithmName
|
||||
import app.dapk.st.matrix.common.EventId
|
||||
import app.dapk.st.matrix.common.MessageType
|
||||
import app.dapk.st.matrix.common.RoomId
|
||||
import app.dapk.st.matrix.common.*
|
||||
import app.dapk.st.matrix.message.internal.DefaultMessageService
|
||||
import app.dapk.st.matrix.message.internal.ImageContentReader
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
@ -43,7 +39,17 @@ interface MessageService : MatrixService {
|
|||
@SerialName("room_id") val roomId: RoomId,
|
||||
@SerialName("local_id") val localId: String,
|
||||
@SerialName("timestamp") val timestampUtc: Long,
|
||||
) : Message()
|
||||
@SerialName("reply") val reply: Reply? = null,
|
||||
@SerialName("reply_id") val replyId: String? = null,
|
||||
) : Message() {
|
||||
@Serializable
|
||||
data class Reply(
|
||||
val authorId: UserId,
|
||||
val originalMessage: String,
|
||||
val replyContent: String,
|
||||
val eventId: EventId
|
||||
)
|
||||
}
|
||||
|
||||
@Serializable
|
||||
@SerialName("image_message")
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package app.dapk.st.matrix.message.internal
|
||||
|
||||
import app.dapk.st.matrix.common.EventId
|
||||
import app.dapk.st.matrix.common.MessageType
|
||||
import app.dapk.st.matrix.common.MxUrl
|
||||
import app.dapk.st.matrix.common.RoomId
|
||||
|
@ -20,8 +21,26 @@ sealed class ApiMessage {
|
|||
@Serializable
|
||||
data class TextContent(
|
||||
@SerialName("body") val body: String,
|
||||
@SerialName("msgtype") val type: String = MessageType.TEXT.value,
|
||||
) : ApiMessageContent
|
||||
@SerialName("m.relates_to") val relatesTo: RelatesTo? = null,
|
||||
@SerialName("formatted_body") val formattedBody: String? = null,
|
||||
@SerialName("format") val format: String? = null,
|
||||
) : ApiMessageContent {
|
||||
|
||||
@SerialName("msgtype")
|
||||
val type: String = MessageType.TEXT.value
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class RelatesTo(
|
||||
@SerialName("m.in_reply_to") val inReplyTo: InReplyTo
|
||||
) {
|
||||
|
||||
@Serializable
|
||||
data class InReplyTo(
|
||||
@SerialName("event_id") val eventId: EventId
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
@Serializable
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
package app.dapk.st.matrix.message.internal
|
||||
|
||||
import app.dapk.st.matrix.common.EventId
|
||||
import app.dapk.st.matrix.common.EventType
|
||||
import app.dapk.st.matrix.common.JsonString
|
||||
import app.dapk.st.matrix.common.RoomId
|
||||
import app.dapk.st.matrix.common.*
|
||||
import app.dapk.st.matrix.http.MatrixHttpClient
|
||||
import app.dapk.st.matrix.http.MatrixHttpClient.HttpRequest
|
||||
import app.dapk.st.matrix.message.ApiSendResponse
|
||||
|
@ -37,7 +34,7 @@ internal class SendMessageUseCase(
|
|||
}
|
||||
|
||||
private suspend fun ApiMessageMapper.textMessageRequest(message: Message.TextMessage): HttpRequest<ApiSendResponse> {
|
||||
val contents = message.toContents()
|
||||
val contents = message.toContents(message.reply)
|
||||
return when (message.sendEncrypted) {
|
||||
true -> sendRequest(
|
||||
roomId = message.roomId,
|
||||
|
@ -49,6 +46,7 @@ internal class SendMessageUseCase(
|
|||
contents.toMessageJson(message.roomId)
|
||||
)
|
||||
),
|
||||
relatesTo = contents.relatesTo
|
||||
)
|
||||
|
||||
false -> sendRequest(
|
||||
|
@ -115,6 +113,7 @@ internal class SendMessageUseCase(
|
|||
eventType = EventType.ENCRYPTED,
|
||||
txId = message.localId,
|
||||
content = messageEncrypter.encrypt(MessageEncrypter.ClearMessagePayload(message.roomId, json)),
|
||||
relatesTo = null
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -148,14 +147,23 @@ internal class SendMessageUseCase(
|
|||
|
||||
}
|
||||
|
||||
private val MX_REPLY_REGEX = "<mx-reply>.*</mx-reply>".toRegex()
|
||||
|
||||
class ApiMessageMapper {
|
||||
|
||||
fun Message.TextMessage.toContents() = ApiMessage.TextMessage.TextContent(
|
||||
this.content.body,
|
||||
this.content.type,
|
||||
fun Message.TextMessage.toContents(reply: Message.TextMessage.Reply?) = when (reply) {
|
||||
null -> ApiMessage.TextMessage.TextContent(
|
||||
body = this.content.body,
|
||||
)
|
||||
|
||||
else -> ApiMessage.TextMessage.TextContent(
|
||||
body = buildReplyFallback(reply.originalMessage, reply.authorId, reply.replyContent),
|
||||
relatesTo = ApiMessage.RelatesTo(ApiMessage.RelatesTo.InReplyTo(reply.eventId)),
|
||||
formattedBody = buildFormattedReply(reply.authorId, reply.originalMessage, reply.replyContent, this.roomId, reply.eventId),
|
||||
format = "org.matrix.custom.html"
|
||||
)
|
||||
}
|
||||
|
||||
fun ApiMessage.TextMessage.TextContent.toMessageJson(roomId: RoomId) = JsonString(
|
||||
MatrixHttpClient.jsonWithDefaults.encodeToString(
|
||||
ApiMessage.TextMessage.serializer(),
|
||||
|
@ -167,4 +175,33 @@ class ApiMessageMapper {
|
|||
)
|
||||
)
|
||||
|
||||
private fun buildReplyFallback(originalMessage: String, originalSenderId: UserId, reply: String): String {
|
||||
return buildString {
|
||||
append("> <")
|
||||
append(originalSenderId.value)
|
||||
append(">")
|
||||
|
||||
val lines = originalMessage.split("\n")
|
||||
lines.forEachIndexed { index, s ->
|
||||
if (index == 0) {
|
||||
append(" $s")
|
||||
} else {
|
||||
append("\n> $s")
|
||||
}
|
||||
}
|
||||
append("\n\n")
|
||||
append(reply)
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildFormattedReply(userId: UserId, originalMessage: String, reply: String, roomId: RoomId, eventId: EventId): String {
|
||||
val permalink = "https://matrix.to/#/${roomId.value}/${eventId.value}"
|
||||
val userLink = "https://matrix.to/#/${userId.value}"
|
||||
val cleanOriginalMessage = originalMessage.replace(MX_REPLY_REGEX, "")
|
||||
return """
|
||||
<mx-reply><blockquote><a href="$permalink">In reply to</a> <a href="$userLink">${userId.value}</a><br>${cleanOriginalMessage}</blockquote></mx-reply>$reply
|
||||
""".trimIndent()
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package app.dapk.st.matrix.message.internal
|
||||
|
||||
import app.dapk.st.matrix.common.EventType
|
||||
import app.dapk.st.matrix.common.RoomId
|
||||
import app.dapk.st.matrix.common.*
|
||||
import app.dapk.st.matrix.http.MatrixHttpClient
|
||||
import app.dapk.st.matrix.http.MatrixHttpClient.HttpRequest.Companion.httpRequest
|
||||
import app.dapk.st.matrix.http.jsonBody
|
||||
|
@ -14,6 +13,8 @@ import app.dapk.st.matrix.message.internal.ApiMessage.TextMessage
|
|||
import io.ktor.http.*
|
||||
import io.ktor.http.content.*
|
||||
import io.ktor.utils.io.jvm.javaio.*
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import java.io.InputStream
|
||||
import java.util.*
|
||||
|
||||
|
@ -26,10 +27,29 @@ internal fun sendRequest(roomId: RoomId, eventType: EventType, txId: String, con
|
|||
}
|
||||
)
|
||||
|
||||
internal fun sendRequest(roomId: RoomId, eventType: EventType, txId: String, content: MessageEncrypter.EncryptedMessagePayload) = httpRequest<ApiSendResponse>(
|
||||
internal fun sendRequest(
|
||||
roomId: RoomId,
|
||||
eventType: EventType,
|
||||
txId: String,
|
||||
content: MessageEncrypter.EncryptedMessagePayload,
|
||||
relatesTo: ApiMessage.RelatesTo?
|
||||
) = httpRequest<ApiSendResponse>(
|
||||
path = "_matrix/client/r0/rooms/${roomId.value}/send/${eventType.value}/${txId}",
|
||||
method = MatrixHttpClient.Method.PUT,
|
||||
body = jsonBody(MessageEncrypter.EncryptedMessagePayload.serializer(), content)
|
||||
body = jsonBody(ApiEncryptedMessage.serializer(), content.let {
|
||||
val apiEncryptedMessage = ApiEncryptedMessage(
|
||||
algorithmName = content.algorithmName,
|
||||
senderKey = content.senderKey,
|
||||
cipherText = content.cipherText,
|
||||
sessionId = content.sessionId,
|
||||
deviceId = content.deviceId,
|
||||
)
|
||||
when (relatesTo) {
|
||||
null -> apiEncryptedMessage
|
||||
else -> apiEncryptedMessage.copy(relatesTo = relatesTo)
|
||||
}
|
||||
|
||||
})
|
||||
)
|
||||
|
||||
internal fun sendRequest(roomId: RoomId, eventType: EventType, content: EventMessage) = httpRequest<ApiSendResponse>(
|
||||
|
@ -52,3 +72,13 @@ internal fun uploadRequest(stream: InputStream, contentLength: Long, filename: S
|
|||
)
|
||||
|
||||
fun txId() = "local.${UUID.randomUUID()}"
|
||||
|
||||
@Serializable
|
||||
data class ApiEncryptedMessage(
|
||||
@SerialName("algorithm") val algorithmName: AlgorithmName,
|
||||
@SerialName("sender_key") val senderKey: String,
|
||||
@SerialName("ciphertext") val cipherText: CipherText,
|
||||
@SerialName("session_id") val sessionId: SessionId,
|
||||
@SerialName("device_id") val deviceId: DeviceId,
|
||||
@SerialName("m.relates_to") val relatesTo: ApiMessage.RelatesTo? = null,
|
||||
)
|
Loading…
Reference in New Issue