All checks were successful
CI / ci (push) Successful in 4m15s
The repository registers a ContentObserver on the calendar provider eagerly in its init block, and an activity-scoped SettingsViewModel (which drives the theme) injects that repository — so the @Singleton is constructed at launch, above RootScreen's permission gate. On newer Android, registering an observer on a provider you lack permission for throws SecurityException instead of silently no-op'ing, so the app crashed instantly on every launch whenever calendar access wasn't granted (fresh install or revoked permission), before the permission screen could ever appear. Guard the registration behind a calendar-permission check and re-attach the observer lazily on the first calendars()/instances() read, which runs once the gate opens and screens subscribe. Access to the observer collections is now synchronized since registration can happen on the main thread (repo init) or the IO dispatcher (query re-attach). Verified on-device: permission-denied launch shows the permission screen instead of crashing; granting it proceeds to the calendar with live updates. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
148 lines
4.7 KiB
Kotlin
148 lines
4.7 KiB
Kotlin
import java.util.Properties
|
|
import java.io.FileInputStream
|
|
|
|
plugins {
|
|
alias(libs.plugins.android.application)
|
|
alias(libs.plugins.kotlin.compose)
|
|
alias(libs.plugins.ksp)
|
|
alias(libs.plugins.hilt)
|
|
}
|
|
|
|
val keystorePropertiesFile = rootProject.file("key.properties")
|
|
val keystoreProperties = Properties().apply {
|
|
if (keystorePropertiesFile.exists()) {
|
|
load(FileInputStream(keystorePropertiesFile))
|
|
}
|
|
}
|
|
|
|
android {
|
|
namespace = "de.jeanlucmakiola.calendula"
|
|
compileSdk = 37
|
|
|
|
defaultConfig {
|
|
applicationId = "de.jeanlucmakiola.calendula"
|
|
minSdk = 29
|
|
targetSdk = 36
|
|
// The git tag is the single source of truth for released builds: at
|
|
// release time .gitea/workflows/release.yaml derives both fields from
|
|
// the tag, with versionCode = MAJOR*10000 + MINOR*100 + PATCH
|
|
// (e.g. v2.0.0 -> 20000). These committed values are the dev/local
|
|
// default; keep them matching the latest released tag. See docs/RELEASING.md.
|
|
versionCode = 20701
|
|
versionName = "2.7.1"
|
|
|
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
|
}
|
|
|
|
signingConfigs {
|
|
if (keystorePropertiesFile.exists()) {
|
|
create("release") {
|
|
keyAlias = keystoreProperties["keyAlias"] as String
|
|
keyPassword = keystoreProperties["keyPassword"] as String
|
|
storeFile = file(keystoreProperties["storeFile"] as String)
|
|
storePassword = keystoreProperties["storePassword"] as String
|
|
}
|
|
}
|
|
}
|
|
|
|
buildTypes {
|
|
release {
|
|
isMinifyEnabled = true
|
|
isShrinkResources = true
|
|
proguardFiles(
|
|
getDefaultProguardFile("proguard-android-optimize.txt"),
|
|
"proguard-rules.pro"
|
|
)
|
|
if (keystorePropertiesFile.exists()) {
|
|
signingConfig = signingConfigs.getByName("release")
|
|
}
|
|
}
|
|
debug {
|
|
applicationIdSuffix = ".debug"
|
|
isMinifyEnabled = false
|
|
}
|
|
}
|
|
|
|
compileOptions {
|
|
sourceCompatibility = JavaVersion.VERSION_17
|
|
targetCompatibility = JavaVersion.VERSION_17
|
|
}
|
|
|
|
buildFeatures {
|
|
compose = true
|
|
}
|
|
|
|
packaging {
|
|
resources {
|
|
excludes += "/META-INF/{AL2.0,LGPL2.1}"
|
|
}
|
|
}
|
|
|
|
lint {
|
|
// Community translations are expected to be partial — a missing string
|
|
// falls back to the English base at runtime — so don't fail the build on
|
|
// it. Stale/extra keys (ExtraTranslation) stay fatal; scripts/
|
|
// check_translations.py guards the same invariants with clearer,
|
|
// translator-facing messages.
|
|
informational += "MissingTranslation"
|
|
}
|
|
|
|
testOptions {
|
|
unitTests {
|
|
all { it.useJUnitPlatform() }
|
|
isReturnDefaultValues = true
|
|
}
|
|
}
|
|
}
|
|
|
|
kotlin {
|
|
compilerOptions {
|
|
jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17
|
|
}
|
|
}
|
|
|
|
dependencies {
|
|
implementation(libs.androidx.core.ktx)
|
|
implementation(libs.androidx.appcompat)
|
|
implementation(libs.androidx.lifecycle.runtime.ktx)
|
|
implementation(libs.androidx.lifecycle.runtime.compose)
|
|
implementation(libs.androidx.activity.compose)
|
|
|
|
implementation(platform(libs.androidx.compose.bom))
|
|
implementation(libs.androidx.ui)
|
|
implementation(libs.androidx.ui.graphics)
|
|
implementation(libs.androidx.ui.tooling.preview)
|
|
implementation(libs.androidx.material3)
|
|
implementation(libs.androidx.compose.material.icons.core)
|
|
implementation(libs.androidx.compose.material.icons.extended)
|
|
|
|
implementation(libs.hilt.android)
|
|
implementation(libs.androidx.hilt.navigation.compose)
|
|
ksp(libs.hilt.compiler)
|
|
|
|
implementation(libs.androidx.datastore.preferences)
|
|
|
|
implementation(libs.androidx.glance.appwidget)
|
|
implementation(libs.androidx.glance.material3)
|
|
|
|
implementation(libs.kotlinx.datetime)
|
|
implementation(libs.kotlinx.coroutines.core)
|
|
|
|
debugImplementation(libs.androidx.ui.tooling)
|
|
debugImplementation(libs.androidx.ui.test.manifest)
|
|
|
|
testImplementation(libs.junit.jupiter.api)
|
|
testRuntimeOnly(libs.junit.jupiter.engine)
|
|
testRuntimeOnly(libs.junit.platform.launcher)
|
|
testImplementation(libs.truth)
|
|
testImplementation(libs.turbine)
|
|
testImplementation(libs.kotlinx.coroutines.test)
|
|
|
|
androidTestImplementation(libs.androidx.junit)
|
|
androidTestImplementation(libs.androidx.espresso.core)
|
|
androidTestImplementation(libs.androidx.test.rules)
|
|
androidTestImplementation(libs.truth)
|
|
androidTestImplementation(platform(libs.androidx.compose.bom))
|
|
androidTestImplementation(libs.androidx.ui.test.junit4)
|
|
}
|