mirror of
https://github.com/fromchat-messenger/app.git
synced 2026-09-22 19:15:05 +03:00
Fix public chat message sending and Theme switches disappearing
This commit is contained in:
@@ -231,13 +231,11 @@ fun ChatScreen(
|
|||||||
"newMessage", "messageEdited", "messageDeleted",
|
"newMessage", "messageEdited", "messageDeleted",
|
||||||
"dmNew", "dmEdited", "dmDeleted",
|
"dmNew", "dmEdited", "dmDeleted",
|
||||||
"typing", "stopTyping", "dmTyping", "stopDmTyping", "suspended", "account_deleted" -> {
|
"typing", "stopTyping", "dmTyping", "stopDmTyping", "suspended", "account_deleted" -> {
|
||||||
Logger.d("ChatScreen", "Launching handleWebSocketMessage for ${update.type}")
|
Logger.d("ChatScreen", "handleWebSocketMessage for ${update.type}")
|
||||||
scope.launch {
|
try {
|
||||||
try {
|
panel.handleWebSocketMessage(wsMessage)
|
||||||
panel.handleWebSocketMessage(wsMessage)
|
} catch (e: Exception) {
|
||||||
} catch (e: Exception) {
|
Logger.e("ChatScreen", "Error handling WebSocket message: ${e.message}", e)
|
||||||
Logger.e("ChatScreen", "Error handling WebSocket message: ${e.message}", e)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -259,6 +257,11 @@ fun ChatScreen(
|
|||||||
panel.handleWebSocketMessage(message)
|
panel.handleWebSocketMessage(message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
"sendMessage" -> {
|
||||||
|
scope.launch {
|
||||||
|
panel.handleWebSocketMessage(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
else -> {
|
else -> {
|
||||||
Logger.d("ChatScreen", "Unhandled top-level WebSocket message type: ${message.type}")
|
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.Message
|
||||||
import ru.fromchat.api.MessageDeletedData
|
import ru.fromchat.api.MessageDeletedData
|
||||||
import ru.fromchat.api.ReactionUpdateData
|
import ru.fromchat.api.ReactionUpdateData
|
||||||
|
import ru.fromchat.api.SendMessageResponse
|
||||||
import ru.fromchat.api.TypingUpdateData
|
import ru.fromchat.api.TypingUpdateData
|
||||||
import ru.fromchat.api.WebSocketMessage
|
import ru.fromchat.api.WebSocketMessage
|
||||||
import ru.fromchat.api.WebSocketUpdatesData
|
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?) {
|
override suspend fun sendMessage(content: String, replyToId: Int?, clientMessageId: String?) {
|
||||||
ApiClient.sendMessage(content, replyToId, clientMessageId)
|
ApiClient.sendMessage(content, replyToId, clientMessageId)
|
||||||
}
|
}
|
||||||
@@ -185,12 +222,18 @@ class PublicChatPanel(
|
|||||||
val data = updateMessage.data ?: return
|
val data = updateMessage.data ?: return
|
||||||
val newMsg = json.decodeFromJsonElement(Message.serializer(), data)
|
val newMsg = json.decodeFromJsonElement(Message.serializer(), data)
|
||||||
Logger.d("PublicChatPanel", "New message received: id=${newMsg.id}, content=${newMsg.content.take(50)}")
|
Logger.d("PublicChatPanel", "New message received: id=${newMsg.id}, content=${newMsg.content.take(50)}")
|
||||||
|
confirmIncomingOwnMessageOrAdd(newMsg)
|
||||||
if (newMsg.client_message_id != null && newMsg.user_id == currentUserId) {
|
}
|
||||||
handleMessageConfirmed(newMsg.client_message_id, newMsg)
|
"sendMessage" -> {
|
||||||
} else {
|
val data = updateMessage.data ?: return
|
||||||
addMessage(newMsg)
|
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" -> {
|
"messageEdited" -> {
|
||||||
val data = updateMessage.data ?: return
|
val data = updateMessage.data ?: return
|
||||||
|
|||||||
@@ -111,6 +111,7 @@ fun SettingsTab(
|
|||||||
Settings.materialYou && materialYouAvailable
|
Settings.materialYou && materialYouAvailable
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
var themeChipIndex by remember { mutableIntStateOf(Settings.theme.ordinal) }
|
||||||
|
|
||||||
Category(Modifier.padding(top = 16.dp)) {
|
Category(Modifier.padding(top = 16.dp)) {
|
||||||
// Material You
|
// Material You
|
||||||
@@ -146,7 +147,6 @@ fun SettingsTab(
|
|||||||
},
|
},
|
||||||
bottomContent = {
|
bottomContent = {
|
||||||
FlowRow(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
FlowRow(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||||
var selectedIndex by remember { mutableIntStateOf(Settings.theme.ordinal) }
|
|
||||||
val options = listOf(
|
val options = listOf(
|
||||||
stringResource(Res.string.as_system),
|
stringResource(Res.string.as_system),
|
||||||
stringResource(Res.string.light),
|
stringResource(Res.string.light),
|
||||||
@@ -155,11 +155,11 @@ fun SettingsTab(
|
|||||||
options.forEachIndexed { index, label ->
|
options.forEachIndexed { index, label ->
|
||||||
FilterChip(
|
FilterChip(
|
||||||
onClick = {
|
onClick = {
|
||||||
selectedIndex = index
|
themeChipIndex = index
|
||||||
Settings.theme = Theme.entries[index]
|
Settings.theme = Theme.entries[index]
|
||||||
theme = Theme.entries[index]
|
theme = Theme.entries[index]
|
||||||
},
|
},
|
||||||
selected = index == selectedIndex,
|
selected = index == themeChipIndex,
|
||||||
leadingIcon = {
|
leadingIcon = {
|
||||||
if (index == 0) {
|
if (index == 0) {
|
||||||
Spacer(Modifier.width(16.dp))
|
Spacer(Modifier.width(16.dp))
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ fun ListItem(
|
|||||||
onClick: (() -> Unit)? = null,
|
onClick: (() -> Unit)? = null,
|
||||||
bodyOnClick: (() -> Unit)? = null,
|
bodyOnClick: (() -> Unit)? = null,
|
||||||
leadingAndBodyShared: Boolean = false,
|
leadingAndBodyShared: Boolean = false,
|
||||||
bottomContent: (@Composable ConstraintLayoutScope.() -> Unit)? = null
|
bottomContent: (@Composable () -> Unit)? = null
|
||||||
) {
|
) {
|
||||||
Column {
|
Column {
|
||||||
@Composable
|
@Composable
|
||||||
@@ -244,7 +244,7 @@ fun ListItem(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (bottomContent != null) {
|
if (bottomContent != null) {
|
||||||
ConstraintLayout(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.constrainAs(btm) {
|
.constrainAs(btm) {
|
||||||
bottom link parent.bottom
|
bottom link parent.bottom
|
||||||
@@ -252,9 +252,10 @@ fun ListItem(
|
|||||||
right link parent.right
|
right link parent.right
|
||||||
width = Dimension.fillToConstraints
|
width = Dimension.fillToConstraints
|
||||||
}
|
}
|
||||||
.padding(start = 16.dp, end = 16.dp, bottom = 12.dp),
|
.padding(start = 16.dp, end = 16.dp, bottom = 12.dp)
|
||||||
content = bottomContent
|
) {
|
||||||
)
|
bottomContent()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -304,11 +305,17 @@ inline fun SwitchListItem(
|
|||||||
supportingText = supportingText,
|
supportingText = supportingText,
|
||||||
leadingContent = leadingContent,
|
leadingContent = leadingContent,
|
||||||
trailingContent = {
|
trailingContent = {
|
||||||
|
val (sw) = createRefs()
|
||||||
Switch(
|
Switch(
|
||||||
checked = checked,
|
checked = checked,
|
||||||
onCheckedChange = onCheckedChange,
|
onCheckedChange = onCheckedChange,
|
||||||
interactionSource = interactionSource,
|
interactionSource = interactionSource,
|
||||||
enabled = enabled
|
enabled = enabled,
|
||||||
|
modifier = Modifier.constrainAs(sw) {
|
||||||
|
top link parent.top
|
||||||
|
bottom link parent.bottom
|
||||||
|
right link parent.right
|
||||||
|
}
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
divider = divider,
|
divider = divider,
|
||||||
|
|||||||
Reference in New Issue
Block a user