adding support for sending replies
This commit is contained in:
parent
bccf947508
commit
81f15c4fca
|
@ -109,7 +109,7 @@ internal class MessengerViewModel(
|
||||||
when (val composerState = state.composerState) {
|
when (val composerState = state.composerState) {
|
||||||
is ComposerState.Text -> {
|
is ComposerState.Text -> {
|
||||||
val copy = composerState.copy()
|
val copy = composerState.copy()
|
||||||
updateState { copy(composerState = composerState.copy(value = "")) }
|
updateState { copy(composerState = composerState.copy(value = "", reply = null)) }
|
||||||
|
|
||||||
state.roomState.takeIfContent()?.let { content ->
|
state.roomState.takeIfContent()?.let { content ->
|
||||||
val roomState = content.roomState
|
val roomState = content.roomState
|
||||||
|
@ -121,6 +121,18 @@ internal class MessengerViewModel(
|
||||||
sendEncrypted = roomState.roomOverview.isEncrypted,
|
sendEncrypted = roomState.roomOverview.isEncrypted,
|
||||||
localId = localIdFactory.create(),
|
localId = localIdFactory.create(),
|
||||||
timestampUtc = clock.millis(),
|
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
|
package app.dapk.st.matrix.message
|
||||||
|
|
||||||
import app.dapk.st.core.Base64
|
|
||||||
import app.dapk.st.matrix.*
|
import app.dapk.st.matrix.*
|
||||||
import app.dapk.st.matrix.common.AlgorithmName
|
import app.dapk.st.matrix.common.*
|
||||||
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.message.internal.DefaultMessageService
|
import app.dapk.st.matrix.message.internal.DefaultMessageService
|
||||||
import app.dapk.st.matrix.message.internal.ImageContentReader
|
import app.dapk.st.matrix.message.internal.ImageContentReader
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
@ -43,7 +39,17 @@ interface MessageService : MatrixService {
|
||||||
@SerialName("room_id") val roomId: RoomId,
|
@SerialName("room_id") val roomId: RoomId,
|
||||||
@SerialName("local_id") val localId: String,
|
@SerialName("local_id") val localId: String,
|
||||||
@SerialName("timestamp") val timestampUtc: Long,
|
@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
|
@Serializable
|
||||||
@SerialName("image_message")
|
@SerialName("image_message")
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package app.dapk.st.matrix.message.internal
|
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.MessageType
|
||||||
import app.dapk.st.matrix.common.MxUrl
|
import app.dapk.st.matrix.common.MxUrl
|
||||||
import app.dapk.st.matrix.common.RoomId
|
import app.dapk.st.matrix.common.RoomId
|
||||||
|
@ -20,8 +21,26 @@ sealed class ApiMessage {
|
||||||
@Serializable
|
@Serializable
|
||||||
data class TextContent(
|
data class TextContent(
|
||||||
@SerialName("body") val body: String,
|
@SerialName("body") val body: String,
|
||||||
@SerialName("msgtype") val type: String = MessageType.TEXT.value,
|
@SerialName("m.relates_to") val relatesTo: RelatesTo? = null,
|
||||||
) : ApiMessageContent
|
@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
|
@Serializable
|
||||||
|
|
|
@ -1,9 +1,6 @@
|
||||||
package app.dapk.st.matrix.message.internal
|
package app.dapk.st.matrix.message.internal
|
||||||
|
|
||||||
import app.dapk.st.matrix.common.EventId
|
import app.dapk.st.matrix.common.*
|
||||||
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.http.MatrixHttpClient
|
import app.dapk.st.matrix.http.MatrixHttpClient
|
||||||
import app.dapk.st.matrix.http.MatrixHttpClient.HttpRequest
|
import app.dapk.st.matrix.http.MatrixHttpClient.HttpRequest
|
||||||
import app.dapk.st.matrix.message.ApiSendResponse
|
import app.dapk.st.matrix.message.ApiSendResponse
|
||||||
|
@ -37,7 +34,7 @@ internal class SendMessageUseCase(
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun ApiMessageMapper.textMessageRequest(message: Message.TextMessage): HttpRequest<ApiSendResponse> {
|
private suspend fun ApiMessageMapper.textMessageRequest(message: Message.TextMessage): HttpRequest<ApiSendResponse> {
|
||||||
val contents = message.toContents()
|
val contents = message.toContents(message.reply)
|
||||||
return when (message.sendEncrypted) {
|
return when (message.sendEncrypted) {
|
||||||
true -> sendRequest(
|
true -> sendRequest(
|
||||||
roomId = message.roomId,
|
roomId = message.roomId,
|
||||||
|
@ -49,6 +46,7 @@ internal class SendMessageUseCase(
|
||||||
contents.toMessageJson(message.roomId)
|
contents.toMessageJson(message.roomId)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
relatesTo = contents.relatesTo
|
||||||
)
|
)
|
||||||
|
|
||||||
false -> sendRequest(
|
false -> sendRequest(
|
||||||
|
@ -115,6 +113,7 @@ internal class SendMessageUseCase(
|
||||||
eventType = EventType.ENCRYPTED,
|
eventType = EventType.ENCRYPTED,
|
||||||
txId = message.localId,
|
txId = message.localId,
|
||||||
content = messageEncrypter.encrypt(MessageEncrypter.ClearMessagePayload(message.roomId, json)),
|
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 {
|
class ApiMessageMapper {
|
||||||
|
|
||||||
fun Message.TextMessage.toContents() = ApiMessage.TextMessage.TextContent(
|
fun Message.TextMessage.toContents(reply: Message.TextMessage.Reply?) = when (reply) {
|
||||||
this.content.body,
|
null -> ApiMessage.TextMessage.TextContent(
|
||||||
this.content.type,
|
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(
|
fun ApiMessage.TextMessage.TextContent.toMessageJson(roomId: RoomId) = JsonString(
|
||||||
MatrixHttpClient.jsonWithDefaults.encodeToString(
|
MatrixHttpClient.jsonWithDefaults.encodeToString(
|
||||||
ApiMessage.TextMessage.serializer(),
|
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
|
package app.dapk.st.matrix.message.internal
|
||||||
|
|
||||||
import app.dapk.st.matrix.common.EventType
|
import app.dapk.st.matrix.common.*
|
||||||
import app.dapk.st.matrix.common.RoomId
|
|
||||||
import app.dapk.st.matrix.http.MatrixHttpClient
|
import app.dapk.st.matrix.http.MatrixHttpClient
|
||||||
import app.dapk.st.matrix.http.MatrixHttpClient.HttpRequest.Companion.httpRequest
|
import app.dapk.st.matrix.http.MatrixHttpClient.HttpRequest.Companion.httpRequest
|
||||||
import app.dapk.st.matrix.http.jsonBody
|
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.*
|
||||||
import io.ktor.http.content.*
|
import io.ktor.http.content.*
|
||||||
import io.ktor.utils.io.jvm.javaio.*
|
import io.ktor.utils.io.jvm.javaio.*
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.util.*
|
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}",
|
path = "_matrix/client/r0/rooms/${roomId.value}/send/${eventType.value}/${txId}",
|
||||||
method = MatrixHttpClient.Method.PUT,
|
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>(
|
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()}"
|
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