Fix the IME padding

Signed-off-by: denis0001-dev <denis0001.dev@ya.ru>
This commit is contained in:
2025-09-12 21:55:16 +03:00
Unverified
parent c1fbb61159
commit 1334da7940
3 changed files with 62 additions and 43 deletions
@@ -13,7 +13,6 @@ import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.TimeoutCancellationException import kotlinx.coroutines.TimeoutCancellationException
import kotlinx.coroutines.cancel import kotlinx.coroutines.cancel
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asSharedFlow
@@ -22,6 +21,7 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withTimeout import kotlinx.coroutines.withTimeout
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import ru.fromchat.API_HOST import ru.fromchat.API_HOST
import kotlin.coroutines.suspendCoroutine
object WebSocketManager { object WebSocketManager {
// Config // Config
@@ -32,10 +32,14 @@ object WebSocketManager {
val messages = _messages.asSharedFlow() val messages = _messages.asSharedFlow()
@Volatile @Volatile
private var globalHandler: ((WebSocketMessage) -> Unit)? = null private var globalHandlers = mutableListOf<((WebSocketMessage) -> Unit)>()
fun setGlobalMessageHandler(handler: ((WebSocketMessage) -> Unit)?) { fun addGlobalMessageHandler(handler: ((WebSocketMessage) -> Unit)) {
globalHandler = handler globalHandlers += handler
}
fun removeGlobalMessageHandler(handler: ((WebSocketMessage) -> Unit)) {
globalHandlers -= handler
} }
// State // State
@@ -69,7 +73,7 @@ object WebSocketManager {
Log.d("WebSocketManager", "Received payload: $text") Log.d("WebSocketManager", "Received payload: $text")
try { try {
val msg = json.decodeFromString<WebSocketMessage>(text) val msg = json.decodeFromString<WebSocketMessage>(text)
globalHandler?.invoke(msg) globalHandlers.forEach { it(msg) }
_messages.emit(msg) _messages.emit(msg)
} catch (e: Throwable) { } catch (e: Throwable) {
Log.w("WebSocketManager", "Received malformed payload:", e) Log.w("WebSocketManager", "Received malformed payload:", e)
@@ -89,38 +93,30 @@ object WebSocketManager {
} }
suspend fun send(message: WebSocketMessage) { suspend fun send(message: WebSocketMessage) {
val payload = json.encodeToString(WebSocketMessage.serializer(), message) session?.send(Frame.Text(json.encodeToString(message)))
ApiClient.http.webSocket(
method = HttpMethod.Get,
host = API_HOST,
request = {
url {
protocol = URLProtocol.WSS
host = "fromchat.ru"
encodedPath = "/api/chat/ws"
}
}
) {
send(Frame.Text(payload))
}
} }
@OptIn(DelicateCoroutinesApi::class) @OptIn(DelicateCoroutinesApi::class)
suspend fun request(message: WebSocketMessage, timeoutMs: Long = 10_000): WebSocketMessage? { suspend fun request(message: WebSocketMessage, timeoutMs: Long = 10_000): WebSocketMessage? {
val ch = Channel<WebSocketMessage>(capacity = 1) Log.d("WebSocketManager", "WebSocket request: $message")
val handler: (WebSocketMessage) -> Unit = { var handler: ((WebSocketMessage) -> Unit)? = null
ch.trySend(it)
}
setGlobalMessageHandler(handler)
return try { return try {
send(message) send(message)
withTimeout(timeoutMs) { ch.receive() } withTimeout(timeoutMs) {
suspendCoroutine { continuation ->
handler = {
continuation.resumeWith(Result.success(it))
removeGlobalMessageHandler(handler!!)
}
addGlobalMessageHandler(handler)
}
}
} catch (_: TimeoutCancellationException) { } catch (_: TimeoutCancellationException) {
Log.w("WebSocketManager", "Request timed out")
null null
} finally { } finally {
setGlobalMessageHandler(null) handler?.let { removeGlobalMessageHandler(it) }
ch.close()
} }
} }
@@ -61,11 +61,16 @@ import kotlinx.datetime.format
import kotlinx.datetime.toLocalDateTime import kotlinx.datetime.toLocalDateTime
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlinx.serialization.json.decodeFromJsonElement import kotlinx.serialization.json.decodeFromJsonElement
import kotlinx.serialization.json.encodeToJsonElement
import ru.fromchat.DATETIME_FORMAT import ru.fromchat.DATETIME_FORMAT
import ru.fromchat.R import ru.fromchat.R
import ru.fromchat.api.ApiClient import ru.fromchat.api.ApiClient
import ru.fromchat.api.Message import ru.fromchat.api.Message
import ru.fromchat.api.SendMessageRequest
import ru.fromchat.api.SendMessageResponse
import ru.fromchat.api.WebSocketCredentials
import ru.fromchat.api.WebSocketManager import ru.fromchat.api.WebSocketManager
import ru.fromchat.api.WebSocketMessage
import ru.fromchat.api.apiRequest import ru.fromchat.api.apiRequest
import ru.fromchat.utils.exclude import ru.fromchat.utils.exclude
import kotlin.time.ExperimentalTime import kotlin.time.ExperimentalTime
@@ -89,8 +94,9 @@ fun PublicChatScreen() {
} }
WebSocketManager.connect() WebSocketManager.connect()
WebSocketManager.setGlobalMessageHandler { msg -> WebSocketManager.addGlobalMessageHandler { msg ->
scope.launch { scope.launch {
if (msg.type == "newMessage") {
try { try {
messages += Json.decodeFromJsonElement<Message>(msg.data!!) messages += Json.decodeFromJsonElement<Message>(msg.data!!)
delay(500) delay(500)
@@ -100,6 +106,7 @@ fun PublicChatScreen() {
} }
} }
} }
}
delay(500) delay(500)
scrollState.animateScrollTo(scrollState.maxValue) scrollState.animateScrollTo(scrollState.maxValue)
@@ -132,6 +139,7 @@ fun PublicChatScreen() {
bottomBar = { bottomBar = {
Row( Row(
Modifier Modifier
.clip(RoundedCornerShape(topStart = 20.dp, topEnd = 20.dp))
.background(MaterialTheme.colorScheme.surfaceContainer) .background(MaterialTheme.colorScheme.surfaceContainer)
.fillMaxWidth() .fillMaxWidth()
.windowInsetsPadding(WindowInsets.safeDrawing.exclude(WindowInsetsSides.Top)) .windowInsetsPadding(WindowInsets.safeDrawing.exclude(WindowInsetsSides.Top))
@@ -162,17 +170,30 @@ fun PublicChatScreen() {
IconButton( IconButton(
onClick = { onClick = {
scope.launch { scope.launch {
val response = apiRequest { runCatching {
ApiClient.send(message.text.toString()) Json.decodeFromJsonElement<SendMessageResponse>(
} WebSocketManager.request(
WebSocketMessage(
message.setTextAndPlaceCursorAtEnd("") type = "sendMessage",
credentials = WebSocketCredentials(
if (response.isSuccess) { scheme = "Bearer",
messages += response.getOrThrow().message credentials = ApiClient.token!!
),
data = Json.encodeToJsonElement(
SendMessageRequest(
content = "${message.text}"
)
)
)
)!!.data!!
)
}.getOrNull()?.let {
messages += it.message
delay(500) delay(500)
scrollState.animateScrollTo(scrollState.maxValue) scrollState.animateScrollTo(scrollState.maxValue)
} }
message.setTextAndPlaceCursorAtEnd("")
} }
} }
) { ) {
@@ -4,6 +4,7 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.WindowInsetsSides import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawing import androidx.compose.foundation.layout.safeDrawing
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
@@ -52,7 +53,8 @@ fun MainScreen() {
) )
} }
}, },
contentWindowInsets = WindowInsets.safeDrawing.exclude(WindowInsetsSides.Top) contentWindowInsets = WindowInsets.safeDrawing.exclude(WindowInsetsSides.Top),
modifier = Modifier.imePadding()
) { innerPadding -> ) { innerPadding ->
Column( Column(
Modifier Modifier