// :data — Room database, Ktor client, token store, sync engine, WorkManager workers, the real repositories and the APK // updater (SPEC §6.2, §14.2, §14.4; owned by M5). Hilt modules here provide the implementations; :app (M8) binds the // :core:model interfaces to them. plugins { id("kvitt.android.library") id("kvitt.android.hilt") alias(libs.plugins.ksp) alias(libs.plugins.room3) alias(libs.plugins.kotlin.serialization) } room3 { schemaDirectory("$projectDir/schemas") } dependencies { implementation(project(":core:model")) implementation(project(":domain")) implementation(libs.androidx.room3.runtime) ksp(libs.androidx.room3.compiler) implementation(libs.androidx.sqlite.bundled) implementation(libs.androidx.datastore.preferences) implementation(libs.androidx.work.runtime.ktx) implementation(libs.ktor.client.core) implementation(libs.ktor.client.okhttp) implementation(libs.ktor.client.content.negotiation) implementation(libs.ktor.serialization.kotlinx.json) implementation(libs.kotlinx.serialization.json) implementation(libs.kotlinx.coroutines.core) implementation(libs.kotlinx.datetime) implementation(libs.coil) implementation(libs.coil.network.ktor3) testImplementation(libs.ktor.client.mock) testImplementation(libs.kotlinx.coroutines.test) testImplementation(libs.robolectric) } // Unit tests read the shared examples (SPEC §14.3.5) and the exported Room schemas (§14.2.3) in place; both are inputs, // so a changed contract file re-runs the tests. val contractsExamples = rootProject.layout.projectDirectory.dir("../contracts/examples") val roomSchemas = layout.projectDirectory.dir("schemas") val categoryKeywordsTable = rootProject.layout.projectDirectory.file("../contracts/category-keywords.csv") tasks.withType().configureEach { inputs.dir(contractsExamples).withPathSensitivity(PathSensitivity.RELATIVE).withPropertyName("contractsExamples") inputs.dir(roomSchemas).withPathSensitivity(PathSensitivity.RELATIVE).withPropertyName("roomSchemas") systemProperty("kvitt.contracts.examples", contractsExamples.asFile.absolutePath) systemProperty("kvitt.room.schemas", roomSchemas.asFile.absolutePath) inputs.file(categoryKeywordsTable).withPathSensitivity(PathSensitivity.RELATIVE).withPropertyName("categoryKeywords") systemProperty("kvitt.contracts.keywords", categoryKeywordsTable.asFile.absolutePath) } // The keyword table of the category suggestion (UX-3, SPEC §9.11 CS-6) is bundled as the Java resource // se/hagstedt/kvitt/data/category-keywords.csv, copied from contracts/category-keywords.csv at build time (never // hand-copied). A missing table fails the build: the task's input file must exist. abstract class CopyCategoryKeywords : DefaultTask() { @get:InputFile @get:PathSensitive(PathSensitivity.NONE) abstract val table: RegularFileProperty @get:Input abstract val resourcePath: Property @get:OutputDirectory abstract val outputDir: DirectoryProperty @TaskAction fun copy() { val source = table.get().asFile if (!source.isFile) throw GradleException("missing ${source.path}") val root = outputDir.get().asFile root.deleteRecursively() val target = root.resolve(resourcePath.get()) target.parentFile.mkdirs() source.copyTo(target) } } val copyCategoryKeywords = tasks.register("copyCategoryKeywords") { description = "Copies contracts/category-keywords.csv into the main Java resources." table.set(categoryKeywordsTable) resourcePath.set("se/hagstedt/kvitt/data/category-keywords.csv") outputDir.set(layout.buildDirectory.dir("generated/category-keywords")) } androidComponents { onVariants { variant -> variant.sources.resources?.addGeneratedSourceDirectory(copyCategoryKeywords, CopyCategoryKeywords::outputDir) } }