import groovy.json.JsonSlurper plugins { id("com.android.application") } // One version for every package (CLAUDE.md): read from the desktop client's // package.json rather than written a second time here. val packageVersion = (JsonSlurper().parse(rootDir.resolve("../meshbay-client/package.json")) as Map<*, *>)["version"] as String val versionParts = packageVersion.split(".").map { it.toInt() } android { namespace = "org.meshbay.client" compileSdk = 37 defaultConfig { applicationId = "org.meshbay.client" minSdk = 26 targetSdk = 36 versionName = packageVersion versionCode = versionParts[0] * 10000 + versionParts[1] * 100 + versionParts[2] } buildTypes { getByName("release") { isMinifyEnabled = false // A stand-in until the release key exists (Stage D12): the debug // key, so a release build installs over a debug one and back // without losing the account. Not a key to publish anything with. signingConfig = signingConfigs.getByName("debug") } } compileOptions { sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 } buildFeatures { buildConfig = true } testOptions { unitTests.isReturnDefaultValues = false } } dependencies { implementation("androidx.webkit:webkit:1.17.1") implementation("com.squareup.okhttp3:okhttp:5.5.0") // Ed25519, X25519, HKDF and Argon2id, identical on the JVM and every // Android version: the platform has no Argon2 and its Ed25519 is recent. implementation("org.bouncycastle:bcprov-jdk18on:1.86") // Casting: discovery through MediaRouter, control through the cast sender // SDK and the default media receiver. Needs the vendor's play services at // run time; where they are absent the page is offered no cast at all. implementation("com.google.android.gms:play-services-cast-framework:22.3.1") implementation("androidx.mediarouter:mediarouter:1.8.1") testImplementation("junit:junit:4.13.2") // Android's org.json is a stub on the JVM; the unit tests need the real one. testImplementation("org.json:json:20260814") } /** * The interface, copied from its single source at build time (§8.3). * * `meshbay-hub/src/meshbay_hub/static/` is the interface for the web, the * desktop application and this one. The copy lands in build/ and is never * committed, so it cannot fork. The page itself is the desktop application's * `scripts/index.html` — the hub builds its own with a /a// prefix that * would point back at the hub — so an application loading from its package * has one page, not two. */ abstract class SyncUi : DefaultTask() { @get:InputDirectory abstract val staticDir: DirectoryProperty @get:InputFile abstract val indexHtml: RegularFileProperty @get:OutputDirectory abstract val outputDir: DirectoryProperty @TaskAction fun copy() { val ui = outputDir.get().asFile.resolve("ui") outputDir.get().asFile.deleteRecursively() staticDir.get().asFile.copyRecursively(ui) indexHtml.get().asFile.copyTo(ui.resolve("index.html"), overwrite = true) } } val syncUi = tasks.register("syncUi") { staticDir.set(rootDir.resolve("../meshbay-hub/src/meshbay_hub/static")) indexHtml.set(rootDir.resolve("../meshbay-client/scripts/index.html")) outputDir.set(layout.buildDirectory.dir("generated/ui-assets")) } androidComponents { onVariants { variant -> variant.sources.assets?.addGeneratedSourceDirectory(syncUi, SyncUi::outputDir) } }