Files
kvitt/android/app/build.gradle.kts
T
jacobh2 2bd6b584f8 M12f: :app invite links dropped while paired, push-token retry, private notifications, en/sv only (D-112, D-116)
- AppViewModel.onLink drops an invite link while paired (AC-2.6, D-084), so it never prefills S-1 later.
- PushTokens: a failed E-17 PUT (network, 429, 5xx) is kept by PushTokenRetry and sent again after each successful
  sync until accepted; a newer FID replaces it (AC-13.7).
- Notifications are VISIBILITY_PRIVATE with a generic public version (notif_generic_title, notif_generic_body):
  no description, amount, account or name on a secure lock screen (security review L-7).
- androidResources.localeFilters en, sv (SPEC G-8, AC-19.1).
- Tests: AppViewModelTest (links while paired and unpaired, AC-2.9 permission after the first finished successful
  sync), PushTokenRetryTest, NotificationPrivacyTest.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
2026-10-18 22:33:07 +02:00

110 lines
4.7 KiB
Kotlin

// :app — MainActivity, Application, Hilt wiring (every :core:model interface bound to :data, D-041 and D-085),
// navigation and deep links, push handling and notifications, network security and release signing (SPEC §6.2, M8).
import java.util.Properties
plugins {
id("kvitt.android.application")
id("kvitt.android.compose")
id("kvitt.android.hilt")
alias(libs.plugins.google.services)
alias(libs.plugins.roborazzi)
}
// SPEC §10.6: release signing from android/local.properties (git-ignored); without the keys the release build is unsigned.
val localProperties = Properties().apply {
val file = rootProject.file("local.properties")
if (file.isFile) file.inputStream().use { load(it) }
}
val releaseStoreFile: String? = localProperties.getProperty("kvitt.release.storeFile")
// SPEC §10.5.3: the pinned Caddy root replaces the system anchors for icehack.asuscomm.com only when this is true.
val pinnedRoot = providers.gradleProperty("kvitt.tls.pinnedRoot").orElse("false").get().toBoolean()
android {
namespace = "se.hagstedt.kvitt"
defaultConfig {
applicationId = "se.hagstedt.kvitt"
// SPEC §10.6: the release script passes kvitt.versionCode and kvitt.versionName; debug builds use 1 and "dev".
versionCode = providers.gradleProperty("kvitt.versionCode").orElse("1").get().toInt()
versionName = providers.gradleProperty("kvitt.versionName").orElse("dev").get()
}
// SPEC G-8 and AC-19.1: the app is English (fallback) and Swedish only; library translations of other languages
// are left out of the APK.
androidResources {
localeFilters += listOf("en", "sv")
}
// BuildConfig carries versionCode, versionName and DEBUG into AppBuildInfo (:data, D-041).
buildFeatures {
buildConfig = true
}
signingConfigs {
if (releaseStoreFile != null) {
create("release") {
storeFile = file(releaseStoreFile)
storePassword = localProperties.getProperty("kvitt.release.storePassword")
keyAlias = localProperties.getProperty("kvitt.release.keyAlias")
keyPassword = localProperties.getProperty("kvitt.release.keyPassword")
}
}
}
buildTypes {
debug {
applicationIdSuffix = ".debug"
}
release {
if (releaseStoreFile != null) signingConfig = signingConfigs.getByName("release")
// SPEC §10.6: R8 shrinks, optimises and obfuscates the release APK; the libraries' consumer rules plus
// proguard-rules.pro. publish-apk.sh keeps build/outputs/mapping/release/mapping.txt next to the APK.
isMinifyEnabled = true
isShrinkResources = true
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
}
}
sourceSets {
// The pinned-root network security config (and the committed kvitt_root_ca.crt in src/release/res/raw) only
// when kvitt.tls.pinnedRoot=true; otherwise src/main's system-anchors config applies.
if (pinnedRoot) getByName("release").res.directories.add("src/releasePinnedRoot/res")
}
}
roborazzi {
outputDir.set(file("src/test/screenshots"))
}
dependencies {
implementation(project(":core:model"))
implementation(project(":core:design"))
implementation(project(":domain"))
implementation(project(":data"))
implementation(project(":adapter:fcm"))
implementation(project(":feature:onboarding"))
implementation(project(":feature:home"))
implementation(project(":feature:account"))
implementation(project(":feature:expense"))
implementation(project(":feature:settle"))
implementation(project(":feature:analytics"))
implementation(project(":feature:settings"))
testImplementation(project(":core:fakes"))
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.activity.compose)
implementation(libs.androidx.navigation.compose)
implementation(libs.androidx.hilt.lifecycle.viewmodel.compose)
implementation(libs.androidx.lifecycle.runtime.compose)
implementation(libs.androidx.lifecycle.viewmodel.compose)
implementation(libs.androidx.lifecycle.process)
implementation(libs.androidx.work.runtime.ktx)
implementation(libs.kotlinx.coroutines.core)
implementation(libs.kotlinx.datetime)
implementation(libs.coil)
implementation(libs.coil.compose)
debugImplementation(libs.androidx.compose.ui.test.manifest)
testImplementation(libs.androidx.compose.ui.test.junit4)
testImplementation(libs.kotlinx.coroutines.test)
testImplementation(libs.robolectric)
testImplementation(libs.roborazzi)
testImplementation(libs.roborazzi.compose)
testImplementation(libs.roborazzi.junit.rule)
}