plugins { id("com.android.application") id("org.jetbrains.kotlin.android") id("org.jetbrains.kotlin.plugin.compose") id("com.google.gms.google-services") id("com.google.firebase.crashlytics") id("com.google.dagger.hilt.android") id("com.google.devtools.ksp") } android { namespace = "app.closer" compileSdk = 35 defaultConfig { applicationId = "closer.app" minSdk = 26 targetSdk = 35 versionCode = 1 versionName = "0.1.0" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" // RevenueCat API key. Set RC_API_KEY in local.properties (never committed). // Debug builds fall back to a placeholder; release builds abort — see task guard below. buildConfigField( "String", "RC_API_KEY", "\"${properties["RC_API_KEY"]?.toString() ?: System.getenv("RC_API_KEY") ?: "PLACEHOLDER_RC_API_KEY"}\"" ) } buildFeatures { buildConfig = true compose = true } signingConfigs { create("release") { // Set these four properties in local.properties (never commit) or as environment // variables in CI. RELEASE_STORE_FILE is the path to the .jks / .keystore file, // relative to the project root. val storeFilePath = (findProperty("RELEASE_STORE_FILE") as? String)?.takeIf { it.isNotBlank() } ?: System.getenv("RELEASE_STORE_FILE")?.takeIf { it.isNotBlank() } val storePwd = (findProperty("RELEASE_STORE_PASSWORD") as? String)?.takeIf { it.isNotBlank() } ?: System.getenv("RELEASE_STORE_PASSWORD")?.takeIf { it.isNotBlank() } val keyAliasVal = (findProperty("RELEASE_KEY_ALIAS") as? String)?.takeIf { it.isNotBlank() } ?: System.getenv("RELEASE_KEY_ALIAS")?.takeIf { it.isNotBlank() } val keyPwd = (findProperty("RELEASE_KEY_PASSWORD") as? String)?.takeIf { it.isNotBlank() } ?: System.getenv("RELEASE_KEY_PASSWORD")?.takeIf { it.isNotBlank() } if (storeFilePath != null && storePwd != null && keyAliasVal != null && keyPwd != null) { storeFile = rootProject.file(storeFilePath) storePassword = storePwd keyAlias = keyAliasVal keyPassword = keyPwd } } } buildTypes { debug { // Stable debug token registered in Firebase Console > App Check. // Pre-seeded into SharedPreferences by FirebaseInitializer so all installs // use the same token without manual re-registration. buildConfigField("String", "APP_CHECK_DEBUG_TOKEN", "\"e2dc8256-403f-449b-846e-76614a7297cc\"") } release { buildConfigField("String", "APP_CHECK_DEBUG_TOKEN", "\"\"") signingConfig = signingConfigs.getByName("release") isMinifyEnabled = true isShrinkResources = true proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" ) } } compileOptions { sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 } kotlinOptions { jvmTarget = "17" } packaging { resources.excludes += "META-INF/versions/9/OSGI-INF/MANIFEST.MF" } testOptions { unitTests { isReturnDefaultValues = true isIncludeAndroidResources = true } } } // Abort any release assemble/bundle task when required credentials are absent. // This runs at execution time so debug builds are never affected. tasks.matching { it.name.let { n -> (n.startsWith("assemble") || n.startsWith("bundle")) && n.contains("Release", ignoreCase = true) }}.configureEach { doFirst { val key = (findProperty("RC_API_KEY") as? String)?.takeIf { it.isNotBlank() } ?: System.getenv("RC_API_KEY")?.takeIf { it.isNotBlank() } if (key == null || key == "PLACEHOLDER_RC_API_KEY") { throw GradleException( "RC_API_KEY is not set. Add it to local.properties or export RC_API_KEY before running a release build." ) } val storeFilePath = (findProperty("RELEASE_STORE_FILE") as? String)?.takeIf { it.isNotBlank() } ?: System.getenv("RELEASE_STORE_FILE")?.takeIf { it.isNotBlank() } if (storeFilePath == null) { throw GradleException( "Release signing is not configured. Set RELEASE_STORE_FILE, RELEASE_STORE_PASSWORD, " + "RELEASE_KEY_ALIAS, and RELEASE_KEY_PASSWORD in local.properties or as environment variables." ) } } } ksp { arg("room.schemaLocation", "$projectDir/schemas") } dependencies { val composeBom = platform("androidx.compose:compose-bom:2025.01.01") implementation(composeBom) implementation("androidx.core:core-ktx:1.15.0") implementation("androidx.core:core-splashscreen:1.0.1") implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.8.7") implementation("androidx.activity:activity-compose:1.9.3") // Compose implementation("androidx.compose.ui:ui") implementation("androidx.compose.ui:ui-graphics") implementation("androidx.compose.ui:ui-tooling-preview") implementation("androidx.compose.material3:material3") implementation("androidx.compose.material:material-icons-extended") // Navigation implementation("androidx.navigation:navigation-compose:2.8.5") // ViewModel implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.8.7") // Firebase implementation(platform("com.google.firebase:firebase-bom:33.8.0")) implementation("com.google.firebase:firebase-auth-ktx") implementation("com.google.firebase:firebase-firestore-ktx") implementation("com.google.firebase:firebase-messaging-ktx") implementation("com.google.firebase:firebase-config-ktx") implementation("com.google.firebase:firebase-analytics-ktx") implementation("com.google.firebase:firebase-crashlytics-ktx") implementation("com.google.firebase:firebase-appcheck-ktx") implementation("com.google.firebase:firebase-appcheck-playintegrity") debugImplementation("com.google.firebase:firebase-appcheck-debug") // Hilt implementation("com.google.dagger:hilt-android:2.53.1") ksp("com.google.dagger:hilt-android-compiler:2.53.1") implementation("androidx.hilt:hilt-navigation-compose:1.2.0") // Room implementation("androidx.room:room-runtime:2.6.1") implementation("androidx.room:room-ktx:2.6.1") ksp("androidx.room:room-compiler:2.6.1") // DataStore implementation("androidx.datastore:datastore-preferences:1.1.2") // Encrypted storage implementation("androidx.security:security-crypto:1.0.0") // Play Integrity API — runtime device integrity check implementation("com.google.android.play:integrity:1.4.0") // Firebase Functions — callable for server-side integrity token verification implementation("com.google.firebase:firebase-storage-ktx") implementation("com.google.firebase:firebase-functions-ktx") // RevenueCat native Android SDK (group: com.revenuecat.purchases, artifact: purchases) implementation("com.revenuecat.purchases:purchases:8.20.0") // Image loading implementation("io.coil-kt:coil-compose:2.7.0") // Animated GIF / WebP support (for GIFs, stickers, Bitmoji from the keyboard) implementation("io.coil-kt:coil-gif:2.7.0") // AppCompat — required by BiometricPrompt (needs FragmentActivity base) implementation("androidx.appcompat:appcompat:1.7.0") // Biometric auth — recovery phrase reveal + app-level login lock implementation("androidx.biometric:biometric:1.1.0") // Google Sign-In via Credential Manager implementation("androidx.credentials:credentials:1.3.0") implementation("androidx.credentials:credentials-play-services-auth:1.3.0") implementation("com.google.android.libraries.identity.googleid:googleid:1.1.1") // Coroutines implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.9.0") // E2EE: Google Tink (AEAD) + Bouncy Castle (Argon2id KDF) implementation("com.google.crypto.tink:tink-android:1.13.0") implementation("org.bouncycastle:bcprov-jdk18on:1.78.1") // Debug debugImplementation("androidx.compose.ui:ui-tooling") debugImplementation("androidx.compose.ui:ui-test-manifest") // Unit tests — JVM only (no device/emulator required) testImplementation("junit:junit:4.13.2") testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.9.0") testImplementation("io.mockk:mockk:1.13.14") // Real org.json on the JVM test classpath so codec round-trips run (Android's stubbed JSONObject // returns defaults under unitTests.isReturnDefaultValues). testImplementation("org.json:json:20240303") // Canonical-vector capture harness (paired-CI for iOS↔Android E2EE fixture fill) androidTestImplementation("androidx.test.ext:junit:1.1.5") androidTestImplementation("androidx.test:runner:1.5.2") // Instrumented Compose render smoke (first-run screens) — the on-device net for the // "composes fine, crashes on first paint" class (e.g. O-ONBOARD-001). Needs the BOM so the // ui-test version matches the app's Compose; ui-test-manifest (debug, above) hosts the ComposeRule. androidTestImplementation(composeBom) androidTestImplementation("androidx.compose.ui:ui-test-junit4") }