Files
agendula/CONTRIBUTING.md
T
Jean-Luc Makiolaandmakiolaj ac01993d41 Release 1.0.0 (#20)
First stable release. Merging this bumps versionName to 1.0.0 and triggers the release pipeline (F-Droid, Codeberg, Play).

**App**
- CalDAV sync built in, with Agendula's own task store; OpenTasks / tasks.org stay available and can be copied over in Settings → Storage
- repeating tasks, several reminders per task, lists managed in the app, iCalendar import/export, widget and Quick Settings tile
- a list can be kept out of the smart lists (#18) and gets its own notification channel (#17)
- duplicate a task with its subtasks (#16)
- HTML descriptions shown as plain text (#15)
- relative day words in reminder notifications (#14)
- asks for exact-alarm access instead of claiming USE_EXACT_ALARM, and re-arms reminders when that access changes

**Release plumbing**
- floret-kit bumped to v0.4.0; the old pin was never pushed, so a clean clone couldn't check out the submodule. 0.4.0 drops CrashConfig.issueTitle (crash issues are always filed in English)
- prebuilt .so files ship unstripped, so the build no longer depends on whether an NDK is installed; now checked by check_reproducible_release.sh
- official F-Droid recipe in docs/fdroid-official/, to submit to fdroiddata once v1.0.0 is tagged
- Google Play: fastlane uploads the AAB and every locale's What's New after the F-Droid release; a separate listing lane pushes text and graphics from the fastlane tree, which CI now checks against Play's limits
- store listing: title "Agendula: Tasks" in every locale, icon, feature graphic, screenshots and 1.0.0 changelogs in en-US, en-GB, de-DE and pt-BR

crash_report_issue_title is now unused but stays until Weblate removes the translated copies.

Closes #14, closes #15, closes #16, closes #17, closes #18

Co-authored-by: Jean-Luc Makiola <business@jeanlucmakiola.de>
Reviewed-on: https://codeberg.org/jlmakiola/agendula/pulls/20
2026-09-24 16:36:49 +02:00

116 lines
5.3 KiB
Markdown

# Contributing to Agendula
Thanks for your interest in Agendula — a Material 3 Expressive task app with its
own Room task store and its own CalDAV sync, which can also work on top of the
OpenTasks `TaskContract` provider. This file covers the practical how.
## The one architectural rule
Everything above the data layer talks to `TasksRepository` and sees only domain
types and Flows. **Room entities, provider column names, `TaskContract`,
`ContentResolver` and the authority string never leak above `data/tasks/`.**
That seam is what let the store move from the provider to Room without touching
a screen, and what lets both stores sit behind one `TasksDataSource`. If a
change would expose storage details to a ViewModel or the UI, it's in the wrong
layer.
## Prerequisites
- JDK 17
- Android SDK: compileSdk 37, build-tools 36.0.0 (the Gradle wrapper handles AGP/Kotlin)
- Any device or emulator for the default path — the store is Agendula's own.
Debug builds seed an "Agendula Demo" list via `DemoSeeder`. For External
mode, one with **OpenTasks** or **tasks.org** installed; for sync, a CalDAV
account (a local Radicale is the quickest).
- Clone with `--recurse-submodules`: the `floret-kit` component library is a
submodule.
## 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 Agendula on Weblate](https://weblate.dev.jeanlucmakiola.de/engage/agendula/)**
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. |
| Task storage | `data/tasks/` (`room/` for our own store) | The only place that knows about Room or the provider. New storage work goes through `TasksDataSource`, for both stores. |
| CalDAV sync | `data/sync/` | Accounts, the engine, scheduling and sync notices. |
| 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, built from **floret-kit** components
first (`CollapsingScaffold`, `GroupedRow`, `InlineTextField`,
`FullScreenPicker` / `OptionPicker`, …) and colour-scheme tokens (never
hardcoded colours). If a floret-kit component is nearly right, add the
parameter there rather than dropping to raw Material 3.
- Prefer the domain layer for anything 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 Android: the Room
data source, migrations and the import paths live under `app/src/androidTest/`.
## 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)).
- 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
Agendula stays on **open** standards: CalDAV and iCalendar, through its own
sync or through the OpenTasks provider (OpenTasks and tasks.org). Proprietary
backends (Google Tasks, Microsoft To Do) are out of scope by design. jtx's
richer contract is a possible later addition.
## License
By contributing you agree your contributions are licensed under the project's
[MIT License](LICENSE).