docs: project README, changelog, and M0 marked done
The README leads with the thing that is actually different about this app rather than burying it: Calendula and Agendula front standards someone else maintains, and Clockula cannot, because there is no open provider behind a clock. Saying so plainly is better than implying a thesis the app does not meet — and the three things that replace it (the AlarmClock contract, IANA zones, an exportable format) are real commitments, not consolation. CONTRIBUTING's scope section now names the trade this project will not make: nothing ships that buys a feature by making an alarm less certain to ring. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L94fydiJC37LtxVusNQBDy
This commit is contained in:
co-authored by
Claude Opus 5
parent
97ed6cf6f5
commit
7b7b730219
@@ -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`.
|
||||
+123
@@ -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 `<locale>` 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/<area>/` | 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).
|
||||
@@ -0,0 +1,78 @@
|
||||
<div align="center">
|
||||
|
||||
<h1>Clockula</h1>
|
||||
|
||||
<p><strong>A modern Material 3 Expressive clock app for Android.</strong><br>
|
||||
Alarms, timers, stopwatch and world clock. Open standards, no account required.</p>
|
||||
|
||||
<a href="https://codeberg.org/jlmakiola/clockula/actions"><img src="https://codeberg.org/jlmakiola/clockula/actions/workflows/ci.yaml/badge.svg?branch=main" alt="CI"></a>
|
||||
<img src="https://img.shields.io/badge/Android-10%2B-3DDC84?logo=android&logoColor=white" alt="Android 10+">
|
||||
<img src="https://img.shields.io/badge/Kotlin-Compose-7F52FF?logo=kotlin&logoColor=white" alt="Kotlin + Compose">
|
||||
<img src="https://img.shields.io/badge/Material%203-Expressive-4285F4" alt="Material 3 Expressive">
|
||||
<a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-green" alt="MIT License"></a>
|
||||
|
||||
</div>
|
||||
|
||||
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).
|
||||
+12
-2
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user