Adds a Settings entry listing the open-source projects Calendula ships, with copyright holder, SPDX licence id and a tap-through to each project's source. Apache-2.0 §4(a)/§4(d) are discharged by what the recipient of the APK is shown, not by a notice in the repository.
New `ui/licences/` package mirroring Agendula's:
- `OpenSourceLicences.kt` — `Attribution(name, copyright, licence, sourceUrl)` list plus a `Licence` enum (SPDX id + licence URL). Hand-maintained; the KDoc says why, and that adding a dependency means adding it here.
- `LicencesScreen.kt` — `CollapsingScaffold` + one `GroupedRow` per entry, summary `<copyright> · <SPDX id>`, tapping a row opens that project's source.
Reached from a new row in the **About** group in `SettingsScreen.kt`, before the privacy row so the group still ends on privacy. It uses `Icons.Default.Code`, since Gavel already belongs to the app's own License row.
Entries cover everything with `implementation` scope: AndroidX/Compose/Glance/WorkManager/DataStore/DocumentFile, Kotlin and kotlinx, Dagger and Hilt, Material Design icons, and floret-kit (MIT). Test-only dependencies are not shipped and stay off the list. Strings live in `values/strings.xml`.
Deviation from the issue: the footer text differs from Agendula's. Agendula's says every change to vendored code is listed in the linked repository — Calendula modifies nothing it ships, so the footer states that instead.
Closes#281
Co-authored-by: Jean-Luc Makiola <business@jeanlucmakiola.de>
Reviewed-on: https://codeberg.org/jlmakiola/calendula/pulls/284
The jump-to-today action in the app bar drew `Icons.Default.Today`, a generic calendar glyph. It now draws the current day number inside an outlined rounded box, so the bar tells you what day it is as well as taking you there. Shared by all four calendar views, so it changes everywhere at once.
- The box fills the same 24dp icon slot `AppBarSpacing` measures its trailing insets from (`IconSize` went from private to internal so the glyph and the spacing math cannot drift apart). No spacing value changed.
- Outlined rather than the month grid's filled circle: that circle means "this cell is today", and a filled accent here would sit heavier than the stroke icons beside it.
- The day comes from `rememberCurrentMinute()` through a `derivedStateOf`, so the button rolls over at midnight — and across a timezone change — while recomposing only when the number actually changes, not on every minute tick.
- The content description is unchanged, still the sentence "Go to today".
### Also here
A review of the branch turned up a loose end in #113, which this branch is merged on top of: the day and week **loading skeletons** still painted one solid `surfaceContainer` column. With the hour grid on that is no longer what a loaded column looks like — the column recedes to `surface` and the container colour moves onto the cells — so the skeleton flashed a solid block into a gapped grid on arrival, the exact resize its own comment promises not to do. Both skeletons now read `LocalShowHourGrid` and draw the same cells.
### Deviation from the issue
The issue asked to watch the width at large font scales. Rather than measuring and adapting, the number is held at a fixed size — `12.dp` converted to sp at draw time, so it ignores the font scale. It is an icon glyph sharing a bar with stroke icons, a growing two-digit day is exactly the width pressure #165 measured, and screen readers get the meaning from the content description either way. A small number is also strictly more than the old icon said, never less.
Closes#220
Co-authored-by: Jean-Luc Makiola <business@jeanlucmakiola.de>
Reviewed-on: https://codeberg.org/jlmakiola/calendula/pulls/282
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
The location on the edit screen was a single line, so a full postal address or a
long meeting link scrolled sideways out of view while you typed it. It now wraps
and the card grows to fit.
- The field is multi-line and animates its height; `setLocation` joins line
breaks with `", "` rather than deleting them, so a pasted address reads as
`12 Main St, 10115 Berlin` — the same shape `readContactAddress` has always
produced for a contact picked from the picker — instead of running together.
- The pin and the contacts button are positioned off the resolved `titleMedium`
line height instead of a hardcoded 32dp/4dp, so they stay level with the first
line at large font scales too. `EditCard`'s `iconAtTop` branch was changed with
it; otherwise the button would have scaled while the icon beside it did not.
The values are identical to the old ones at font scale 1.0.
- The field passes `ImeAction.Done`. Without it, a multi-line field with no IME
action turns Enter into a newline key whose every newline is then folded away —
a dead key that only nudged the caret.
The last two points are beyond the issue itself; they came out of a review of the
first, since making the field multi-line is what put a fixed-height button next to
text that grows.
Closes#273
Co-authored-by: Jean-Luc Makiola <business@jeanlucmakiola.de>
Reviewed-on: https://codeberg.org/jlmakiola/calendula/pulls/278
The floating drag copy hardcoded `maxLines = 1` on both its time label and its title, so picking up a block whose text wrapped clipped it to one line for as long as it was held, and it snapped back on drop.
- `DragCopy` now measures the range at its own width and height, exactly as the block does — same `MAX_TIME_LINES` cap, same spare-height rule.
- The block's measured title budget rides along on `TimelineDrag`, so the copy spends its height the same way the block did; the copy draws through `BlockTitle` now rather than a bare `Text`, so a wrapped title clips the way the block's does.
- That measurement moves into a shared `blockTimeLines`, which Week and Day each had their own copy of.
Deviation from the issue: it names only the time label, but the title had the identical hardcode and the two share one height budget — fixing the range alone would just have moved the mismatch to the title.
The copy still shows the range on a block too short to carry one; that is the feedback the drag exists for and is left as is.
Closes#267
Co-authored-by: Jean-Luc Makiola <business@jeanlucmakiola.de>
Reviewed-on: https://codeberg.org/jlmakiola/calendula/pulls/269
Events waiting on your answer now read differently from the ones you have answered.
`EventInstance.isDeclined` becomes an `EventResponse` enum — `Going` / `Invited` / `Declined` — so there is one source of truth for the column; `isDeclined` stays as an extension property, so the surfaces that only care about that case are untouched. A tentative "maybe" counts as `Going`.
An unanswered invitation is drawn as an outline: the calendar's colour on the border and on the title, at regular weight, filled with the surface the chip sits on. The outline leaves open whichever edges the event runs past, so a bar carried across a week boundary stays one bar. The accent is harmonised even when soften-colours is off — raw mode exists so a filled container matches the sync source, and an outlined chip has no container, only coloured text, which needs its lightness pinned or a pale calendar goes unreadable.
Applied to the month chips, the week and day all-day bars, both timed blocks and the drag copy.
Declined events also sort last in the month view now: the timed chips, the split style's day pane, and the all-day bars, which pack into a lane below the ones you answered. A day that overflows drops the events you said no to first. `layoutAllDay` had to stop deciding lanes from how far right each one reaches and track the columns it holds instead, or packing declined last would waste a lane.
### Deviations from the issue
The issue asked for declined invitations to be outlined too, with the title crossed out. They keep their filled container instead: at chip size the strike-through needs something under it to read against, and on a hollow chip it was illegible. Declined is still distinguished — filled and struck through, against filled and plain — and the sort order is the extra separation it gained.
Not covered, and each needs its own mark rather than this one: the agenda rows, search results and the split view's day pane carry a 6dp colour stripe rather than a filled chip, and both widgets are Glance, which has no border modifier. All four still mark declined only.
Closes#230
Co-authored-by: Jean-Luc Makiola <business@jeanlucmakiola.de>
Reviewed-on: https://codeberg.org/jlmakiola/calendula/pulls/268
The medium-to-regular step is a no-op on Atkinson Hyperlegible, JetBrains
Mono and imported fonts, which ship no medium face — so the title takes
full ink against the time's 0.8 instead, and the four title sites share
the constant.
Also: the drag copy carries the time its source chip had rather than
deriving one with continuesLeft hardcoded false; the month grid takes
its zone from the state; times are formatted once per row and the chip
width measured once per grid; the chip geometry lives in one file; and
a screen reader hears the time whether or not the chip is wide enough
to draw it.
InlineField pinned KeyboardCapitalization.None for every field, which
overrode floret-kit's Sentences default. The override moves to location,
which is as often a URL as an address.
Closes#146
Long-pressing one of the three free-text fields in the event view puts the whole field on the clipboard.
| Field | Tap | Long-press |
| --- | --- | --- |
| Title | — | copy |
| Location | opens maps (unchanged) | copy |
| Description | links still open (unchanged) | copy |
Times, calendar, recurrence and attendees stay uncopyable. The confirmation snackbar only shows below API 33 — Android 13+ raises its own clipboard chip. A failed copy is reported on every API level.
First clipboard use in the app, so the helper lives in `ui/common` as a `FieldCopier` plus a `copyOnLongPress` modifier. A field with no tap action gets the gesture through `pointerInput` and merged `onLongClick` semantics rather than a `clickable` that would announce a tap it doesn't have.
Deviation worth naming: the description takes the gesture on its card rather than its text. A linkified URL consumes the press over its own glyphs, so long-pressing a URL copies nothing either way — the card at least adds the icon and padding as target. A description that is nothing but a link stays awkward to copy.
Closes#195
Co-authored-by: Jean-Luc Makiola <business@jeanlucmakiola.de>
Reviewed-on: https://codeberg.org/jlmakiola/calendula/pulls/263
### Part A — the title shortens instead of wrapping
`CalendarTitleButton` measures the full title against the width the bar hands
the title slot (`BoxWithConstraints` + `rememberTextMeasurer`) and falls back to
a shorter form when it does not fit. One pass, so no flicker, and it adapts on
its own to locale, font scale and to the pill being absent after #150.
- Month and week fall back to the three-letter month ("Sep", "Mär").
- Day drops the weekday instead ("Wed, 2 Sep 2027" → "2 Sep 2027") — there is no
shorter month form left there, `EEEdMMM` is already abbreviated.
- Either way the title now clamps to one line and ellipsises rather than growing
the bar to two lines.
The choice itself is a pure `titleFor(title, shortTitle, titleWidth,
availableWidth)`, so it is unit-tested rather than only reachable through a
composable. `formatCalendarTitle` gained no new behaviour, so the widget header
is untouched.
### Part B — the trailing edge, not the whole rhythm
`AppBarSpacing` holds the shared values: the side inset (also consumed by the
month grid, so the pill lines up with the content under it), the title's start
inset, and the two trailing insets — one measured to a container's background,
one to an icon button's glyph.
**Deviation from the issue:** the issue asks for one edge inset at *both* ends
and one gap between adjacent controls. Only the trailing edge and the title
inset are shared here. M3 derives both the title's position and the gap between
two adjacent action icons from the icon buttons' width, so putting the leading
edge and the inter-icon gaps on the same rhythm meant narrowing the icon buttons
from 48.dp to 32.dp — that reads cramped and shrinks the touch targets. The
leading edge and the icon gaps stay on M3's defaults. The title's 8.dp start
inset stays real padding rather than a negative offset, so the menu button's hit
box is intact.
The agenda range bar picks its end inset from whether the view switcher is
showing: aligned to the pill's background when it is, to the search icon's glyph
once #150 hides it.
Closes#165
Co-authored-by: Jean-Luc Makiola <business@jeanlucmakiola.de>
Reviewed-on: https://codeberg.org/jlmakiola/calendula/pulls/261
Release v2.19.4.
**Fixed**
- A deleted occurrence stays deleted through a series re-timing, and through backup/restore ([#225]).
- "Only this event" saves the edit on calendars that never synced, and on a row that doesn't recur it edits or deletes the event itself instead of filing an exception against nothing.
- A reminder left on the account's default is no longer read as a lead time of Calendula's own.
- The widgets turn the page at midnight; the re-arm backstop for the inexact rollover alarm is back, throttled to once per 15 minutes on calendar changes.
- An event running past midnight can be dragged by either half ([#253]).
- One event the provider refuses no longer costs the rest of the import, and an import cut short reports honestly how far it got.
- EXDATE zone prefixes are read in the bare `<zone>;` form the provider and DAVx5 actually write, not only the iCalendar `TZID=` spelling; a list holding a stamp we can't parse is kept whole rather than re-emitted stripped of its zone; RDATE travels with RRULE when the recurrence set is rewritten.
**Added**
- Belarusian, Hungarian and Slovak, from community translators on Weblate.
**Changed**
- Arabic, Polish and Simplified Chinese are complete; Czech grew, and Russian is now at about half.
Bumps `versionName` to 2.19.4, so merging this triggers the release pipeline.
Co-authored-by: Jean-Luc Makiola <business@jeanlucmakiola.de>
Reviewed-on: https://codeberg.org/jlmakiola/calendula/pulls/259