Fix public chat message sending and Theme switches disappearing

This commit is contained in:
2026-04-03 16:18:04 +03:00
Unverified
parent 58dc1223c2
commit 8cdf5b2167
4 changed files with 75 additions and 22 deletions
@@ -231,13 +231,11 @@ fun ChatScreen(
"newMessage", "messageEdited", "messageDeleted",
"dmNew", "dmEdited", "dmDeleted",
"typing", "stopTyping", "dmTyping", "stopDmTyping", "suspended", "account_deleted" -> {
Logger.d("ChatScreen", "Launching handleWebSocketMessage for ${update.type}")
scope.launch {
try {
panel.handleWebSocketMessage(wsMessage)
} catch (e: Exception) {
Logger.e("ChatScreen", "Error handling WebSocket message: ${e.message}", e)
}
Logger.d("ChatScreen", "handleWebSocketMessage for ${update.type}")
try {
panel.handleWebSocketMessage(wsMessage)
} catch (e: Exception) {
Logger.e("ChatScreen", "Error handling WebSocket message: ${e.message}", e)
}
}
}
@@ -259,6 +257,11 @@ fun ChatScreen(
panel.handleWebSocketMessage(message)
}
}
"sendMessage" -> {
scope.launch {
panel.handleWebSocketMessage(message)
}
}
else -> {
Logger.d("ChatScreen", "Unhandled top-level WebSocket message type: ${message.type}")
}
@@ -8,6 +8,7 @@ import ru.fromchat.api.ApiClient
import ru.fromchat.api.Message
import ru.fromchat.api.MessageDeletedData
import ru.fromchat.api.ReactionUpdateData
import ru.fromchat.api.SendMessageResponse
import ru.fromchat.api.TypingUpdateData
import ru.fromchat.api.WebSocketMessage
import ru.fromchat.api.WebSocketUpdatesData
@@ -65,6 +66,42 @@ class PublicChatPanel(
}
}
/**
* Server often omits [Message.client_message_id] on broadcast [newMessage] / [SendMessageResponse.message].
* Match the oldest pending optimistic row (same user, text, reply) and replace it; otherwise append.
*/
private suspend fun confirmIncomingOwnMessageOrAdd(newMsg: Message) {
val uid = currentUserId
if (uid == null) {
addMessage(newMsg)
return
}
if (newMsg.user_id != uid) {
addMessage(newMsg)
return
}
if (newMsg.client_message_id != null) {
handleMessageConfirmed(newMsg.client_message_id, newMsg)
return
}
if (newMsg.id <= 0) {
addMessage(newMsg)
return
}
val pending = _state.messages.firstOrNull { msg ->
msg.id < 0 &&
msg.user_id == uid &&
msg.client_message_id != null &&
msg.content == newMsg.content &&
msg.reply_to?.id == newMsg.reply_to?.id
}
if (pending?.client_message_id != null) {
handleMessageConfirmed(pending.client_message_id, newMsg)
} else {
addMessage(newMsg)
}
}
override suspend fun sendMessage(content: String, replyToId: Int?, clientMessageId: String?) {
ApiClient.sendMessage(content, replyToId, clientMessageId)
}
@@ -185,12 +222,18 @@ class PublicChatPanel(
val data = updateMessage.data ?: return
val newMsg = json.decodeFromJsonElement(Message.serializer(), data)
Logger.d("PublicChatPanel", "New message received: id=${newMsg.id}, content=${newMsg.content.take(50)}")
if (newMsg.client_message_id != null && newMsg.user_id == currentUserId) {
handleMessageConfirmed(newMsg.client_message_id, newMsg)
} else {
addMessage(newMsg)
}
confirmIncomingOwnMessageOrAdd(newMsg)
}
"sendMessage" -> {
val data = updateMessage.data ?: return
val resp = json.decodeFromJsonElement(SendMessageResponse.serializer(), data)
if (!resp.status.equals("success", ignoreCase = true)) return
val confirmed = resp.message
Logger.d(
"PublicChatPanel",
"sendMessage ack: id=${confirmed.id}, clientId=${confirmed.client_message_id}"
)
confirmIncomingOwnMessageOrAdd(confirmed)
}
"messageEdited" -> {
val data = updateMessage.data ?: return
@@ -111,6 +111,7 @@ fun SettingsTab(
Settings.materialYou && materialYouAvailable
)
}
var themeChipIndex by remember { mutableIntStateOf(Settings.theme.ordinal) }
Category(Modifier.padding(top = 16.dp)) {
// Material You
@@ -146,7 +147,6 @@ fun SettingsTab(
},
bottomContent = {
FlowRow(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
var selectedIndex by remember { mutableIntStateOf(Settings.theme.ordinal) }
val options = listOf(
stringResource(Res.string.as_system),
stringResource(Res.string.light),
@@ -155,11 +155,11 @@ fun SettingsTab(
options.forEachIndexed { index, label ->
FilterChip(
onClick = {
selectedIndex = index
themeChipIndex = index
Settings.theme = Theme.entries[index]
theme = Theme.entries[index]
},
selected = index == selectedIndex,
selected = index == themeChipIndex,
leadingIcon = {
if (index == 0) {
Spacer(Modifier.width(16.dp))
@@ -111,7 +111,7 @@ fun ListItem(
onClick: (() -> Unit)? = null,
bodyOnClick: (() -> Unit)? = null,
leadingAndBodyShared: Boolean = false,
bottomContent: (@Composable ConstraintLayoutScope.() -> Unit)? = null
bottomContent: (@Composable () -> Unit)? = null
) {
Column {
@Composable
@@ -244,7 +244,7 @@ fun ListItem(
)
}
if (bottomContent != null) {
ConstraintLayout(
Box(
modifier = Modifier
.constrainAs(btm) {
bottom link parent.bottom
@@ -252,9 +252,10 @@ fun ListItem(
right link parent.right
width = Dimension.fillToConstraints
}
.padding(start = 16.dp, end = 16.dp, bottom = 12.dp),
content = bottomContent
)
.padding(start = 16.dp, end = 16.dp, bottom = 12.dp)
) {
bottomContent()
}
}
}
}
@@ -304,11 +305,17 @@ inline fun SwitchListItem(
supportingText = supportingText,
leadingContent = leadingContent,
trailingContent = {
val (sw) = createRefs()
Switch(
checked = checked,
onCheckedChange = onCheckedChange,
interactionSource = interactionSource,
enabled = enabled
enabled = enabled,
modifier = Modifier.constrainAs(sw) {
top link parent.top
bottom link parent.bottom
right link parent.right
}
)
},
divider = divider,