Refactor code

Signed-off-by: denis0001-dev <denis0001.dev@ya.ru>
This commit is contained in:
2025-09-08 22:29:41 +03:00
Unverified
parent b35808a25c
commit 7e9448c883
6 changed files with 174 additions and 41 deletions
+17 -25
View File
@@ -10,11 +10,10 @@ import io.ktor.client.plugins.websocket.WebSockets
import io.ktor.client.request.post import io.ktor.client.request.post
import io.ktor.client.request.setBody import io.ktor.client.request.setBody
import io.ktor.http.ContentType import io.ktor.http.ContentType
import io.ktor.http.URLBuilder
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
object ApiClient { object ApiClient {
private val json = Json { private val json = Json {
@@ -45,28 +44,21 @@ object ApiClient {
var token: String? = null var token: String? = null
private set private set
private fun apiUrlBuilder(): URLBuilder { suspend fun login(request: LoginRequest) =
return URLBuilder().apply { http
protocol = io.ktor.http.URLProtocol.HTTPS .post("https://fromchat.ru/api/login") {
host = API_HOST contentType(ContentType.Application.Json)
} setBody(request)
} }
.failOnError()
.body<LoginResponse>()
.also { token = it.token }
suspend fun login(request: LoginRequest): LoginResponse { suspend fun register(request: RegisterRequest) =
// Endpoint exists in web: POST /login with {username,password} http
val response = http.post("https://fromchat.ru/api/login") { .post("https://fromchat.ru/api/register") {
contentType(ContentType.Application.Json) contentType(ContentType.Application.Json)
setBody(request) setBody(request)
}.body<LoginResponse>() }
token = response.token .failOnError()
return response
}
suspend fun register(request: RegisterRequest) {
// Endpoint exists in web: POST /register with {username,password,confirm_password}
http.post("https://fromchat.ru/api/register") {
contentType(ContentType.Application.Json)
setBody(request)
}
}
} }
+1 -1
View File
@@ -17,7 +17,7 @@ data class RegisterRequest(
@Serializable @Serializable
data class ErrorResponse( data class ErrorResponse(
val message: String? = null val detail: String
) )
@Serializable @Serializable
@@ -0,0 +1,29 @@
package ru.fromchat.api
import io.ktor.client.call.body
import io.ktor.client.plugins.ClientRequestException
suspend inline fun <Response> apiRequest(
onError: (String, Exception) -> Unit,
onSuccess: (Response) -> Unit,
request: suspend () -> Response
): Result<Response> {
try {
val response = request()
onSuccess(response)
return Result.success(response)
} catch (e: ClientRequestException) {
onError(
if (e.response.status.value in arrayOf(401, 403)) {
e.response.body<ErrorResponse>().detail
} else {
"Unexpected error"
},
e
)
return Result.failure(e)
} catch (e: Exception) {
onError("Unexpected error", e)
return Result.failure(e)
}
}
@@ -1,5 +1,6 @@
package ru.fromchat.ui.auth package ru.fromchat.ui.auth
import android.util.Log
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.FlowRow
@@ -16,16 +17,16 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.text.input.PasswordVisualTransformation import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import ru.fromchat.api.ApiClient import ru.fromchat.api.ApiClient
import ru.fromchat.api.LoginRequest import ru.fromchat.api.LoginRequest
import ru.fromchat.api.apiRequest
import ru.fromchat.ui.RowHeader import ru.fromchat.ui.RowHeader
@Composable @Composable
@@ -38,6 +39,8 @@ fun LoginScreen(
var password by remember { mutableStateOf("") } var password by remember { mutableStateOf("") }
var alert by remember { mutableStateOf<String?>(null) } var alert by remember { mutableStateOf<String?>(null) }
val scope = rememberCoroutineScope()
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
@@ -84,12 +87,15 @@ fun LoginScreen(
return@Button return@Button
} }
CoroutineScope(Dispatchers.IO).launch { scope.launch {
try { apiRequest(
onError = { message, e ->
Log.e("LoginScreen", "An error ocurred while logging in:", e)
alert = message
},
onSuccess = { onLoginSuccess() }
) {
ApiClient.login(LoginRequest(username, password)) ApiClient.login(LoginRequest(username, password))
onLoginSuccess()
} catch (e: Exception) {
alert = "Неверное имя пользователя или пароль"
} }
} }
} }
@@ -16,16 +16,16 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.text.input.PasswordVisualTransformation import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import ru.fromchat.api.ApiClient import ru.fromchat.api.ApiClient
import ru.fromchat.api.RegisterRequest import ru.fromchat.api.RegisterRequest
import ru.fromchat.api.apiRequest
import ru.fromchat.ui.RowHeader import ru.fromchat.ui.RowHeader
@Composable @Composable
@@ -38,6 +38,8 @@ fun RegisterScreen(
var confirmPassword by remember { mutableStateOf("") } var confirmPassword by remember { mutableStateOf("") }
var alert by remember { mutableStateOf<String?>(null) } var alert by remember { mutableStateOf<String?>(null) }
val scope = rememberCoroutineScope()
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
@@ -105,8 +107,14 @@ fun RegisterScreen(
} }
// Send the register request // Send the register request
CoroutineScope(Dispatchers.IO).launch { scope.launch {
try { apiRequest(
onError = { message, e ->
Log.d("RegisterScreen", "Error while registering:", e)
alert = message
},
onSuccess = { onRegistered() }
) {
ApiClient.register( ApiClient.register(
RegisterRequest( RegisterRequest(
username, username,
@@ -114,10 +122,6 @@ fun RegisterScreen(
confirmPassword confirmPassword
) )
) )
onRegistered()
} catch (e: Exception) {
Log.d("RegisterScreen", "Error while registering:", e)
alert = "Ошибка при регистрации"
} }
} }
} }
+102
View File
@@ -0,0 +1,102 @@
@file:Suppress("NOTHING_TO_INLINE")
package ru.fromchat.utils
import android.util.Log
import io.ktor.client.HttpClientConfig
import io.ktor.client.plugins.ClientRequestException
import io.ktor.client.plugins.DefaultRequest
import io.ktor.client.plugins.ResponseException
import io.ktor.client.plugins.ServerResponseException
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
import io.ktor.client.plugins.logging.DEFAULT
import io.ktor.client.plugins.logging.LogLevel
import io.ktor.client.plugins.logging.Logger
import io.ktor.client.plugins.logging.Logging
import io.ktor.client.plugins.logging.LoggingConfig
import io.ktor.client.statement.HttpResponse
import io.ktor.client.statement.bodyAsText
import io.ktor.serialization.Configuration
import io.ktor.serialization.kotlinx.json.json
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonBuilder
/**
* Configures the Ktor client for kotlinx.serialization JSON with custom settings.
* @param settings Lambda to configure the [JsonBuilder].
*/
@PublishedApi
internal inline fun Configuration.json(crossinline settings: JsonBuilder.() -> Unit) {
json(
Json {
settings()
}
)
}
/**
* Installs [ContentNegotiation] with JSON support and custom settings in a Ktor client config.
* @param settings Lambda to configure the [JsonBuilder].
*/
inline fun HttpClientConfig<*>.jsonConfig(
crossinline settings: JsonBuilder.() -> Unit
) = install(ContentNegotiation) {
json {
settings()
}
}
/**
* Installs [DefaultRequest] in a Ktor client config.
* @param settings Lambda to configure the [DefaultRequest.DefaultRequestBuilder].
*/
inline fun HttpClientConfig<*>.defaultRequest(
crossinline settings: DefaultRequest.DefaultRequestBuilder.() -> Unit
) = install(DefaultRequest) {
settings()
}
/**
* Installs [Logging] in a Ktor client config.
* @param settings Lambda to configure the [LoggingConfig].
*/
inline fun HttpClientConfig<*>.logging(
crossinline settings: LoggingConfig.() -> Unit
) = install(Logging) {
settings()
}
/**
* Installs [Logging] in a Ktor client config with default settings (all logs).
*/
inline fun HttpClientConfig<*>.logging() = logging {
logger = Logger.DEFAULT
level = LogLevel.ALL
}
/**
* Throws if the HTTP response status code is an error (>=400).
* @return The [HttpResponse] if successful.
* @throws ClientRequestException
* @throws ServerResponseException
* @throws ResponseException on error.
*/
suspend fun HttpResponse.failOnError(): HttpResponse {
Log.d("failOnError", "Checking the response code...")
val statusCode = status.value
suspend fun body() = bodyAsText()
Log.d("failOnError", "code: $statusCode")
when (statusCode) {
in 400..499 -> throw ClientRequestException(this, body())
in 500..599 -> throw ServerResponseException(this, body())
}
if (statusCode >= 600) {
throw ResponseException(this, body())
}
Log.d("failOnError", "Success")
return this
}