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
5.3 KiB
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: thefloret-kitcomponent library is a submodule.
Build, test, lint
./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
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
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.mdunder[Unreleased]for any user-visible change — its sections feed the release notes and F-Droid "What's New" (seedocs/RELEASING.md). - Don't bump
versionName/versionCodein a regular PR — the committedversionNameis bumped only when cutting a release (that bump reachingmainis what triggers the release; the pipeline then mints the tag). Seedocs/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.