Initial commit

This commit is contained in:
tanexc
2026-06-12 23:49:11 +03:00
commit f69ffd79c2
222 changed files with 7094 additions and 0 deletions
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
</manifest>
@@ -0,0 +1,10 @@
package ru.tanexc.hemoanalysis.core.network.util
import io.ktor.client.HttpClient
import io.ktor.client.HttpClientConfig
import io.ktor.client.engine.okhttp.OkHttp
internal actual fun PlatformHttpClient(declaration: HttpClientConfig<*>.() -> Unit): HttpClient = HttpClient(
engineFactory = OkHttp,
block = declaration,
)
@@ -0,0 +1,3 @@
package ru.tanexc.hemoanalysis.core.network
internal const val DEFAULT_BASE_URL = "http://111.88.154.194:8000"
@@ -0,0 +1,67 @@
package ru.tanexc.hemoanalysis.core.network
import io.ktor.client.HttpClient
import io.ktor.client.request.header
import io.ktor.client.request.parameter
import io.ktor.client.statement.HttpResponse
import io.ktor.client.statement.bodyAsChannel
import io.ktor.http.ContentType
import io.ktor.http.HttpStatusCode
import io.ktor.http.contentType
import io.ktor.http.isSuccess
import io.ktor.util.logging.KtorSimpleLogger
import io.ktor.utils.io.availableForRead
import io.ktor.utils.io.readBuffer
import kotlinx.io.readByteArray
import ru.tanexc.hemoanalysis.core.network.response.ModelDownloadResponse
import ru.tanexc.hemoanalysis.core.network.response.ModelMetaResponse
import ru.tanexc.hemoanalysis.core.network.util.get
import ru.tanexc.hemoanalysis.domain.usecase.BytesReader
import kotlin.math.log
import io.ktor.client.request.get as ktorGet
class ModelApi(
private val httpClient: HttpClient,
) {
suspend fun getLatestModelInfo(apiKey: String): RequestState<ModelMetaResponse> =
httpClient.get(url = "$DEFAULT_BASE_URL/models/latest") {
header("X-API-KEY", apiKey)
}
suspend fun downloadLatestModel(
apiKey: String,
version: String,
filename: String
): RequestState<ModelDownloadResponse> = runCatching {
val response: HttpResponse =
httpClient.ktorGet("$DEFAULT_BASE_URL/models/latest/download") {
header("X-API-KEY", apiKey)
parameter("version", version)
parameter("filename", filename)
contentType(ContentType.Application.OctetStream)
}
when {
response.status.isSuccess() -> {
val buffer = response.bodyAsChannel()
val bytesReader = object : BytesReader {
override suspend fun readNext(size: Int): ByteArray {
return buffer.readBuffer(size).readByteArray()
}
}
RequestState.Success(
ModelDownloadResponse(
bytesReader = bytesReader,
totalBytes = buffer.availableForRead
)
)
}
response.status == HttpStatusCode.NotFound -> RequestState.Error("Model not found")
else -> RequestState.Error(response.status.description)
}
}.getOrElse {
RequestState.Error(
message = it.message ?: "Request error: $DEFAULT_BASE_URL/models/latest/download"
)
}
}
@@ -0,0 +1,7 @@
package ru.tanexc.hemoanalysis.core.network
sealed interface RequestState<out T> {
data class Success<T>(val data: T): RequestState<T>
data class Error(val message: String): RequestState<Nothing>
}
@@ -0,0 +1,31 @@
package ru.tanexc.hemoanalysis.core.network.di
import io.ktor.client.HttpClient
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.SIMPLE
import io.ktor.serialization.kotlinx.json.json
import kotlinx.serialization.json.Json
import org.koin.core.module.dsl.singleOf
import org.koin.dsl.module
import ru.tanexc.hemoanalysis.core.network.ModelApi
import ru.tanexc.hemoanalysis.core.network.util.PlatformHttpClient
val networkModule = module {
single<HttpClient> {
PlatformHttpClient {
install(ContentNegotiation) {
json(Json { ignoreUnknownKeys = true })
}
install(Logging) {
level = LogLevel.HEADERS
}
}
}
singleOf(::ModelApi)
}
@@ -0,0 +1,8 @@
package ru.tanexc.hemoanalysis.core.network.response
import ru.tanexc.hemoanalysis.domain.usecase.BytesReader
data class ModelDownloadResponse(
val bytesReader: BytesReader,
val totalBytes: Int
)
@@ -0,0 +1,12 @@
package ru.tanexc.hemoanalysis.core.network.response
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class ModelMetaResponse(
val version: String,
@SerialName("created_at")
val createdAt: String,
val filename: String
)
@@ -0,0 +1,7 @@
package ru.tanexc.hemoanalysis.core.network.util
import io.ktor.client.HttpClient
import io.ktor.client.HttpClientConfig
import org.koin.core.scope.Scope
internal expect fun PlatformHttpClient(declaration: HttpClientConfig<*>.() -> Unit): HttpClient
@@ -0,0 +1,28 @@
package ru.tanexc.hemoanalysis.core.network.util
import io.ktor.client.HttpClient
import io.ktor.client.call.body
import io.ktor.client.request.HttpRequestBuilder
import io.ktor.client.request.get
import io.ktor.client.request.post
import io.ktor.client.request.setBody
import io.ktor.client.statement.HttpResponse
import io.ktor.http.ContentType
import io.ktor.http.contentType
import io.ktor.http.isSuccess
import ru.tanexc.hemoanalysis.core.network.RequestState
internal suspend inline fun <reified T> HttpClient.get(
url: String,
block: HttpRequestBuilder.() -> Unit = {}
): RequestState<T> = runCatching {
get(
urlString = url,
block = block
).handleResponse<T>()
}.getOrElse { RequestState.Error(message = it.message ?: "Request error: $url") }
internal suspend inline fun <reified T> HttpResponse.handleResponse(): RequestState<T> = when {
status.isSuccess() -> RequestState.Success(body())
else -> RequestState.Error(status.description)
}
@@ -0,0 +1,10 @@
package ru.tanexc.hemoanalysis.core.network.util
import io.ktor.client.HttpClient
import io.ktor.client.HttpClientConfig
import io.ktor.client.engine.darwin.Darwin
internal actual fun PlatformHttpClient(declaration: HttpClientConfig<*>.() -> Unit): HttpClient = HttpClient(
engineFactory = Darwin,
block = declaration
)