The app module was a design gallery: tokens, type and a dial, with no backend, no notification and nothing driving the rear matrix. This is the rest of it. **One engine, three views.** `TimerEngine` lives on the Application, not in a ViewModel, because the Compose screen, the foreground service's notification and the Glyph must show the *same* second. Two engines polling the same backend drift apart within seconds and the user sees a notification disagreeing with the dial. It polls, ticks from a monotonic anchor, and puts every reply through `:core`'s `reconcile`. `pomodoro.state()` does not finish an interval whose time is up — it reports `remaining_seconds = 0` and waits for a client to call `complete`. The tick loop does, behind the same re-entry guard `desktop/controller.py` uses, since without one a 1 Hz tick at zero fires `complete` every second until the reply lands. **Notifications.** Two channels, because the running interval must be silent and its *end* must not; one channel cannot be both, and a user who silences a buzzing countdown has silenced the only part that mattered. The ongoing one is an Android 16 Live Update: promotion needs `setOngoing` + `setRequestPromotedOngoing` + a `ProgressStyle`, and missing any one renders it as an ordinary notification with no error and no log. Its segments come from `:core`'s `dialTickCount`, so the status bar, the dial and the Glyph ring agree on what one tick is worth. **Glyph.** `GlyphMatrixManager.init()` binds `com.nothing.thirdparty/.GlyphService` and *discards* the boolean `bindService` returns — so on Android 11+ a missing `<queries>` entry makes the package invisible, the bind silently fail, and the matrix stay dark with no exception, no log and `onServiceConnected` never arriving. That was the whole of "the glyph does not work". The manifest now declares it and `MatrixGlyphDisplay` checks visibility itself rather than leaving the next person with the same dead end. The SDK's licence forbids redistribution (clause 2.1(c)), so the .aar stays git-ignored and `fetch-glyph-sdk.sh` downloads it. Without it a second source set compiles a no-op display: the app builds, installs and runs with an inert rear panel, which is also the correct behaviour on any phone that is not a Nothing. Verified on the device (Phone (3), Android 16), debug and release: onboarding against kadence.timka.su, the Live Update with `shortCriticalText` and a 600s progress track, the matrix accepting 625-dot frames, tasks opening in the Jira app, and R8 leaving the DTOs decodable. 225 tests, none needing a device. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
58 lines
3.2 KiB
TOML
58 lines
3.2 KiB
TOML
[versions]
|
|
agp = "9.4.1"
|
|
kotlin = "2.4.21"
|
|
# compileSdk 37 because Compose 1.12 (BOM 2026.09.00) requires it; the platform is
|
|
# published under the new minor-version scheme as `platforms;android-37.0`.
|
|
# targetSdk stays 36 — Android 16 is what the Nothing Phone (3) runs, and opting into
|
|
# a newer runtime's behaviour changes is a separate decision from compiling against it.
|
|
compileSdk = "37"
|
|
targetSdk = "36"
|
|
minSdk = "34"
|
|
composeBom = "2026.09.00"
|
|
activityCompose = "1.13.0"
|
|
navigationCompose = "2.10.2"
|
|
lifecycle = "2.11.0"
|
|
coroutines = "1.10.2"
|
|
robolectric = "4.14.1"
|
|
serialization = "1.11.0"
|
|
okhttp = "5.5.0"
|
|
junit = "4.13.2"
|
|
# 1.19.1 is what carries NotificationCompat.ProgressStyle and
|
|
# setRequestPromotedOngoing — the Android 16 Live Update API, via the compat layer
|
|
# rather than raw platform calls behind SDK_INT checks.
|
|
androidxCore = "1.19.1"
|
|
|
|
[libraries]
|
|
kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" }
|
|
junit = { module = "junit:junit", version.ref = "junit" }
|
|
robolectric = { module = "org.robolectric:robolectric", version.ref = "robolectric" }
|
|
coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "coroutines" }
|
|
serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "serialization" }
|
|
okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
|
|
mockwebserver = { module = "com.squareup.okhttp3:mockwebserver3-junit4", version.ref = "okhttp" }
|
|
|
|
compose-bom = { module = "androidx.compose:compose-bom", version.ref = "composeBom" }
|
|
compose-ui = { module = "androidx.compose.ui:ui" }
|
|
compose-ui-graphics = { module = "androidx.compose.ui:ui-graphics" }
|
|
compose-ui-tooling = { module = "androidx.compose.ui:ui-tooling" }
|
|
compose-ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview" }
|
|
compose-ui-test-junit4 = { module = "androidx.compose.ui:ui-test-junit4" }
|
|
compose-ui-test-manifest = { module = "androidx.compose.ui:ui-test-manifest" }
|
|
compose-foundation = { module = "androidx.compose.foundation:foundation" }
|
|
compose-material3 = { module = "androidx.compose.material3:material3" }
|
|
|
|
activity-compose = { module = "androidx.activity:activity-compose", version.ref = "activityCompose" }
|
|
navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "navigationCompose" }
|
|
lifecycle-viewmodel-compose = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "lifecycle" }
|
|
lifecycle-runtime-compose = { module = "androidx.lifecycle:lifecycle-runtime-compose", version.ref = "lifecycle" }
|
|
lifecycle-service = { module = "androidx.lifecycle:lifecycle-service", version.ref = "lifecycle" }
|
|
|
|
androidx-core = { module = "androidx.core:core-ktx", version.ref = "androidxCore" }
|
|
coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "coroutines" }
|
|
|
|
[plugins]
|
|
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
|
|
android-application = { id = "com.android.application", version.ref = "agp" }
|
|
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
|
|
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
|