diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 103609a56..151774ae6 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -64,6 +64,8 @@ dependencies { implementation(project(":core:domain")) implementation(project(":core:data")) implementation(project(":core:ui")) + implementation(project(":backend:api")) + implementation(project(":backend:config")) implementation(platform(libs.compose.bom)) implementation(libs.compose.ui) diff --git a/android/backend/config/build.gradle.kts b/android/backend/config/build.gradle.kts new file mode 100644 index 000000000..3e0090952 --- /dev/null +++ b/android/backend/config/build.gradle.kts @@ -0,0 +1,45 @@ +plugins { + alias(libs.plugins.android.library) + alias(libs.plugins.compose.compiler) + alias(libs.plugins.serialization) +} + +android { + namespace = "io.clawdroid.backend.config" + compileSdk = libs.versions.android.compileSdk.get().toInt() + + defaultConfig { + minSdk = libs.versions.android.minSdk.get().toInt() + } + + buildFeatures { + compose = true + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } +} + +dependencies { + implementation(project(":backend:api")) + implementation(project(":core:ui")) + + implementation(libs.ktor.client.okhttp) + implementation(libs.ktor.client.content.negotiation) + implementation(libs.ktor.serialization.json) + implementation(libs.serialization.json) + + implementation(platform(libs.compose.bom)) + implementation(libs.compose.ui) + implementation(libs.compose.material3) + implementation(libs.navigation.compose) + + implementation(libs.koin.android) + implementation(libs.koin.compose) + + implementation(libs.coroutines.android) + + debugImplementation(libs.compose.ui.tooling) +} diff --git a/android/backend/config/src/main/AndroidManifest.xml b/android/backend/config/src/main/AndroidManifest.xml new file mode 100644 index 000000000..8072ee00d --- /dev/null +++ b/android/backend/config/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + diff --git a/android/backend/config/src/main/java/io/clawdroid/backend/config/ConfigApiClient.kt b/android/backend/config/src/main/java/io/clawdroid/backend/config/ConfigApiClient.kt new file mode 100644 index 000000000..809cb944b --- /dev/null +++ b/android/backend/config/src/main/java/io/clawdroid/backend/config/ConfigApiClient.kt @@ -0,0 +1,79 @@ +package io.clawdroid.backend.config + +import io.ktor.client.HttpClient +import io.ktor.client.call.body +import io.ktor.client.engine.okhttp.OkHttp +import io.ktor.client.plugins.contentnegotiation.ContentNegotiation +import io.ktor.client.request.get +import io.ktor.client.request.put +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 io.ktor.serialization.kotlinx.json.json +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.JsonObject +import java.io.Closeable +import java.io.IOException + +@Serializable +data class ConfigSchema(val sections: List) + +@Serializable +data class SchemaSection(val key: String, val label: String, val fields: List) + +@Serializable +data class SchemaField( + val key: String, + val label: String, + val type: String, + val secret: Boolean = false, + val default: JsonElement = Json.parseToJsonElement("null"), +) + +@Serializable +data class SaveConfigResult( + val status: String? = null, + val restart: Boolean = false, + val error: String? = null, +) + +class ConfigApiClient : Closeable { + private val baseUrl: String get() = "http://127.0.0.1:18790" + + private val client = HttpClient(OkHttp) { + install(ContentNegotiation) { + json(Json { ignoreUnknownKeys = true; isLenient = true }) + } + } + + suspend fun getSchema(): ConfigSchema { + return client.get("$baseUrl/api/config/schema").ensureSuccess().body() + } + + suspend fun getConfig(): JsonObject { + return client.get("$baseUrl/api/config").ensureSuccess().body() + } + + suspend fun saveConfig(config: JsonObject): SaveConfigResult { + return client.put("$baseUrl/api/config") { + contentType(ContentType.Application.Json) + setBody(config) + }.ensureSuccess().body() + } + + override fun close() { + client.close() + } + + private suspend fun HttpResponse.ensureSuccess(): HttpResponse { + if (!status.isSuccess()) { + val error = runCatching { body().error }.getOrNull() + throw IOException("HTTP ${status.value}: ${error ?: "request failed"}") + } + return this + } +} diff --git a/android/settings.gradle.kts b/android/settings.gradle.kts index c8eb14336..70700201d 100644 --- a/android/settings.gradle.kts +++ b/android/settings.gradle.kts @@ -19,3 +19,4 @@ include(":core:domain") include(":core:data") include(":core:ui") include(":backend:api") +include(":backend:config")