Floret is promoted to the family / design-language (shared-kit) name; the
tasks app itself becomes Agendula (de.jeanlucmakiola.agendula) — agenda
('things to be done') + Calendula's -ula, a twin of the Calendula name.
Renames the package, namespace, applicationId, rootProject.name, app_name,
FloretApp/FloretNavHost/FloretTransitions classes, theme, F-Droid metadata
dir, CI artifact name, and docs. The botanical word 'florets' is preserved in
the name-origin prose, which is rewritten to Agendula's etymology. Clean
build + unit tests green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# Floret — architecture
|
||||
# Agendula — architecture
|
||||
|
||||
This document describes how Floret is built **as it stands today**. For the
|
||||
This document describes how Agendula is built **as it stands today**. For the
|
||||
*why* behind the big decisions and the long-term plan, see
|
||||
[`PLAN.md`](PLAN.md); for status and what's next, see [`ROADMAP.md`](ROADMAP.md).
|
||||
|
||||
@@ -8,10 +8,10 @@ This document describes how Floret is built **as it stands today**. For the
|
||||
|
||||
## 1. The thesis in one sentence
|
||||
|
||||
Floret is a Material 3 Expressive **front-end** over the OpenTasks
|
||||
Agendula is a Material 3 Expressive **front-end** over the OpenTasks
|
||||
`TaskContract` provider — it reads, writes, and reminds on top of a tasks store
|
||||
that some other app (DAVx5, SmoothSync, DecSync CC, tasks.org, …) syncs over
|
||||
CalDAV. **Floret owns no database and no sync stack.** It is the task-list
|
||||
CalDAV. **Agendula owns no database and no sync stack.** It is the task-list
|
||||
sibling to [Calendula](https://gitea.jeanlucmakiola.de/makiolaj/calendula),
|
||||
which does the same thing for `CalendarContract`.
|
||||
|
||||
@@ -69,7 +69,7 @@ Both are bound in Hilt in `data/di/DataModule.kt`.
|
||||
|
||||
## 3. Module & package layout
|
||||
|
||||
Single `:app` module (Posture A). Package root `de.jeanlucmakiola.floret`.
|
||||
Single `:app` module (Posture A). Package root `de.jeanlucmakiola.agendula`.
|
||||
|
||||
| Package | Contents |
|
||||
|---|---|
|
||||
@@ -80,7 +80,7 @@ Single `:app` module (Posture A). Package root `de.jeanlucmakiola.floret`.
|
||||
| `data/di/` | `DataModule` (binds + provides), `Qualifiers` (`@IoDispatcher`). |
|
||||
| `data/demo/` | `DemoSeeder` (debug-only sample data). |
|
||||
| `ui/` | `theme/`, `common/` (GroupedList, ListChip), `lists/`, `tasklist/`, `detail/`, `edit/`, `settings/`, `permission/` (each a ViewModel + UiState; `lists` also has its screen), `RootScreen`. |
|
||||
| root | `FloretApp` (Hilt app), `MainActivity`. |
|
||||
| root | `AgendulaApp` (Hilt app), `MainActivity`. |
|
||||
|
||||
---
|
||||
|
||||
@@ -106,7 +106,7 @@ runtime perms for the active provider.
|
||||
### 4.2 `TasksContract`
|
||||
|
||||
A vendored subset of the Apache-2.0 OpenTasks `TaskContract` — column names,
|
||||
table paths, status/priority constants, the local-account type. Floret does
|
||||
table paths, status/priority constants, the local-account type. Agendula does
|
||||
**not** take a runtime dependency on OpenTasks; the authority is injected from
|
||||
`ProviderResolver`, never hardcoded in the contract.
|
||||
|
||||
@@ -117,7 +117,7 @@ occurrence is a row with the joined list colour, account, etc.), maps each
|
||||
cursor row through `ColumnReader` → `TaskMapper` → domain `Task`, and exposes
|
||||
the result as a `Flow`. A `ContentObserver` on the active authority's
|
||||
Tasks/TaskLists URIs bridges into the Flow via `callbackFlow`, so **any change
|
||||
re-emits** — Floret's own writes *and* external sync (DAVx5 pulling new tasks)
|
||||
re-emits** — Agendula's own writes *and* external sync (DAVx5 pulling new tasks)
|
||||
update the UI live, and multiple sync sources coexist in one list.
|
||||
|
||||
### 4.4 Writes — repository API
|
||||
@@ -170,7 +170,7 @@ priority / etc.). None of this touches Android, which is why it's all in
|
||||
## 6. Reminders — the one subsystem that does NOT mirror Calendula
|
||||
|
||||
Calendula relies on the calendar provider broadcasting `EVENT_REMINDER`. **Tasks
|
||||
providers broadcast nothing**, so Floret schedules its own (`data/reminders/`):
|
||||
providers broadcast nothing**, so Agendula schedules its own (`data/reminders/`):
|
||||
|
||||
- **`ReminderScheduler.sync()`** reads upcoming, non-closed, due-dated tasks
|
||||
within a rolling **30-day window**, computes each trigger as `due − lead`
|
||||
@@ -186,7 +186,7 @@ providers broadcast nothing**, so Floret schedules its own (`data/reminders/`):
|
||||
sync changed the data). The store lets each run diff like Calendula diffs
|
||||
reminder rows.
|
||||
|
||||
This is the single largest piece of genuinely-new code in Floret.
|
||||
This is the single largest piece of genuinely-new code in Agendula.
|
||||
|
||||
---
|
||||
|
||||
@@ -201,7 +201,7 @@ This is the single largest piece of genuinely-new code in Floret.
|
||||
domain, and `TasksRepository` do not change** — only the resolver's default and
|
||||
some manifest perms.
|
||||
|
||||
Bundling the provider bundles **storage, not sync** — Floret stays a pure
|
||||
Bundling the provider bundles **storage, not sync** — Agendula stays a pure
|
||||
front-end over open backends either way.
|
||||
|
||||
---
|
||||
@@ -228,8 +228,8 @@ checkbox/FAB/swipe motion).
|
||||
|
||||
Hilt, `SingletonComponent`. `DataModule` has a `@Binds` module
|
||||
(`TasksDataSource` → `AndroidTasksDataSource`, `TasksRepository` →
|
||||
`TasksRepositoryImpl`) and a `@Provides` module (the `floret_prefs` DataStore,
|
||||
the `@IoDispatcher`). `FloretApp` is the `@HiltAndroidApp` entry point;
|
||||
`TasksRepositoryImpl`) and a `@Provides` module (the `agendula_prefs` DataStore,
|
||||
the `@IoDispatcher`). `AgendulaApp` is the `@HiltAndroidApp` entry point;
|
||||
`MainActivity` is `@AndroidEntryPoint`. ViewModels get the repository injected.
|
||||
|
||||
---
|
||||
|
||||
45
docs/PLAN.md
45
docs/PLAN.md
@@ -1,23 +1,25 @@
|
||||
# Floret — implementation plan
|
||||
# Agendula — implementation plan
|
||||
|
||||
> A modern Material 3 Expressive **task** app for Android. Reads, writes, and
|
||||
> reminds — on top of an existing tasks provider (synced by DAVx5 / SmoothSync /
|
||||
> DecSync over CalDAV), with no own sync stack.
|
||||
>
|
||||
> Sibling to **Calendula**. Calendula is a calendar over `CalendarContract`;
|
||||
> Floret is a to-do list over the **OpenTasks `TaskContract` provider**. A
|
||||
> Calendula flower head is made of many small *florets* — the individual items
|
||||
> that make up the bloom.
|
||||
> Agendula is a to-do list over the **OpenTasks `TaskContract` provider**. The
|
||||
> name mirrors Calendula's: *agenda* (Latin, “things to be done”) + the `-ula`
|
||||
> ending — and a Calendula flower head is itself a cluster of small *florets*,
|
||||
> so the two apps are florets of one bloom.
|
||||
|
||||
Working identifiers (placeholder, easily renamed):
|
||||
`applicationId = de.jeanlucmakiola.floret`, app name **Floret**.
|
||||
Identifiers: `applicationId = de.jeanlucmakiola.agendula`, app name **Agendula**
|
||||
(renamed from the working title *Floret*, which was promoted to the shared
|
||||
family / design-language name).
|
||||
|
||||
---
|
||||
|
||||
## 0. The thesis this app embodies
|
||||
|
||||
A nice M3-Expressive front end over open backends, no reinvented storage or
|
||||
sync. Calendula proved the pattern against the OS calendar provider. Floret
|
||||
sync. Calendula proved the pattern against the OS calendar provider. Agendula
|
||||
applies it to tasks. The crucial difference: **there is no OS tasks provider**,
|
||||
so we depend on a tasks *provider app* being present — exactly as Calendula
|
||||
depends on a sync app like DAVx5 for CalDAV.
|
||||
@@ -46,7 +48,7 @@ string leak above the data layer.**
|
||||
|
||||
Calendula's layering is the template. Lift these **verbatim or near-verbatim**:
|
||||
|
||||
| Area | From Calendula | Change for Floret |
|
||||
| Area | From Calendula | Change for Agendula |
|
||||
|---|---|---|
|
||||
| Gradle setup | `build.gradle.kts`, `settings.gradle.kts`, `gradle/libs.versions.toml`, wrapper, `key.properties` flow, versionCode-from-tag CI | namespace/appId only |
|
||||
| Build config | AGP 9.2.1, Kotlin 2.3.21, KSP, Hilt 2.59.2, compileSdk 37 / minSdk 29 / targetSdk 36, Java 17 | identical |
|
||||
@@ -65,7 +67,7 @@ Calendula's layering is the template. Lift these **verbatim or near-verbatim**:
|
||||
`TaskContract` data layer, the task screens, and a **self-scheduled reminder
|
||||
engine** (see §6 — the one place Calendula's pattern does *not* carry over).
|
||||
|
||||
### What does NOT exist here (why Floret is simpler than Calendula)
|
||||
### What does NOT exist here (why Agendula is simpler than Calendula)
|
||||
|
||||
No month/week/day grid rendering. No recurrence-scoped writes ("this & following"
|
||||
vs "whole series"). No timezone/all-day gymnastics. Tasks are a flat-or-lightly-
|
||||
@@ -76,7 +78,7 @@ nested list with a due date and a checkbox.
|
||||
## 2. Module & package layout
|
||||
|
||||
Single `:app` module for A (mirrors Calendula). Package root
|
||||
`de.jeanlucmakiola.floret`.
|
||||
`de.jeanlucmakiola.agendula`.
|
||||
|
||||
```
|
||||
domain/
|
||||
@@ -111,10 +113,10 @@ ui/
|
||||
edit/ TaskEditScreen + VM + UiState
|
||||
settings/ (copy + adapt)
|
||||
permission/ provider-presence + permission + POST_NOTIFICATIONS onboarding
|
||||
FloretHost.kt nav host (mirrors CalendarHost)
|
||||
AgendulaHost.kt nav host (mirrors CalendarHost)
|
||||
RootScreen.kt
|
||||
widget/ Glance task widget (later milestone)
|
||||
FloretApp.kt, MainActivity.kt
|
||||
AgendulaApp.kt, MainActivity.kt
|
||||
```
|
||||
|
||||
---
|
||||
@@ -217,9 +219,9 @@ Material 3 Expressive throughout: `MaterialExpressiveTheme`,
|
||||
Three gates, in order:
|
||||
|
||||
1. **Provider present?** `ProviderResolver.detect()`. If none → a screen
|
||||
explaining Floret needs a tasks provider, with one-tap links to install
|
||||
explaining Agendula needs a tasks provider, with one-tap links to install
|
||||
**OpenTasks** (FOSS) or **tasks.org**, plus "I use DAVx5 — set its Tasks app".
|
||||
(This is Floret's "needs DAVx5" moment. Disappears entirely under Posture B.)
|
||||
(This is Agendula's "needs DAVx5" moment. Disappears entirely under Posture B.)
|
||||
2. **Tasks read/write permission** — request the active provider's runtime perms
|
||||
(`org.dmfs.permission.*` etc.). Declared in the manifest *and* requested at
|
||||
runtime; resolved dynamically from the detected provider.
|
||||
@@ -235,7 +237,7 @@ exactly like Calendula's launcher query.
|
||||
|
||||
Calendula relies on the **calendar provider broadcasting `EVENT_REMINDER`** and
|
||||
just posts the notification (Etar model). **Tasks providers do not broadcast
|
||||
reminders.** So Floret must schedule its own:
|
||||
reminders.** So Agendula must schedule its own:
|
||||
|
||||
- `DueReminderScheduler` (AlarmManager, exact alarms via `USE_EXACT_ALARM` /
|
||||
`SCHEDULE_EXACT_ALARM`) sets an alarm per task at `DUE` minus the chosen
|
||||
@@ -273,7 +275,7 @@ receiver. **No `EVENT_REMINDER` receiver** (doesn't apply).
|
||||
## 8. Milestones
|
||||
|
||||
- **M0 — Skeleton.** Copy Gradle/version-catalog/theme/DI/app+activity from
|
||||
Calendula, rename to Floret. Builds, shows themed empty scaffold. *(~½ day)*
|
||||
Calendula, rename to Agendula. Builds, shows themed empty scaffold. *(~½ day)*
|
||||
- **M1 — Read path.** `TasksContract` + `ProviderResolver` + `OpenTasksDataSource`
|
||||
+ repository. Lists overview + task list render real synced tasks (read-only),
|
||||
live-updating via ContentObserver. *(the meaty milestone)*
|
||||
@@ -297,21 +299,22 @@ M4 (subtasks) and M5 (reminders) are the two spots needing real thought.
|
||||
|
||||
## 9. Open decisions / to verify
|
||||
|
||||
1. **Name** — `Floret` is the working title (florets compose a Calendula head).
|
||||
Confirm or replace before M0 (touches appId, namespace, package).
|
||||
1. ~~**Name**~~ — **Agendula** (final): *agenda* + Calendula's `-ula`. Renamed
|
||||
from the working title *Floret*, which was promoted to the family /
|
||||
design-language name.
|
||||
2. **tasks.org provider authority** — verify on a real device; OpenTasks
|
||||
(`org.dmfs.tasks`) is the certain target for v1.
|
||||
3. **jtx Board** — support its richer contract later, or stay OpenTasks-only?
|
||||
4. **B authority choice** — bundling `org.dmfs.tasks` makes Floret a *replacement*
|
||||
4. **B authority choice** — bundling `org.dmfs.tasks` makes Agendula a *replacement*
|
||||
for OpenTasks (one authority owner per device). Intended (one app instead of
|
||||
two), but a conscious choice.
|
||||
5. **Repo home** — sibling Gitea repo next to Calendula, MIT license, same CI.
|
||||
|
||||
---
|
||||
|
||||
## 10. Sync sources Floret inherits for free (README copy)
|
||||
## 10. Sync sources Agendula inherits for free (README copy)
|
||||
|
||||
Because Floret builds on the provider, not on any one sync app, it works with
|
||||
Because Agendula builds on the provider, not on any one sync app, it works with
|
||||
**anything that writes to the tasks provider**: DAVx5 (CalDAV), SmoothSync,
|
||||
CalDAV-Sync, DecSync CC, and any Android sync adapter — no per-app integration.
|
||||
Google Tasks / Microsoft To Do are out of scope by design (proprietary, would
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Floret — documentation
|
||||
# Agendula — documentation
|
||||
|
||||
Floret is a Material 3 Expressive **task** app for Android: a pure front-end over
|
||||
Agendula is a Material 3 Expressive **task** app for Android: a pure front-end over
|
||||
the OpenTasks `TaskContract` provider (synced by DAVx5 / SmoothSync / DecSync
|
||||
over CalDAV), with no own database or sync stack. Sibling to
|
||||
[Calendula](https://gitea.jeanlucmakiola.de/makiolaj/calendula). See the
|
||||
@@ -10,7 +10,7 @@ top-level [`../README.md`](../README.md) for the project pitch.
|
||||
|
||||
| Doc | What it covers |
|
||||
|---|---|
|
||||
| [`ARCHITECTURE.md`](ARCHITECTURE.md) | How Floret is built **today** — layers, the data seam, provider resolution, the reminder engine, DI, build/tooling, manifest. Start here to work on the code. |
|
||||
| [`ARCHITECTURE.md`](ARCHITECTURE.md) | How Agendula is built **today** — layers, the data seam, provider resolution, the reminder engine, DI, build/tooling, manifest. Start here to work on the code. |
|
||||
| [`ROADMAP.md`](ROADMAP.md) | **Status** and what's next — milestones (M0–M6 + Posture B), what's done, open decisions, how to build/verify. |
|
||||
| [`PLAN.md`](PLAN.md) | The original implementation plan and **design rationale** — the A-now-B-later thesis, what transfers from Calendula, the locked decisions. The "why". |
|
||||
| [`RELEASING.md`](RELEASING.md) | How to cut a release — the git-tag-as-source-of-truth flow, CI jobs, F-Droid repo, required secrets. |
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Floret — releasing
|
||||
# Agendula — releasing
|
||||
|
||||
Floret is distributed through a **self-hosted F-Droid repo** (on Hetzner) with a
|
||||
Agendula is distributed through a **self-hosted F-Droid repo** (on Hetzner) with a
|
||||
human-readable **Gitea release** per tag. Both are produced automatically by
|
||||
`.gitea/workflows/release.yaml` when you push a tag. There are no APK assets on
|
||||
the Gitea release itself — distribution lives in the F-Droid repo; the release is
|
||||
@@ -94,7 +94,7 @@ Keep this green before tagging.
|
||||
|
||||
## F-Droid metadata
|
||||
|
||||
App store listing lives in `fdroid-metadata/` (`de.jeanlucmakiola.floret.yml`
|
||||
App store listing lives in `fdroid-metadata/` (`de.jeanlucmakiola.agendula.yml`
|
||||
plus `en-US/` summary/description). Per-version changelogs are generated into the
|
||||
repo's `metadata/.../en-US/changelogs/<versionCode>.txt` from `CHANGELOG.md` at
|
||||
release time; metadata is uploaded alongside `repo/` so changelog history
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Floret — roadmap
|
||||
# Agendula — roadmap
|
||||
|
||||
Where the project is and where it's going. This is the **status** view; the
|
||||
design rationale behind each milestone lives in [`PLAN.md`](PLAN.md), and how the
|
||||
@@ -50,7 +50,7 @@ The real Material 3 Expressive UI, built screen by screen against the M1
|
||||
ViewModels.
|
||||
- ✅ Provider/permission onboarding gate (`RootScreen`).
|
||||
- ✅ Lists overview (`ListsScreen`) — smart lists + user lists grouped by account.
|
||||
- ✅ Navigation host wiring (`FloretNavHost` + `Dest` route table: lists → task
|
||||
- ✅ Navigation host wiring (`AgendulaNavHost` + `Dest` route table: lists → task
|
||||
list → detail / edit, each binding its M1 ViewModel from route args).
|
||||
- ✅ Task list screen — checkbox rows + toggle-complete, swipe-to-complete /
|
||||
-delete (`SwipeToDismissBox`), inline add field (`InlineAdd` → `quickAdd`),
|
||||
@@ -140,12 +140,12 @@ permissions; ship self-contained. UI / repository / domain untouched — see
|
||||
|
||||
These carry over from [`PLAN.md`](PLAN.md) §9; resolved ones are struck through.
|
||||
|
||||
1. ~~**Name** — `Floret`~~ confirmed (appId `de.jeanlucmakiola.floret`).
|
||||
1. ~~**Name** — `Agendula`~~ confirmed (appId `de.jeanlucmakiola.agendula`).
|
||||
2. ~~**tasks.org provider authority**~~ verified on device:
|
||||
`org.tasks.opentasks` + `org.tasks.permission.*`.
|
||||
3. **jtx Board** — support its richer contract later, or stay OpenTasks-only?
|
||||
(Not in the candidate list today.)
|
||||
4. **Posture B authority choice** — bundling `org.dmfs.tasks` makes Floret a
|
||||
4. **Posture B authority choice** — bundling `org.dmfs.tasks` makes Agendula a
|
||||
*replacement* for OpenTasks (one authority owner per device). Intended, but a
|
||||
conscious choice.
|
||||
5. **Recurring tasks** — read as occurrences today (`isRecurring` flag exists);
|
||||
|
||||
Reference in New Issue
Block a user