Jean-Luc Makiola b0d86af959 Seat each hour in its own cell (#113) (#280)
The hour lines in week and day view become a per-hour grid, in the same visual
language as the month grid: each hour is a rounded cell on `surfaceContainer`,
separated by a 2dp gap that reveals `surface` behind it, so the boundary is
negative space rather than a stroke drawn across the column. The column's Card
takes `surface` and the container colour moves onto the cells.

### What changed

- `HourLines.kt` → `HourGrid.kt`. `hourGridCells` draws 24 rounded rects in the
  same `drawBehind` the 23 lines used — still a background layer, so blocks keep
  their continuous coordinate space and keep spanning cells.
- The cell corner is the event chip's 4dp rather than the month grid's 12dp: on
  a cell this size the 12dp out-rounds its own content. It is clamped to half
  the shorter side, so neither a fit-the-day sliver nor a narrow week column
  rounds into a lozenge.
- The hour gutter's label lift is derived from the label's own line height
  instead of a hardcoded `-6.dp`, so the label straddles the seam at any font
  scale. This was the third defect the issue listed; the other two (a
  pixel-width stroke, and a "line" broken into seven segments by `COLUMN_GAP`)
  go away with the lines themselves.
- The preference keeps its stored key and its string keys — only the English
  source text and the Kotlin names change (`showHourLines` → `showHourGrid`),
  as the issue asked.

### Deviation

The issue expected block placement to be untouched. It isn't: a block gives up
the same half-gap at each end that the cells do, otherwise an on-the-hour event
overhangs the seam into its neighbours' cells and the grid reads as broken
wherever there is an event. That inset follows through to the drag — the
floating copy takes the same gap off so it stays exactly the size of the block
it lifted off (or it has room for a time label the block had to drop, and the
label appears on long-press and goes again on drop, #267), and the grab is
measured off the block's placement rather than off where the grid seats it. An
end cut at midnight keeps none of the inset and stays against the column edge,
where `timedBlockCuts` already squares it off.

### Note for translators

`settings_hour_lines` and its summary keep their keys but changed meaning, so
the 12 `values-*` files still describe a separator line until Weblate re-syncs.
Renaming the keys is not an option — stale keys in the translations are fatal.

Closes #113

Supersedes #279, which was opened against `main` by mistake and has been undone
there.

Co-authored-by: Jean-Luc Makiola <business@jeanlucmakiola.de>
Reviewed-on: https://codeberg.org/jlmakiola/calendula/pulls/280
2026-09-07 20:02:33 +02:00
2026-07-31 22:22:10 +02:00
2026-09-02 12:44:40 +02:00
2026-09-02 12:44:40 +02:00
2026-08-31 05:02:32 +00:00
2026-07-31 22:22:10 +02:00
2026-09-02 12:44:40 +02:00

Calendula icon

Calendula

A modern Material 3 Expressive calendar for Android.
Reads, writes, and reminds — on top of the system calendar, with zero network access.

CI Android 10+ Kotlin + Compose Material 3 Expressive MIT License

Get it on F-Droid   Get it on Google Play   Get it on Obtainium   Support me on Ko-fi

Week view  Month view  Day view  Event detail  Agenda view  Reminder onboarding

Calendula is named after the flower whose name — like the word calendar — comes from the Latin kalendae, the first day of the month. It lives entirely on top of Android's CalendarContract: any calendar synced to your device (CalDAV via DAVx5, Google, local, WebCal subscriptions, …) simply appears, and everything you create or edit syncs back the same way. No own database, no sync stack reinvented.

Features

Calendar

  • Month, week, and day views with a one-tap view switcher
  • Full event details — attendees and their responses, reminders, recurrence (humanized), availability, visibility, foreign time zones
  • Per-calendar visibility toggle, grouped by account

Editing

  • Create, edit, and delete events — including recurring events with scoped writes: only this event, this and all following, or the whole series
  • Recurrence picker with one-tap presets and custom rules (interval, weekday toggles, end conditions); rules it can't express are preserved verbatim
  • Conflict-safe saves: if an event changed elsewhere while you were editing, Calendula asks instead of silently overwriting
  • Read-only calendars (WebCal, birthdays) are detected and respected

Reminders

  • Event reminders delivered by Calendula itself as notifications — essential when it's your only calendar app, since Android delegates reminder delivery to calendar apps
  • Tap a reminder to land on the event

Design & privacy

  • Real Material 3 Expressive throughout — dynamic color (Android 12+), expressive motion and shapes, light/dark theme
  • English and German UI plus community translations (Spanish, French, Italian, Polish, and more in progress), per-app language setting — and open to more languages
  • Zero telemetry, zero analytics, no internet permission — your data never leaves the device

📦 Install

Pick whichever channel you already use — they all 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; needs the repo added once
Codeberg release / Obtainium Per release Plain APK download, or automated by Obtainium
Google Play Per release Signed with Google's key — see below
Build from source Whenever you build Full control

Calendula is on the official F-Droid repository — just search for Calendula in any F-Droid client, or install it from f-droid.org.

F-Droid rebuilds 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)

Every release is built, signed, and published to a self-hosted F-Droid repository as part of the release pipeline, so it lands there first. Add it once and your F-Droid client handles updates from then on:

  1. 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=C2C0640402BF458FC0ED957AF0B37AA4C14022E72F89CE90B5965B458CF73425
    

    Repo: 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

  2. Refresh, search for Calendula, install.

Codeberg release / Obtainium

If you'd rather not use F-Droid at all, every release is also published on Codeberg with the signed APK (calendula_vX.Y.Z.apk) and a .sha256 checksum attached — download and install it directly.

For automatic updates from that channel, use Obtainium — on the phone, add Calendula in one tap, or do it by hand: Add App → paste https://codeberg.org/jlmakiola/calendulaAdd. Either way, Obtainium tracks the releases and prompts you when a new one appears.

Google Play

Calendula is live on Google Play: play.google.com/store/apps/details?id=de.jeanlucmakiola.calendula.

Play builds are signed with Google's key rather than mine, so switching between Play and any other channel requires an uninstall (and with it, a fresh start for app settings — your events live in the system calendar and are unaffected).

Build from source

The build is a plain Gradle build with no proprietary dependencies — see docs/BUILDING.md (note the floret-kit submodule).

Official F-Droid, the self-hosted repo, and the Codeberg releases all share the same signing key, so you can switch freely between them without reinstalling. Google Play is the exception — see above.

📚 Documentation

🤝 Contributing

Bug reports, ideas, and patches are all welcome on Codeberg.

The short version: start with an issue. Features get a yes-or-no before they get code, and both features and bugs are assigned a milestone whose release/vX.Y.Z branch your pull request then targets. Typo and docs fixes can skip straight to a pull request. Translations don't go through pull requests at all — Weblate owns them.

Read CONTRIBUTING.md before writing code: it covers the workflow, the build (note the floret-kit submodule), and the architectural rules a change is reviewed against.

🌍 Translations

Calendula ships in English and German, with community translations in Arabic, Chinese, French, Italian, Polish, Portuguese, Russian, and Spanish at varying degrees of completeness — partial is fine, untranslated strings simply fall back to English. You're warmly invited to add or finish your language. Translations are managed on a self-hosted Weblate:

Help translate Calendula

No coding needed — register on the Weblate server, pick (or request) a language, and translate the strings in your browser. You can also reach this link in the app from the top of Settings → App language.

📜 License

MIT — Jean-Luc Makiola, 2026

Description
Build infrastructure only — canonical repo: https://codeberg.org/jlmakiola/calendula
Readme MIT 17 MiB
v2.19.4 Latest
2026-09-02 10:56:44 +00:00
Languages
Kotlin 99.3%
Shell 0.4%
Ruby 0.2%
Python 0.1%