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.TimeoutCancellationException
import kotlinx.coroutines.cancel
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asSharedFlow
@@ -22,6 +21,7 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withTimeout
import kotlinx.serialization.json.Json
import ru.fromchat.API_HOST
import kotlin.coroutines.suspendCoroutine
object WebSocketManager {
// Config
@@ -32,10 +32,14 @@ object WebSocketManager {
val messages = _messages.asSharedFlow()
@Volatile
private var globalHandler: ((WebSocketMessage) -> Unit)? = null
private var globalHandlers = mutableListOf<((WebSocketMessage) -> Unit)>()
fun setGlobalMessageHandler(handler: ((WebSocketMessage) -> Unit)?) {
globalHandler = handler
fun addGlobalMessageHandler(handler: ((WebSocketMessage) -> Unit)) {
globalHandlers += handler
}
fun removeGlobalMessageHandler(handler: ((WebSocketMessage) -> Unit)) {
globalHandlers -= handler
}
// State
@@ -69,7 +73,7 @@ object WebSocketManager {
Log.d("WebSocketManager", "Received payload: $text")
try {
val msg = json.decodeFromString<WebSocketMessage>(text)
globalHandler?.invoke(msg)
globalHandlers.forEach { it(msg) }
_messages.emit(msg)
} catch (e: Throwable) {
Log.w("WebSocketManager", "Received malformed payload:", e)
@@ -89,38 +93,30 @@ object WebSocketManager {
}
suspend fun send(message: WebSocketMessage) {
val payload = json.encodeToString(WebSocketMessage.serializer(), 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))
}
session?.send(Frame.Text(json.encodeToString(message)))
}
@OptIn(DelicateCoroutinesApi::class)
suspend fun request(message: WebSocketMessage, timeoutMs: Long = 10_000): WebSocketMessage? {
val ch = Channel<WebSocketMessage>(capacity = 1)
val handler: (WebSocketMessage) -> Unit = {
ch.trySend(it)
}
setGlobalMessageHandler(handler)
Log.d("WebSocketManager", "WebSocket request: $message")
var handler: ((WebSocketMessage) -> Unit)? = null
return try {
send(message)
withTimeout(timeoutMs) { ch.receive() }
withTimeout(timeoutMs) {
suspendCoroutine { continuation ->
handler = {
continuation.resumeWith(Result.success(it))
removeGlobalMessageHandler(handler!!)
}
addGlobalMessageHandler(handler)
}
}
} catch (_: TimeoutCancellationException) {
Log.w("WebSocketManager", "Request timed out")
null
} finally {
setGlobalMessageHandler(null)
ch.close()
handler?.let { removeGlobalMessageHandler(it) }
}
}
@@ -61,11 +61,16 @@ import kotlinx.datetime.format
import kotlinx.datetime.toLocalDateTime
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.decodeFromJsonElement
import kotlinx.serialization.json.encodeToJsonElement
import ru.fromchat.DATETIME_FORMAT
import ru.fromchat.R
import ru.fromchat.api.ApiClient
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.WebSocketMessage
import ru.fromchat.api.apiRequest
import ru.fromchat.utils.exclude
import kotlin.time.ExperimentalTime
@@ -89,8 +94,9 @@ fun PublicChatScreen() {
}
WebSocketManager.connect()
WebSocketManager.setGlobalMessageHandler { msg ->
WebSocketManager.addGlobalMessageHandler { msg ->
scope.launch {
if (msg.type == "newMessage") {
try {
messages += Json.decodeFromJsonElement<Message>(msg.data!!)
delay(500)
@@ -100,6 +106,7 @@ fun PublicChatScreen() {
}
}
}
}
delay(500)
scrollState.animateScrollTo(scrollState.maxValue)
@@ -132,6 +139,7 @@ fun PublicChatScreen() {
bottomBar = {
Row(
Modifier
.clip(RoundedCornerShape(topStart = 20.dp, topEnd = 20.dp))
.background(MaterialTheme.colorScheme.surfaceContainer)
.fillMaxWidth()
.windowInsetsPadding(WindowInsets.safeDrawing.exclude(WindowInsetsSides.Top))
@@ -162,17 +170,30 @@ fun PublicChatScreen() {
IconButton(
onClick = {
scope.launch {
val response = apiRequest {
ApiClient.send(message.text.toString())
}
message.setTextAndPlaceCursorAtEnd("")
if (response.isSuccess) {
messages += response.getOrThrow().message
runCatching {
Json.decodeFromJsonElement<SendMessageResponse>(
WebSocketManager.request(
WebSocketMessage(
type = "sendMessage",
credentials = WebSocketCredentials(
scheme = "Bearer",
credentials = ApiClient.token!!
),
data = Json.encodeToJsonElement(
SendMessageRequest(
content = "${message.text}"
)
)
)
)!!.data!!
)
}.getOrNull()?.let {
messages += it.message
delay(500)
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.WindowInsetsSides
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawing
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 ->
Column(
Modifier