Six widgets, each with minimal settings, styled with M3 Expressive shapes and Material You colours, and sized proportionally to the placed widget (SizeMode.Exact + LocalSize): - Analog clock (new): nine faces on MaterialShapes dials (Round, Numeral, Day, Cookie, Sunny, Scallop, Clover, Square, Petal) with per-face hands, a seconds dot and Google-Clock-style orbiting date on some faces, and a day/date pill on others. Style picker previews inflate the real views. - Digital clock: rebuilt as RemoteViews faces (Classic, Thin, Bold, Pill, Stacked) shared by the widget and its config previews; world clocks are opt-in and only on the card faces. - Next alarm and Timers: shape badges, icon buttons, proportional readouts; a lone timer on a tall widget gets a stacked layout. - Stopwatch (new): large readout with play/pause pill and lap/reset. - Quick timer (new): a Scallop that is a timer of its own — tap to start a transient timer of its configured duration, tap again to reset/stop. Colours resolve in the launcher (widget_* palette resources, GlanceTheme's dynamic colours) so widgets follow day/night and the wallpaper. Data widgets collect their flows inside the composition so a live Glance session never shows stale state, chronometer bases use elapsedRealtime, and config activities close cleanly on an invalid widget id.
222 lines
8.4 KiB
Kotlin
222 lines
8.4 KiB
Kotlin
import java.util.Properties
|
|
import java.io.FileInputStream
|
|
|
|
plugins {
|
|
alias(libs.plugins.android.application)
|
|
alias(libs.plugins.kotlin.compose)
|
|
alias(libs.plugins.kotlin.serialization)
|
|
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.clockula"
|
|
compileSdk = 37
|
|
|
|
defaultConfig {
|
|
applicationId = "de.jeanlucmakiola.clockula"
|
|
minSdk = 29
|
|
targetSdk = 36
|
|
// These committed values ARE the source of truth for a release: merging
|
|
// a bumped versionName into main triggers .gitea/workflows/release.yaml,
|
|
// which builds this version and then creates the matching vX.Y.Z tag +
|
|
// release itself (versionCode is pinned to MAJOR*10000 + MINOR*100 +
|
|
// PATCH from versionName, e.g. 0.2.0 -> 200). The Gitea release is marked
|
|
// as a pre-release while MAJOR is 0. See docs/RELEASING.md.
|
|
versionCode = 100
|
|
versionName = "0.1.0"
|
|
|
|
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 {
|
|
// Keep release builds reproducible for F-Droid: don't let AGP embed
|
|
// build-environment git metadata (META-INF/version-control-info.textproto),
|
|
// whose `revision`/path content varies by build machine and is the only
|
|
// thing that otherwise differs from a clean from-source rebuild.
|
|
vcsInfo { include = false }
|
|
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
|
|
}
|
|
// A locally-installable twin of `release`: same R8 shrinking + obfuscation
|
|
// and resource shrinking, but debug-signed and given its own applicationId
|
|
// suffix so it installs alongside both the production app (signed with the
|
|
// real key) and the debug build. Used to smoke-test a release candidate on
|
|
// a real device before merging to main — R8-only breakage and first-run/
|
|
// permission states don't surface in the unminified debug build, nor on a
|
|
// device that already holds the permission. Never published. See
|
|
// docs/RELEASING.md.
|
|
create("releaseTest") {
|
|
initWith(getByName("release"))
|
|
applicationIdSuffix = ".releasetest"
|
|
signingConfig = signingConfigs.getByName("debug")
|
|
isMinifyEnabled = true
|
|
isShrinkResources = true
|
|
matchingFallbacks += "release"
|
|
}
|
|
}
|
|
|
|
compileOptions {
|
|
sourceCompatibility = JavaVersion.VERSION_17
|
|
targetCompatibility = JavaVersion.VERSION_17
|
|
}
|
|
|
|
buildFeatures {
|
|
compose = true
|
|
buildConfig = true
|
|
}
|
|
|
|
// Don't embed AGP's dependency-metadata block in the APK signing block. It's
|
|
// a Play-oriented blob, and F-Droid's reproducible-build scanner rejects any
|
|
// "extra signing block" — so leaving it in blocks publishing to the official
|
|
// repo. It lives in the signing block, not the zip entries, so disabling it
|
|
// doesn't change the build output (reproducibility is unaffected).
|
|
dependenciesInfo {
|
|
includeInApk = false
|
|
includeInBundle = false
|
|
}
|
|
|
|
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. Likewise a translated <plurals> may not fill every CLDR quantity
|
|
// form its locale defines (e.g. Arabic needs "zero"); the missing form
|
|
// falls back to "other" at runtime, so MissingQuantity is informational
|
|
// too. Stale/extra keys (ExtraTranslation) stay fatal; scripts/
|
|
// check_translations.py guards the same invariants with clearer,
|
|
// translator-facing messages.
|
|
informational += listOf("MissingTranslation", "MissingQuantity")
|
|
}
|
|
|
|
// The exported schema doubles as an androidTest asset, so M3's
|
|
// MigrationTestHelper can open a v1 database on the device.
|
|
sourceSets {
|
|
getByName("androidTest") {
|
|
assets.srcDir("$projectDir/schemas")
|
|
}
|
|
}
|
|
|
|
testOptions {
|
|
unitTests {
|
|
all { it.useJUnitPlatform() }
|
|
isReturnDefaultValues = true
|
|
}
|
|
}
|
|
}
|
|
|
|
// Export the Room schema to a committed directory: migrations are only
|
|
// reviewable, and only testable, against a recorded previous version.
|
|
ksp {
|
|
arg("room.schemaLocation", "$projectDir/schemas")
|
|
}
|
|
|
|
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.material3.adaptive.navigation.suite)
|
|
implementation(libs.androidx.graphics.shapes)
|
|
implementation(libs.androidx.compose.material.icons.core)
|
|
implementation(libs.androidx.compose.material.icons.extended)
|
|
|
|
implementation(libs.hilt.android)
|
|
implementation(libs.androidx.hilt.navigation.compose)
|
|
implementation(libs.androidx.navigation.compose)
|
|
ksp(libs.hilt.compiler)
|
|
|
|
implementation(libs.androidx.datastore.preferences)
|
|
implementation(libs.androidx.documentfile)
|
|
|
|
// Clockula owns its storage — see docs/PLAN.md §0. Nothing above the data
|
|
// layer may import a Room type.
|
|
implementation(libs.androidx.room.runtime)
|
|
implementation(libs.androidx.room.ktx)
|
|
ksp(libs.androidx.room.compiler)
|
|
|
|
implementation(libs.kotlinx.datetime)
|
|
implementation(libs.kotlinx.coroutines.core)
|
|
implementation(libs.kotlinx.serialization.json)
|
|
|
|
// M10 home-screen widgets (docs/PLAN.md §9).
|
|
implementation(libs.androidx.glance.appwidget)
|
|
implementation(libs.androidx.glance.material3)
|
|
|
|
implementation("de.jeanlucmakiola.floret:core-time")
|
|
implementation("de.jeanlucmakiola.floret:core-locale")
|
|
implementation("de.jeanlucmakiola.floret:core-crash")
|
|
implementation("de.jeanlucmakiola.floret:core-prefs")
|
|
implementation("de.jeanlucmakiola.floret:core-di")
|
|
implementation("de.jeanlucmakiola.floret:identity")
|
|
implementation("de.jeanlucmakiola.floret:components")
|
|
implementation("de.jeanlucmakiola.floret:glance")
|
|
|
|
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)
|
|
testImplementation(libs.androidx.lifecycle.viewmodel.savedstate)
|
|
|
|
androidTestImplementation(libs.androidx.room.testing)
|
|
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)
|
|
}
|