mirror of
https://github.com/fromchat-messenger/app.git
synced 2026-09-22 19:15:05 +03:00
Fix the payload format
Signed-off-by: denis0001-dev <denis0001.dev@ya.ru>
This commit is contained in:
@@ -9,7 +9,8 @@
|
|||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
android:theme="@style/Theme.FromChat">
|
android:theme="@style/Theme.FromChat"
|
||||||
|
android:usesCleartextTraffic="true">
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ package ru.fromchat
|
|||||||
import kotlinx.datetime.LocalDateTime
|
import kotlinx.datetime.LocalDateTime
|
||||||
import kotlinx.datetime.format.char
|
import kotlinx.datetime.format.char
|
||||||
|
|
||||||
const val API_HOST = "fromchat.ru"
|
const val API_HOST = "http://10.0.2.2:8301"
|
||||||
|
const val WS_API_HOST = "ws://10.0.2.2:8301"
|
||||||
|
|
||||||
val DATETIME_FORMAT = LocalDateTime.Format {
|
val DATETIME_FORMAT = LocalDateTime.Format {
|
||||||
day()
|
day()
|
||||||
|
|||||||
@@ -16,10 +16,11 @@ import io.ktor.http.ContentType
|
|||||||
import io.ktor.http.contentType
|
import io.ktor.http.contentType
|
||||||
import io.ktor.serialization.kotlinx.json.json
|
import io.ktor.serialization.kotlinx.json.json
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
|
import ru.fromchat.API_HOST
|
||||||
import ru.fromchat.utils.failOnError
|
import ru.fromchat.utils.failOnError
|
||||||
|
|
||||||
object ApiClient {
|
object ApiClient {
|
||||||
private val json = Json {
|
val json = Json {
|
||||||
ignoreUnknownKeys = true
|
ignoreUnknownKeys = true
|
||||||
isLenient = true
|
isLenient = true
|
||||||
encodeDefaults = true
|
encodeDefaults = true
|
||||||
@@ -53,7 +54,7 @@ object ApiClient {
|
|||||||
|
|
||||||
suspend fun login(request: LoginRequest) =
|
suspend fun login(request: LoginRequest) =
|
||||||
http
|
http
|
||||||
.post("https://fromchat.ru/api/login") {
|
.post("$API_HOST/api/login") {
|
||||||
contentType(ContentType.Application.Json)
|
contentType(ContentType.Application.Json)
|
||||||
setBody(request.also { Log.d("ApiClient", "Login request: $it") })
|
setBody(request.also { Log.d("ApiClient", "Login request: $it") })
|
||||||
}
|
}
|
||||||
@@ -66,7 +67,7 @@ object ApiClient {
|
|||||||
|
|
||||||
suspend fun register(request: RegisterRequest) =
|
suspend fun register(request: RegisterRequest) =
|
||||||
http
|
http
|
||||||
.post("https://fromchat.ru/api/register") {
|
.post("$API_HOST/api/register") {
|
||||||
contentType(ContentType.Application.Json)
|
contentType(ContentType.Application.Json)
|
||||||
setBody(request)
|
setBody(request)
|
||||||
}
|
}
|
||||||
@@ -74,7 +75,7 @@ object ApiClient {
|
|||||||
|
|
||||||
suspend fun getMessages() =
|
suspend fun getMessages() =
|
||||||
http
|
http
|
||||||
.get("https://fromchat.ru/api/get_messages") {
|
.get("$API_HOST/api/get_messages") {
|
||||||
contentType(ContentType.Application.Json)
|
contentType(ContentType.Application.Json)
|
||||||
}
|
}
|
||||||
.failOnError()
|
.failOnError()
|
||||||
@@ -82,7 +83,7 @@ object ApiClient {
|
|||||||
|
|
||||||
suspend fun send(message: String) =
|
suspend fun send(message: String) =
|
||||||
http
|
http
|
||||||
.post("https://fromchat.ru/api/send_message") {
|
.post("$API_HOST/api/send_message") {
|
||||||
contentType(ContentType.Application.Json)
|
contentType(ContentType.Application.Json)
|
||||||
bearerAuth(token!!)
|
bearerAuth(token!!)
|
||||||
setBody(SendMessageRequest(message))
|
setBody(SendMessageRequest(message))
|
||||||
|
|||||||
@@ -61,7 +61,8 @@ data class Message(
|
|||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class SendMessageRequest(
|
data class SendMessageRequest(
|
||||||
val content: String
|
val content: String,
|
||||||
|
val reply_to_id: Int? = null
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
|||||||
@@ -3,9 +3,8 @@ package ru.fromchat.api
|
|||||||
import android.util.Log
|
import android.util.Log
|
||||||
import io.ktor.client.plugins.websocket.DefaultClientWebSocketSession
|
import io.ktor.client.plugins.websocket.DefaultClientWebSocketSession
|
||||||
import io.ktor.client.plugins.websocket.webSocket
|
import io.ktor.client.plugins.websocket.webSocket
|
||||||
|
import io.ktor.client.request.url
|
||||||
import io.ktor.http.HttpMethod
|
import io.ktor.http.HttpMethod
|
||||||
import io.ktor.http.URLProtocol
|
|
||||||
import io.ktor.http.encodedPath
|
|
||||||
import io.ktor.websocket.Frame
|
import io.ktor.websocket.Frame
|
||||||
import io.ktor.websocket.readText
|
import io.ktor.websocket.readText
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
@@ -20,7 +19,7 @@ import kotlinx.coroutines.isActive
|
|||||||
import kotlinx.coroutines.launch
|
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.WS_API_HOST
|
||||||
import kotlin.coroutines.suspendCoroutine
|
import kotlin.coroutines.suspendCoroutine
|
||||||
|
|
||||||
object WebSocketManager {
|
object WebSocketManager {
|
||||||
@@ -55,16 +54,12 @@ object WebSocketManager {
|
|||||||
try {
|
try {
|
||||||
ApiClient.http.webSocket(
|
ApiClient.http.webSocket(
|
||||||
method = HttpMethod.Get,
|
method = HttpMethod.Get,
|
||||||
host = API_HOST,
|
|
||||||
request = {
|
request = {
|
||||||
url {
|
url("$WS_API_HOST/api/chat/ws")
|
||||||
protocol = URLProtocol.WSS
|
|
||||||
host = "fromchat.ru"
|
|
||||||
encodedPath = "/api/chat/ws"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
session = this
|
session = this
|
||||||
|
connecting = false
|
||||||
|
|
||||||
Log.d("WebSocketManager", "WebSocket connected")
|
Log.d("WebSocketManager", "WebSocket connected")
|
||||||
for (frame in incoming) {
|
for (frame in incoming) {
|
||||||
@@ -82,17 +77,31 @@ object WebSocketManager {
|
|||||||
}
|
}
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
Log.w("WebSocketManager", "An error occurred:", e)
|
Log.w("WebSocketManager", "An error occurred:", e)
|
||||||
|
connecting = false
|
||||||
|
session = null
|
||||||
delay(3000)
|
delay(3000)
|
||||||
} finally {
|
} finally {
|
||||||
Log.w("WebSocketManager", "WebSocket disconnected")
|
Log.w("WebSocketManager", "WebSocket disconnected")
|
||||||
session = null
|
session = null
|
||||||
|
connecting = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun send(message: WebSocketMessage) {
|
suspend fun send(message: WebSocketMessage) {
|
||||||
session?.send(Frame.Text(json.encodeToString(message)))
|
val session = session
|
||||||
|
if (session != null) {
|
||||||
|
try {
|
||||||
|
session.send(Frame.Text(json.encodeToString(message)))
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("WebSocketManager", "Failed to send message", e)
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.w("WebSocketManager", "Cannot send message: no active session")
|
||||||
|
throw IllegalStateException("No active WebSocket session")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(DelicateCoroutinesApi::class)
|
@OptIn(DelicateCoroutinesApi::class)
|
||||||
@@ -101,12 +110,21 @@ object WebSocketManager {
|
|||||||
var handler: ((WebSocketMessage) -> Unit)? = null
|
var handler: ((WebSocketMessage) -> Unit)? = null
|
||||||
|
|
||||||
return try {
|
return try {
|
||||||
|
// Check if we have a valid session before sending
|
||||||
|
if (session == null) {
|
||||||
|
Log.w("WebSocketManager", "No active WebSocket session")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
send(message)
|
send(message)
|
||||||
withTimeout(timeoutMs) {
|
withTimeout(timeoutMs) {
|
||||||
suspendCoroutine { continuation ->
|
suspendCoroutine { continuation ->
|
||||||
handler = {
|
handler = { response ->
|
||||||
continuation.resumeWith(Result.success(it))
|
// Only process responses that match our request type
|
||||||
removeGlobalMessageHandler(handler!!)
|
if (response.type == message.type) {
|
||||||
|
continuation.resumeWith(Result.success(response))
|
||||||
|
removeGlobalMessageHandler(handler!!)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
addGlobalMessageHandler(handler)
|
addGlobalMessageHandler(handler)
|
||||||
}
|
}
|
||||||
@@ -114,6 +132,9 @@ object WebSocketManager {
|
|||||||
} catch (_: TimeoutCancellationException) {
|
} catch (_: TimeoutCancellationException) {
|
||||||
Log.w("WebSocketManager", "Request timed out")
|
Log.w("WebSocketManager", "Request timed out")
|
||||||
null
|
null
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("WebSocketManager", "Request failed", e)
|
||||||
|
null
|
||||||
} finally {
|
} finally {
|
||||||
handler?.let { removeGlobalMessageHandler(it) }
|
handler?.let { removeGlobalMessageHandler(it) }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,7 +67,6 @@ import kotlinx.coroutines.launch
|
|||||||
import kotlinx.datetime.TimeZone
|
import kotlinx.datetime.TimeZone
|
||||||
import kotlinx.datetime.format
|
import kotlinx.datetime.format
|
||||||
import kotlinx.datetime.toLocalDateTime
|
import kotlinx.datetime.toLocalDateTime
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
import kotlinx.serialization.json.decodeFromJsonElement
|
import kotlinx.serialization.json.decodeFromJsonElement
|
||||||
import kotlinx.serialization.json.encodeToJsonElement
|
import kotlinx.serialization.json.encodeToJsonElement
|
||||||
import ru.fromchat.DATETIME_FORMAT
|
import ru.fromchat.DATETIME_FORMAT
|
||||||
@@ -75,7 +74,6 @@ 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.SendMessageRequest
|
||||||
import ru.fromchat.api.SendMessageResponse
|
|
||||||
import ru.fromchat.api.WebSocketCredentials
|
import ru.fromchat.api.WebSocketCredentials
|
||||||
import ru.fromchat.api.WebSocketManager
|
import ru.fromchat.api.WebSocketManager
|
||||||
import ru.fromchat.api.WebSocketMessage
|
import ru.fromchat.api.WebSocketMessage
|
||||||
@@ -116,8 +114,12 @@ fun PublicChatScreen() {
|
|||||||
scope.launch {
|
scope.launch {
|
||||||
if (msg.type == "newMessage") {
|
if (msg.type == "newMessage") {
|
||||||
try {
|
try {
|
||||||
messages += Json.decodeFromJsonElement<Message>(msg.data!!)
|
val newMessage = ApiClient.json.decodeFromJsonElement<Message>(msg.data!!)
|
||||||
scrollDown()
|
// Check if message already exists to prevent duplicates
|
||||||
|
if (messages.none { it.id == newMessage.id }) {
|
||||||
|
messages += newMessage
|
||||||
|
scrollDown()
|
||||||
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e("PublicChatScreen", "Failed to process incoming message:", e)
|
Log.e("PublicChatScreen", "Failed to process incoming message:", e)
|
||||||
}
|
}
|
||||||
@@ -188,29 +190,36 @@ fun PublicChatScreen() {
|
|||||||
IconButton(
|
IconButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
runCatching {
|
val messageText = "${message.text.trim()}"
|
||||||
Json.decodeFromJsonElement<SendMessageResponse>(
|
if (messageText.isEmpty()) return@launch
|
||||||
WebSocketManager.request(
|
|
||||||
WebSocketMessage(
|
try {
|
||||||
type = "sendMessage",
|
val response = WebSocketManager.request(
|
||||||
credentials = WebSocketCredentials(
|
WebSocketMessage(
|
||||||
scheme = "Bearer",
|
type = "sendMessage",
|
||||||
credentials = ApiClient.token!!
|
credentials = WebSocketCredentials(
|
||||||
),
|
scheme = "Bearer",
|
||||||
data = Json.encodeToJsonElement(
|
credentials = ApiClient.token!!
|
||||||
SendMessageRequest(
|
),
|
||||||
content = "${message.text}"
|
data = ApiClient.json.encodeToJsonElement(
|
||||||
)
|
SendMessageRequest(
|
||||||
|
content = messageText
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)!!.data!!
|
)
|
||||||
)
|
)
|
||||||
}.getOrNull()?.let {
|
|
||||||
messages += it.message
|
|
||||||
scrollDown()
|
|
||||||
}
|
|
||||||
|
|
||||||
message.setTextAndPlaceCursorAtEnd("")
|
if (response != null && response.error == null) {
|
||||||
|
message.setTextAndPlaceCursorAtEnd("")
|
||||||
|
Log.d("PublicChatScreen", "Message sent successfully")
|
||||||
|
// Message will be added to the list via WebSocket "newMessage" event
|
||||||
|
} else {
|
||||||
|
Log.e("PublicChatScreen", "WebSocket error: ${response?.error}")
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("PublicChatScreen", "Failed to send message", e)
|
||||||
|
// Could show a toast or error message to user here
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
|
|||||||
Reference in New Issue
Block a user