feat: extremely rudimentary editing prone to breakage
Signed-off-by: Infi <wingit@geist.ga>
This commit is contained in:
parent
21c2556eb1
commit
1bd77254ad
|
|
@ -33,6 +33,7 @@ import kotlinx.coroutines.launch
|
|||
import kotlinx.coroutines.newSingleThreadContext
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.serialization.ExperimentalSerializationApi
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.Json
|
||||
import chat.revolt.api.schemas.Channel as ChannelSchema
|
||||
|
|
@ -51,7 +52,11 @@ fun buildUserAgent(accessMethod: String = "Ktor"): String {
|
|||
|
||||
private const val BACKEND_IS_STABLE = false
|
||||
|
||||
val RevoltJson = Json { ignoreUnknownKeys = true }
|
||||
@OptIn(ExperimentalSerializationApi::class)
|
||||
val RevoltJson = Json {
|
||||
ignoreUnknownKeys = true
|
||||
explicitNulls = false
|
||||
}
|
||||
|
||||
val RevoltHttp = HttpClient(OkHttp) {
|
||||
install(DefaultRequest)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package chat.revolt.api.routes.channel
|
||||
|
||||
import chat.revolt.api.RevoltAPI
|
||||
import chat.revolt.api.RevoltError
|
||||
import chat.revolt.api.RevoltHttp
|
||||
import chat.revolt.api.RevoltJson
|
||||
import chat.revolt.api.internals.ULID
|
||||
|
|
@ -9,12 +10,14 @@ import chat.revolt.api.schemas.Message
|
|||
import chat.revolt.api.schemas.MessagesInChannel
|
||||
import io.ktor.client.request.get
|
||||
import io.ktor.client.request.parameter
|
||||
import io.ktor.client.request.patch
|
||||
import io.ktor.client.request.post
|
||||
import io.ktor.client.request.put
|
||||
import io.ktor.client.request.setBody
|
||||
import io.ktor.client.statement.bodyAsText
|
||||
import io.ktor.http.ContentType
|
||||
import io.ktor.http.contentType
|
||||
import kotlinx.serialization.SerializationException
|
||||
import kotlinx.serialization.builtins.ListSerializer
|
||||
|
||||
suspend fun fetchMessagesFromChannel(
|
||||
|
|
@ -72,6 +75,11 @@ data class SendMessageBody(
|
|||
val attachments: List<String>?,
|
||||
)
|
||||
|
||||
@kotlinx.serialization.Serializable
|
||||
data class EditMessageBody(
|
||||
val content: String?,
|
||||
)
|
||||
|
||||
suspend fun sendMessage(
|
||||
channelId: String,
|
||||
content: String,
|
||||
|
|
@ -97,6 +105,31 @@ suspend fun sendMessage(
|
|||
return response
|
||||
}
|
||||
|
||||
suspend fun editMessage(
|
||||
channelId: String,
|
||||
messageId: String,
|
||||
newContent: String? = null,
|
||||
) {
|
||||
val response = RevoltHttp.patch("/channels/$channelId/messages/$messageId") {
|
||||
headers.append(RevoltAPI.TOKEN_HEADER_NAME, RevoltAPI.sessionToken)
|
||||
|
||||
contentType(ContentType.Application.Json)
|
||||
setBody(
|
||||
EditMessageBody(
|
||||
content = newContent
|
||||
)
|
||||
)
|
||||
}
|
||||
.bodyAsText()
|
||||
|
||||
try {
|
||||
val error = RevoltJson.decodeFromString(RevoltError.serializer(), response)
|
||||
throw Error(error.type)
|
||||
} catch (e: SerializationException) {
|
||||
// Not an error
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun ackChannel(channelId: String, messageId: String = ULID.makeNext()) {
|
||||
RevoltHttp.put("/channels/$channelId/ack/$messageId") {
|
||||
headers.append(RevoltAPI.TOKEN_HEADER_NAME, RevoltAPI.sessionToken)
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import kotlinx.coroutines.flow.MutableSharedFlow
|
|||
|
||||
sealed class UiCallback {
|
||||
data class ReplyToMessage(val messageId: String) : UiCallback()
|
||||
data class EditMessage(val messageId: String) : UiCallback()
|
||||
}
|
||||
|
||||
object UiCallbacks {
|
||||
|
|
@ -12,4 +13,8 @@ object UiCallbacks {
|
|||
suspend fun replyToMessage(messageId: String) {
|
||||
uiCallbackFlow.emit(UiCallback.ReplyToMessage(messageId))
|
||||
}
|
||||
|
||||
suspend fun editMessage(messageId: String) {
|
||||
uiCallbackFlow.emit(UiCallback.EditMessage(messageId))
|
||||
}
|
||||
}
|
||||
|
|
@ -18,6 +18,8 @@ import androidx.compose.foundation.text.KeyboardActions
|
|||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material.icons.filled.Close
|
||||
import androidx.compose.material.icons.filled.Edit
|
||||
import androidx.compose.material.icons.filled.Send
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
|
|
@ -57,6 +59,8 @@ fun MessageField(
|
|||
modifier: Modifier = Modifier,
|
||||
forceSendButton: Boolean = false,
|
||||
disabled: Boolean = false,
|
||||
editMode: Boolean = false,
|
||||
cancelEdit: () -> Unit = {},
|
||||
) {
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
val placeholderResource = when (channelType) {
|
||||
|
|
@ -73,6 +77,7 @@ fun MessageField(
|
|||
modifier = modifier
|
||||
.background(MaterialTheme.colorScheme.surfaceColorAtElevation(1.dp))
|
||||
) {
|
||||
|
||||
BasicTextField(
|
||||
value = messageContent,
|
||||
onValueChange = onMessageContentChange,
|
||||
|
|
@ -118,15 +123,23 @@ fun MessageField(
|
|||
contentPadding = PaddingValues(16.dp),
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
Icons.Default.Add,
|
||||
when {
|
||||
editMode -> Icons.Default.Close
|
||||
else -> Icons.Default.Add
|
||||
},
|
||||
tint = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.5f),
|
||||
contentDescription = stringResource(id = R.string.add_attachment_alt),
|
||||
modifier = Modifier
|
||||
.clip(CircleShape)
|
||||
.size(32.dp)
|
||||
.clickable {
|
||||
focusRequester.freeFocus() // hide keyboard because it's annoying
|
||||
onAddAttachment()
|
||||
when {
|
||||
editMode -> cancelEdit()
|
||||
else -> {
|
||||
focusRequester.freeFocus() // hide keyboard because it's annoying
|
||||
onAddAttachment()
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(4.dp)
|
||||
.testTag("add_attachment")
|
||||
|
|
@ -143,7 +156,10 @@ fun MessageField(
|
|||
targetOffsetY = { it }
|
||||
) + fadeOut(animationSpec = RevoltTweenFloat)) {
|
||||
Icon(
|
||||
Icons.Default.Send,
|
||||
when {
|
||||
editMode -> Icons.Default.Edit
|
||||
else -> Icons.Default.Send
|
||||
},
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
contentDescription = stringResource(id = R.string.send_alt),
|
||||
modifier = Modifier
|
||||
|
|
|
|||
|
|
@ -404,7 +404,9 @@ fun ChannelScreen(
|
|||
R.string.unknown
|
||||
),
|
||||
forceSendButton = viewModel.pendingAttachments.isNotEmpty(),
|
||||
disabled = viewModel.pendingAttachments.isNotEmpty() && viewModel.isSendingMessage
|
||||
disabled = viewModel.pendingAttachments.isNotEmpty() && viewModel.isSendingMessage,
|
||||
editMode = viewModel.editingMessage != null,
|
||||
cancelEdit = viewModel::cancelEditingMessage,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import chat.revolt.api.realtime.frames.receivable.MessageFrame
|
|||
import chat.revolt.api.realtime.frames.receivable.MessageUpdateFrame
|
||||
import chat.revolt.api.routes.channel.SendMessageReply
|
||||
import chat.revolt.api.routes.channel.ackChannel
|
||||
import chat.revolt.api.routes.channel.editMessage
|
||||
import chat.revolt.api.routes.channel.fetchMessagesFromChannel
|
||||
import chat.revolt.api.routes.channel.fetchSingleChannel
|
||||
import chat.revolt.api.routes.channel.sendMessage
|
||||
|
|
@ -58,6 +59,8 @@ class ChannelScreenViewModel : ViewModel() {
|
|||
|
||||
var pendingUploadProgress by mutableFloatStateOf(0f)
|
||||
|
||||
var editingMessage by mutableStateOf<String?>(null)
|
||||
|
||||
private fun popAttachmentBatch() {
|
||||
pendingAttachments =
|
||||
pendingAttachments.drop(MAX_ATTACHMENTS_PER_MESSAGE).toMutableStateList()
|
||||
|
|
@ -141,6 +144,11 @@ class ChannelScreenViewModel : ViewModel() {
|
|||
}
|
||||
|
||||
fun sendPendingMessage() {
|
||||
if (editingMessage != null) {
|
||||
editPendingMessage()
|
||||
return
|
||||
}
|
||||
|
||||
isSendingMessage = true
|
||||
|
||||
viewModelScope.launch {
|
||||
|
|
@ -184,6 +192,21 @@ class ChannelScreenViewModel : ViewModel() {
|
|||
}
|
||||
}
|
||||
|
||||
private fun editPendingMessage() {
|
||||
isSendingMessage = true
|
||||
|
||||
viewModelScope.launch {
|
||||
editMessage(
|
||||
channelId = activeChannel!!.id!!,
|
||||
messageId = editingMessage!!,
|
||||
newContent = pendingMessageContent.trimIndent()
|
||||
)
|
||||
|
||||
pendingMessageContent = ""
|
||||
isSendingMessage = false
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun regroupMessages(newMessages: List<Message> = renderableMessages) {
|
||||
val groupedMessages = mutableMapOf<String, Message>()
|
||||
|
||||
|
|
@ -308,6 +331,14 @@ class ChannelScreenViewModel : ViewModel() {
|
|||
)
|
||||
)
|
||||
}
|
||||
|
||||
is UiCallback.EditMessage -> {
|
||||
editingMessage = it.messageId
|
||||
val message = renderableMessages.find { msg ->
|
||||
msg.id == it.messageId
|
||||
} ?: return@onEach
|
||||
pendingMessageContent = message.content ?: ""
|
||||
}
|
||||
}
|
||||
}.catch {
|
||||
Log.e("ChannelScreen", "Failed to receive UI callback", it)
|
||||
|
|
@ -347,4 +378,9 @@ class ChannelScreenViewModel : ViewModel() {
|
|||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun cancelEditingMessage() {
|
||||
editingMessage = null
|
||||
pendingMessageContent = ""
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -285,13 +285,8 @@ fun MessageContextSheet(
|
|||
)
|
||||
},
|
||||
) {
|
||||
Toast.makeText(
|
||||
context,
|
||||
context.getString(R.string.comingsoon_toast),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
|
||||
coroutineScope.launch {
|
||||
UiCallbacks.editMessage(messageId)
|
||||
onHideSheet()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue