From 1bd77254ad7fce589d962bc3cadc329fe25d1552 Mon Sep 17 00:00:00 2001 From: Infi Date: Mon, 12 Jun 2023 20:04:51 +0200 Subject: [PATCH] feat: extremely rudimentary editing prone to breakage Signed-off-by: Infi --- .../main/java/chat/revolt/api/RevoltAPI.kt | 7 +++- .../chat/revolt/api/routes/channel/Channel.kt | 33 +++++++++++++++++ .../java/chat/revolt/callbacks/UiCallbacks.kt | 5 +++ .../revolt/components/chat/MessageField.kt | 24 ++++++++++--- .../chat/views/channel/ChannelScreen.kt | 4 ++- .../views/channel/ChannelScreenViewModel.kt | 36 +++++++++++++++++++ .../chat/revolt/sheets/MessageContextSheet.kt | 7 +--- 7 files changed, 104 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/chat/revolt/api/RevoltAPI.kt b/app/src/main/java/chat/revolt/api/RevoltAPI.kt index d0763333..9a0e195a 100644 --- a/app/src/main/java/chat/revolt/api/RevoltAPI.kt +++ b/app/src/main/java/chat/revolt/api/RevoltAPI.kt @@ -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) diff --git a/app/src/main/java/chat/revolt/api/routes/channel/Channel.kt b/app/src/main/java/chat/revolt/api/routes/channel/Channel.kt index 94d7e9e8..a2086432 100644 --- a/app/src/main/java/chat/revolt/api/routes/channel/Channel.kt +++ b/app/src/main/java/chat/revolt/api/routes/channel/Channel.kt @@ -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?, ) +@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) diff --git a/app/src/main/java/chat/revolt/callbacks/UiCallbacks.kt b/app/src/main/java/chat/revolt/callbacks/UiCallbacks.kt index 4e578ad3..7e3fdc52 100644 --- a/app/src/main/java/chat/revolt/callbacks/UiCallbacks.kt +++ b/app/src/main/java/chat/revolt/callbacks/UiCallbacks.kt @@ -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)) + } } \ No newline at end of file diff --git a/app/src/main/java/chat/revolt/components/chat/MessageField.kt b/app/src/main/java/chat/revolt/components/chat/MessageField.kt index 279594ff..75b0b09b 100644 --- a/app/src/main/java/chat/revolt/components/chat/MessageField.kt +++ b/app/src/main/java/chat/revolt/components/chat/MessageField.kt @@ -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 diff --git a/app/src/main/java/chat/revolt/screens/chat/views/channel/ChannelScreen.kt b/app/src/main/java/chat/revolt/screens/chat/views/channel/ChannelScreen.kt index 920b7060..50c7aded 100644 --- a/app/src/main/java/chat/revolt/screens/chat/views/channel/ChannelScreen.kt +++ b/app/src/main/java/chat/revolt/screens/chat/views/channel/ChannelScreen.kt @@ -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, ) } } diff --git a/app/src/main/java/chat/revolt/screens/chat/views/channel/ChannelScreenViewModel.kt b/app/src/main/java/chat/revolt/screens/chat/views/channel/ChannelScreenViewModel.kt index 2d72cbc7..cf6a2ff9 100644 --- a/app/src/main/java/chat/revolt/screens/chat/views/channel/ChannelScreenViewModel.kt +++ b/app/src/main/java/chat/revolt/screens/chat/views/channel/ChannelScreenViewModel.kt @@ -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(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 = renderableMessages) { val groupedMessages = mutableMapOf() @@ -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 = "" + } } diff --git a/app/src/main/java/chat/revolt/sheets/MessageContextSheet.kt b/app/src/main/java/chat/revolt/sheets/MessageContextSheet.kt index bd687296..9b7e3d3d 100644 --- a/app/src/main/java/chat/revolt/sheets/MessageContextSheet.kt +++ b/app/src/main/java/chat/revolt/sheets/MessageContextSheet.kt @@ -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() } }