Combines #470 and #471 into one PR. ## Midnight-to-midnight events (#296) A timed event that starts and ends on local midnight now displays as an all-day event: - Day/week: in the all-day strip instead of the time grid. - Month and month widget: a bar with no start time. - Agenda and agenda widget: "All day". - Sorting: with the all-day events. - Countdown widget: skipped in next-event mode like other all-day events, with the all-day label when pinned. - Reminder notification: the text names the day(s) instead of 00:00 – 00:00. The event itself stays timed. The detail sheet still shows 00:00 – 00:00, and reminders still fire at their own lead time before 00:00, since that is what the stored reminder says. Midnight is checked in the device zone, so a feed event stored as 22:00Z only counts as whole-day at UTC+2. Search results are left as they are. Dragging or resizing a block in the timeline so it lands exactly on midnight to midnight moves it to the all-day strip. The floating copy and the selection are dropped right away. Display priority is now computed once per event, not on every sort comparison. ## Event status (#388) The calendar views now use the event's own status (Events.STATUS), not just your reply to an invite: - Cancelled events are struck through and sorted last, the same as declined invites. This applies to day/week, month, agenda, search and the widgets. The countdown widget skips them in both modes, and the week-strip widget leaves them out of its count and dots. - Tentative events get the outline used for unanswered invites (in-app grids). An event that is both cancelled and an open invite is struck through, not outlined. - Cancelled standalone events and cancelled series are no longer hidden. Cancelled exception rows (a single occurrence deleted from a series) are still filtered out, matched by ORIGINAL_ID or ORIGINAL_SYNC_ID. - The editor has a new optional Status field after Availability, built on floret-kit's new `SegmentedChoiceCard` (Availability uses it too). A new event writes STATUS only when it isn't Confirmed, an edit writes it only when it changed. - `isDeclined` was renamed to `isCrossedOut`, and a new `isOutlined` covers both cases. Deviations from the issue: - The editor offers Confirmed and Tentative only. Cancelled is offered only for an event that's already cancelled, so it can be put back. Google treats a cancelled event as deleted, so letting people pick it would silently delete Google events. - A cancelled series can't be edited or moved one occurrence at a time. "Only this event" is left out of the scope prompts, because Android saves a cancelled single occurrence as a deleted one. A drag that could only move the single occurrence is refused. - The widgets don't draw outlines for unanswered invites either, so tentative events look filled there. Depends on jlmakiola/floret-kit#27. The submodule is pinned to its branch, so that PR has to land on floret-kit main and the pin has to be moved before release. Closes #296 Closes #388 Co-authored-by: Jean-Luc Makiola <business@jeanlucmakiola.de> Reviewed-on: https://codeberg.org/jlmakiola/calendula/pulls/472
Calendula
A Material 3 Expressive calendar for Android.
It works on top of the system calendar and has no network access.
Calendula is named after the flower. Both its name and the word calendar come from the Latin kalendae, the first day of the month.
The app works directly on Android's CalendarContract. Any calendar synced to
your device (CalDAV via DAVx5, Google, local, WebCal subscriptions) shows up in
Calendula, and whatever you create or edit syncs back the same way. Calendula
has no database or sync code of its own.
Features
Calendar
- Month, week and day views, with a switcher in the top bar
- Event details with attendees and their responses, reminders, recurrence in plain words, availability, visibility and foreign time zones
- Calendars can be shown or hidden one by one, grouped by account
- Your contacts' birthdays, anniversaries and other dates, if you turn it on. Contacts are read on the device and never changed.
Editing
- Create, edit and delete events. For a recurring event you choose whether a change applies to only this event, this and all following, or the whole series.
- The repeat picker has presets and custom rules (interval, weekdays, end condition). Rules it can't show are kept as they are.
- If an event changed elsewhere while you were editing it, Calendula asks before saving over it.
- Read-only calendars such as WebCal subscriptions and birthdays are detected and can't be edited by accident.
Reminders
- Calendula shows event reminders as notifications itself. Android leaves reminder delivery to calendar apps, so if Calendula is your only one, this is where your reminders come from.
- Tapping a reminder opens the event.
Design and privacy
- Material 3 Expressive throughout, with dynamic color on Android 12+, light and dark themes, and expressive motion and shapes
- Available in 16 languages, with a per-app language setting. Help translate.
- No telemetry, no analytics and no internet permission. Your data stays on the device.
Install
All channels install the same app:
| Channel | Updates | Notes |
|---|---|---|
| Official F-Droid | On F-Droid's build schedule | Recommended; no extra setup |
| Self-hosted F-Droid repo | Minutes after a release | Fastest; add the repo once |
| Codeberg release / Obtainium | Per release | Plain APK download, or automatic with Obtainium |
| Google Play | Per release | Signed with Google's key, see below |
| Build from source | Whenever you build | Full control |
F-Droid (recommended)
Calendula is in the official F-Droid repository. Search for Calendula in any F-Droid client, or install it from f-droid.org.
F-Droid builds from source on its own schedule, so a new version usually shows up there a few days after release.
Self-hosted F-Droid repo (fastest updates)
The release pipeline builds, signs and publishes every release to a self-hosted F-Droid repository, so new versions land there first. Add it once and your F-Droid client handles updates from then on:
-
In your F-Droid client, open Settings → Repositories → Add (or open the link below on your phone):
https://apps.dev.jeanlucmakiola.de/dev/fdroid/repo?fingerprint=C2C0640402BF458FC0ED957AF0B37AA4C14022E72F89CE90B5965B458CF73425Repo:
https://apps.dev.jeanlucmakiola.de/dev/fdroid/repo· fingerprint (SHA-256):C2C0 6404 02BF 458F C0ED 957A F0B3 7AA4 C140 22E7 2F89 CE90 B596 5B45 8CF7 3425 -
Refresh, search for Calendula and install it.
Codeberg release / Obtainium
Every release is also on
Codeberg with the signed
APK (calendula_vX.Y.Z.apk) and a .sha256 checksum. Download it and install
it directly.
For automatic updates from Codeberg, use
Obtainium. On your phone,
add Calendula in one tap,
or add it by hand: Add App, paste https://codeberg.org/jlmakiola/calendula,
then Add. Obtainium then watches the releases and tells you when a new one
is out. Betas of upcoming versions are published there too, as pre-releases; to
test them, switch on Include prereleases for Calendula in Obtainium.
Google Play
Calendula is on Google Play.
Play builds are signed with Google's key rather than mine, so switching between Play and any other channel means uninstalling first. You lose the app settings. Your events live in the system calendar and aren't affected.
Build from source
It's a plain Gradle build with no proprietary dependencies. See
docs/BUILDING.md, and note the floret-kit submodule.
Official F-Droid, the self-hosted repo and the Codeberg releases share the same signing key, so you can switch between them without reinstalling. Google Play is the exception (see above).
Documentation
- Contributing: how to report bugs, propose features and send patches
- Building from source: requirements and Gradle tasks
- Architecture: the layers and the main pipelines
- Milestones: what has shipped and what's next
- Privacy policy: the source of the page at jeanlucmakiola.de/calendula/privacy
Contributing
Bug reports, ideas and patches are welcome on Codeberg.
Start with an issue. A feature gets a yes or no before anyone writes code, and
features and bugs are assigned a milestone whose release/vX.Y.Z branch your
pull request targets. Typo and docs fixes can go straight to a pull request.
Translations go through Weblate, not pull requests.
Read CONTRIBUTING.md before writing code. It covers the
workflow, the build (including the floret-kit submodule) and the
architectural rules changes are reviewed against.
Translations
Calendula is written in English. Translators on Weblate have added Arabic, Belarusian, Chinese (Simplified), Czech, French, German, Hungarian, Italian, Japanese, Polish, Portuguese (Portugal and Brazil), Russian, Slovak and Spanish, some of them partly. Untranslated strings fall back to English, so a partial translation is fine. Translations are managed on a self-hosted Weblate:
You don't need to code. Register on the Weblate server, pick a language (or request one) and translate in your browser. The app links here from the top of Settings → App language.
Privacy
Calendula has no analytics, tracking, ads, crash-reporting SDK or third-party services. It doesn't have the internet permission, so it can't send anything anywhere. Your events stay in Android's own calendar store.
Privacy policy, written in
docs/PRIVACY.md, which is what that page renders.
Support
Calendula is free and has no ads. If you'd like to support its development, you can tip once on Ko-fi or donate regularly on Liberapay. Both are linked in the app under Settings → Support development.
License
MIT, Jean-Luc Makiola, 2026










