diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..090d2e2 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,23 @@ +# Changelog + +All notable changes to Clockula are documented here. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +Tag sections feed the release notes — see [`docs/RELEASING.md`](docs/RELEASING.md). + +## [Unreleased] + +### Added +- Project skeleton: Gradle build, version catalog, Hilt, Compose and the Material 3 + Expressive theme over floret-kit's `identity` module, seeded `#6B7A5C` — the + third 120° hue rotation of Calendula's slate, completing the family's palette. +- floret-kit as a git submodule wired in as a Gradle composite build. +- Crash capture and the report surface via floret-kit's `core-crash`; per-app + language support via `core-locale`. +- Launcher and notification icons: a line-art clock face carrying the family's + Calendula bloom badge. +- Full release pipeline, Codeberg-canonical from the first commit — CI, + translations, release (F-Droid repo + Codeberg release + Google Play) and + Renovate, with the F-Droid reproducibility rules intact. +- The locked specification: `docs/PLAN.md` and `docs/ROADMAP.md`. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..c4b65d0 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,123 @@ +# Contributing to Clockula + +Thanks for your interest in Clockula — a Material 3 Expressive clock app: +alarms, timers, stopwatch and world clock. Unlike its siblings it owns its +storage, because there is no open provider behind a clock. Before diving in, skim [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) +(how it's built), [`docs/ROADMAP.md`](docs/ROADMAP.md) (what's next), and +[`docs/PLAN.md`](docs/PLAN.md) (the design rationale). This file covers the +practical how. + +## The one architectural rule + +Everything above the data layer talks to a repository and sees only domain types +and Flows. **Room entities, DAOs and `@Query` strings never leak above the data +layer.** Clockula owns its storage, which makes this rule more important here +than in the siblings, not less: it is the only thing keeping the schema a +private implementation detail rather than the app's de-facto domain model. If a +change would expose a Room type to a ViewModel or the UI, it's in the wrong +layer — see [`docs/PLAN.md`](docs/PLAN.md) §0. + +## Prerequisites + +- JDK 17 +- Android SDK: compileSdk 37, build-tools 36.0.0 (the Gradle wrapper handles AGP/Kotlin) +- A real device for anything touching the alarm engine. An emulator will not + tell you whether an alarm survives doze, a reboot, or an OEM battery-saver — + and that is the only question that matters about an alarm. + +## Build, test, lint + +```sh +./gradlew :app:assembleDebug # build the debug APK +./gradlew :app:testDebugUnitTest # JVM unit tests (JUnit5 + Truth + Turbine) +./gradlew lintDebug # Android lint (CI runs this on every PR) +``` + +CI (`.forgejo/workflows/ci.yaml`, on Codeberg) runs a reproducible-release invariant check, +then lint → unit tests → debug build on every pull request, so run these locally +before opening a PR. Keep CI green. + +## Translations + +**Never edit a `values-*/strings.xml` file in a pull request.** Translations are +owned by a self-hosted Weblate that writes to this repository directly, and a +hand-edit is overwritten on the next sync. + +→ **[Translate Clockula on Weblate](https://weblate.dev.jeanlucmakiola.de/engage/clockula/)** + +Adding a *new* English string to `values/strings.xml` is normal PR work; Weblate +picks it up and offers it to translators. Partial translations are expected and +fine — missing keys are informational. Stale and orphaned keys are not, so run + +```sh +python3 scripts/check_translations.py +``` + +before pushing. It reports those more clearly than lint's `MissingTranslation` +does, and it's what the `Translations` check runs on every PR. + +A new language also needs one `` line in +`app/src/main/res/xml/locales_config.xml` — that file is the single source of +truth for both the in-app picker and the Android 13+ per-app language setting. + +## Where to put code + +| Layer | Lives in | Rule of thumb | +|---|---|---| +| Pure logic (models, filtering, sorting, form validation, date maths) | `domain/` | No Android imports — must be JVM-unit-testable. | +| Provider access | `data/tasks/` | The only place that knows about the provider. New provider work goes through `TasksDataSource`. | +| Reminders, prefs, DI, demo data | `data/reminders/`, `data/prefs/`, `data/di/`, `data/demo/` | | +| Screens | `ui//` | One ViewModel + immutable `UiState` per area; Compose for the screen. | + +## Code style & conventions + +- **Kotlin**, 4-space indent, LF line endings, final newline, no trailing + whitespace — all enforced by `.editorconfig` (2-space for yaml/toml/json/md). + Match the surrounding code. +- **Material 3 Expressive** for all UI: use `MaterialExpressiveTheme`, the + colour-scheme tokens (never hardcoded colours), and canonical M3 components + (e.g. `ListItem` for rows). Consult the `material-3` skill before designing a + new screen or component. +- Prefer the domain layer for anything testable; keep `AndroidTasksDataSource` + the only Android-coupled data implementation so the rest stays JVM-testable. + +## Tests + +- New domain logic (mappers, filters, sorting, forms, value mapping) **must** + come with JVM unit tests under `app/src/test/`. The data source is the + JVM-testable seam — mock or fake it rather than reaching for instrumentation. +- Add an instrumented test only when a path genuinely needs a real + `ContentResolver`. + +## Commits & PRs + +- Write focused commits with clear messages (the existing history uses short, + scoped subjects like `UI: ...` / `M1 ...`). +- Update [`CHANGELOG.md`](CHANGELOG.md) under `[Unreleased]` for any + user-visible change — its sections feed the release notes and F-Droid "What's + New" (see [`docs/RELEASING.md`](docs/RELEASING.md)). +- If your change shifts the architecture or completes a milestone, update + [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) / [`docs/ROADMAP.md`](docs/ROADMAP.md) + in the same PR. +- Don't bump `versionName` / `versionCode` in a regular PR — the committed + `versionName` is bumped only when **cutting a release** (that bump reaching + `main` is what triggers the release; the pipeline then mints the tag). See + [`docs/RELEASING.md`](docs/RELEASING.md). + +## Scope + +v1 is four surfaces — alarms, timers, stopwatch, world clock — done properly, +and nothing else. Widgets, a Quick Settings tile, screensaver mode and a bedtime +schedule are all wanted, and all deliberately deferred until v1 is finished; see +[`docs/ROADMAP.md`](docs/ROADMAP.md). + +Two things are out of scope by design. **A sync stack of our own** — your alarms +export to a documented JSON file, and syncing them is a job for something that +already does syncing well. **Anything that makes an alarm less reliable in +exchange for a feature** — if a change trades away the certainty that an alarm +rings, it loses, however nice the feature is. + +## License + +By contributing you agree your contributions are licensed under the project's +[MIT License](LICENSE). diff --git a/README.md b/README.md new file mode 100644 index 0000000..d3f5776 --- /dev/null +++ b/README.md @@ -0,0 +1,78 @@ +
+ +

Clockula

+ +

A modern Material 3 Expressive clock app for Android.
+Alarms, timers, stopwatch and world clock. Open standards, no account required.

+ +CI +Android 10+ +Kotlin + Compose +Material 3 Expressive +MIT License + +
+ +Clockula is the clock sibling to [Calendula](https://codeberg.org/jlmakiola/calendula) +(calendar) and [Agendula](https://codeberg.org/jlmakiola/agendula) (tasks). The +name follows theirs: *clock*, given Calendula's `-ula` ending. A Calendula flower +head is botanically a cluster of many small *florets*, so the three apps are +florets of one bloom — and they share one, [floret-kit](https://codeberg.org/jlmakiola/floret-kit). + +> 🚧 **Early development.** The spec is locked; the app is being built. See +> [`docs/ROADMAP.md`](docs/ROADMAP.md) for where it actually is. + +## The honest difference from its siblings + +Calendula is a pure front-end over Android's `CalendarContract`. Agendula speaks +the dmfs `TaskContract`. Both put a good interface on a standard someone else +already maintains. + +**There is no open provider behind a clock.** Alarms, timers and laps are +Clockula's own data, so Clockula is the first app in the family to own its +storage — by design, not as a fallback. What that commitment is replaced with is +the first section of [`docs/PLAN.md`](docs/PLAN.md), and it comes down to three +things: + +- **Clockula speaks Android's standard alarm contract.** It implements + `android.provider.AlarmClock` in full, so your assistant and your automation + apps can set alarms and timers in it exactly as they would in any stock clock, + and the system status bar and lock screen show its next alarm. +- **World clocks are IANA time zones**, not a bespoke city list that goes stale. +- **Your data exports** to a documented, versioned JSON file whenever you want + it, because data you can't take with you isn't really yours. + +## Alarms that actually ring + +An alarm app is only worth having if you can trust it, so Clockula treats that as +the feature rather than the plumbing. Alarms are scheduled as real alarm clocks — +doze-exempt, visible to the system — and re-resolved against your current time +zone whenever anything moves underneath them, so DST and travel don't silently +shift them. + +When something on your device *would* stop an alarm ringing, Clockula tells you +which setting it is and takes you there, instead of failing quietly at 7am. + +## Documentation + +Start with [`docs/`](docs/): [`PLAN.md`](docs/PLAN.md) is the spec and the +reasoning, [`ROADMAP.md`](docs/ROADMAP.md) is the status, +[`ARCHITECTURE.md`](docs/ARCHITECTURE.md) is how the code is put together, and +[`RELEASING.md`](docs/RELEASING.md) is the release runbook. + +## Building + +```sh +git clone --recurse-submodules https://codeberg.org/jlmakiola/clockula.git +cd clockula +./gradlew assembleDebug +``` + +floret-kit is a submodule built from source as a Gradle composite build, so +`--recurse-submodules` is not optional. The included build needs to find the +Android SDK on its own: either export `ANDROID_HOME`, or drop a `sdk.dir=…` line +in a (gitignored) `floret-kit/local.properties`. + +## Licence + +MIT — see [`LICENSE`](LICENSE). diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 7b9455b..dc77dfd 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -10,7 +10,10 @@ Status legend: ✅ done · 🚧 in progress · ⬜ not started ## Current state (one line) -⬜ **Nothing built yet.** The spec is locked (`PLAN.md`); M0 is next. +✅ **M0 done.** The project builds, installs and shows a themed placeholder; +floret-kit is wired in as a composite build, the identity is seeded, and the +full Codeberg-canonical release pipeline is in place. **M1** (the `core-prefs` + +`core-di` extraction into floret-kit) is next. --- @@ -42,7 +45,7 @@ Rules that hold across every iteration: ## v1 milestones -### ⬜ M0 — Skeleton & pipeline +### ✅ M0 — Skeleton & pipeline The project exists, builds, is themed, and ships itself. - Gradle project: `settings.gradle.kts`, version catalog copied from Agendula (drop `:provider`, add Room + KSP), AGP 9.x conventions, `vcsInfo` off on @@ -59,6 +62,13 @@ The project exists, builds, is themed, and ships itself. `.gitignore`, `docs/README.md` index. - **Done when:** it builds, installs, shows a themed placeholder, and CI is green. + ✅ `assembleDebug`, `lintDebug` and `testDebugUnitTest` all pass; + `scripts/check_reproducible_release.sh` reports every invariant holding. + Two notes for later: the store listing and `fastlane` changelogs are written + but no screenshots exist yet (M11), and `docs/ARCHITECTURE.md` is still + unwritten by design — M2 writes it, once there is an architecture to + describe. + ### ⬜ M1 — floret-kit: `core-prefs` + `core-di` Pay the extraction the kit's roadmap has been waiting for a third app to trigger. - In the kit: `core-prefs` (`ThemeMode`, `dynamicColor`, typed DataStore wrapper,