import java.util.Properties import java.io.File plugins { alias(libs.plugins.android.application) alias(libs.plugins.kotlin.android) alias(libs.plugins.kotlinx.serialization) alias(libs.plugins.compose.compiler) } // Подпись release ключом Binom (assembleRelease сам подпишет APK, v2+v3). Кестор + креды — // из скилла android-sign (вне git); переопределение: -Pbinom.keystore.dir=... / env BINOM_KEYSTORE_DIR. val binomCertsDir: File = File( (project.findProperty("binom.keystore.dir") as? String) ?: System.getenv("BINOM_KEYSTORE_DIR") ?: "${System.getProperty("user.home")}/.config/opencode/skills/android-sign/certs" ) val binomStoreFile = File(binomCertsDir, "android-release.keystore") val binomCreds: Map = (File(binomCertsDir, "creds.env").let { c -> if (binomStoreFile.exists() && c.exists()) c.readLines().filter { it.isNotBlank() && !it.startsWith("#") && it.contains('=') } .associate { val i = it.indexOf('='); it.substring(0, i) to it.substring(i + 1).trim().removeSurrounding("\"") } else emptyMap() }) // LLM-ключ из local.properties (llm.apiKey) — файл вне git; пустой = ещё не задан. // providers.gradleProperty не читает local.properties, поэтому грузим файл вручную. val llmApiKey: String = run { val props = Properties() val local = rootProject.file("local.properties") if (local.exists()) local.inputStream().use(props::load) props.getProperty("llm.apiKey", "") .replace("\\", "\\\\") .replace("\"", "\\\"") } // Модель для ассистента — из local.properties (llm.model), вне git. // Дефолт — быстрая дешёвая (пользователь укажет боевую). val llmModel: String = run { val props = Properties() val local = rootProject.file("local.properties") if (local.exists()) local.inputStream().use(props::load) props.getProperty("llm.model", "Routerai/deepseek/deepseek-v4-flash-0731") .replace("\\", "\\\\") .replace("\"", "\\\"") } android { namespace = "pw.binom.viewmate.phone" compileSdk = 35 defaultConfig { applicationId = "pw.binom.viewmate.phone" minSdk = 26 targetSdk = 34 versionCode = (project.findProperty("versionCode") as? String)?.toIntOrNull() ?: 1 versionName = (project.findProperty("versionName") as? String) ?: "0.1.0" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" // LLM-ключ из local.properties (llm.apiKey) — вне git; пустой = ещё не задан buildConfigField("String", "LLM_API_KEY", "\"$llmApiKey\"") // Модель ассистента — из local.properties (llm.model), вне git buildConfigField("String", "LLM_MODEL", "\"$llmModel\"") } // Подпись release ключом Binom (всё из binomStoreFile/binomCreds выше). // Без кестора binomCreds пусто → release остаётся неподписанным. if (binomCreds.isNotEmpty()) { val binomAlias = binomCreds.getValue("ANDROID_KEYSTORE_ALIAS") val binomStorePass = binomCreds.getValue("ANDROID_KEYSTORE_PASSWORD") val binomKeyPass = binomCreds["ANDROID_KEY_PASSWORD"] ?: binomStorePass signingConfigs { create("binom") { storeFile = binomStoreFile storePassword = binomStorePass keyAlias = binomAlias keyPassword = binomKeyPass enableV1Signing = false enableV2Signing = true enableV3Signing = true } } } buildTypes { release { isMinifyEnabled = false if (binomCreds.isNotEmpty()) { signingConfig = signingConfigs.getByName("binom") } } } compileOptions { sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 } kotlin { compilerOptions { jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17) } } buildFeatures { compose = true buildConfig = true } packaging { resources { // okhttp-sse 5.x и jspecify оба кладут этот OSGI-manifest excludes += "META-INF/versions/9/OSGI-INF/MANIFEST.MF" } } } dependencies { implementation("androidx.appcompat:appcompat:1.3.0") implementation("androidx.core:core-ktx:1.7.0") implementation(libs.androidx.activity.compose) implementation(platform(libs.androidx.compose.bom)) implementation(libs.android.material3) implementation("androidx.compose.ui:ui") implementation("androidx.compose.foundation:foundation") implementation("io.coil-kt:coil-compose:2.6.0") implementation("com.squareup.okhttp3:okhttp:4.12.0") implementation(libs.exoplayer) implementation(libs.media3.exoplayer) implementation(libs.kotlinx.coroutines.android) implementation(libs.kotlinx.serialization.json) implementation(project(":lib-core")) // RayNeo Mercury BLE protocol (RayNeo X2): pair/connect/команды с телефона implementation("pw.binom.mercury:rayneo-mercury:0.1.0-SNAPSHOT") // QR-код для паринга очков (pairCode) — рендер BitMatrix->Bitmap implementation("com.google.zxing:core:3.5.2") // Koog (JetBrains) — LLM-клиент (OpenAI-совместимый) implementation(libs.koog.openai.client) implementation(libs.koog.http.client.okhttp) // LiteRT-LM (Google) — локальный LLM (Gemma 4 E2B, .litertlm, GPU/CPU): // KMP-обёртка pw.binom.litert (универсальный контракт RAW-инференса + google-бэкенд) implementation(libs.litert.google) // универсальный контракт (LiteLlm/LiteRequest…) — импортируется напрямую implementation(libs.litert.api) // Silero-VAD на sherpa-onnx (SherpaStreamingVad / VadSegmenter), JitPack implementation("com.github.k2-fsa.sherpa-onnx:sherpa-onnx:v1.13.6") // Офлайн-распознавание Qwen3-ASR (asr-kmp, caffeine nexus) — Qwen3AsrStt implementation("pw.binom.asr:asr-qwen3-android:4") // WS-сервер телефона (CIO — работает на Android) implementation("io.ktor:ktor-server-core:3.3.0") implementation("io.ktor:ktor-server-cio:3.3.0") implementation("io.ktor:ktor-server-websockets:3.3.0") implementation("io.ktor:ktor-serialization-kotlinx-json:3.3.0") testImplementation(kotlin("test")) // Тестовый стриминговый VAD (Silero на ONNX, JVM) — из mavenLocal testImplementation("pw.binom.ai.voice:vad-jvm:1.0.0-SNAPSHOT") testImplementation("io.ktor:ktor-server-test-host:3.3.0") testImplementation("io.ktor:ktor-client-websockets:3.3.0") testImplementation("io.ktor:ktor-client-content-negotiation:3.3.0") testImplementation("io.ktor:ktor-serialization-kotlinx-json:3.3.0") testImplementation(libs.kotlinx.coroutines.test) androidTestImplementation("androidx.test.ext:junit:1.2.1") androidTestImplementation("androidx.test:runner:1.6.2") }