Calendula's bespoke About card was the floret-kit components.AboutCard recipe in
disguise — logo + name/author, a row of equal-width outlined links, and a
full-width tonal highlight link. Replace the hand-rolled layout with the kit
recipe, supplying Calendula's logo (AppLogo, app-local) and its source
(ic_gitea, via vectorResource) / licence / support links as AboutLinks. Drop the
now-unused button/icon imports.
Calendula's language picker keeps its app-local form (it uses a custom header
the generic LanguagePickerRow recipe doesn't carry).
Compile + unit tests + lintDebug green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
OnboardingScaffold, OnboardingSpace and BenefitRow were identical to the kit's
components forms, so delete the app-local copies and repoint the permission and
reminder onboarding screens at floret-kit. BrandHero — Calendula's own launcher
mark with the permission-denied lock badge — stays app-local in its own file
(each sibling app keeps its own hero).
Compile + unit tests + lintDebug green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
With the kit grown to carry Calendula's richer variants (previous floret-kit
commit), move the last app-local UI-core onto the shared components module and
bump the submodule:
- Delete the app-local GroupedList.kt (Position/positionOf/GroupedRow/
CollapsingScaffold) and Picker.kt's FullScreenPicker/OptionPicker — all now
come from floret-kit. Repoint every screen's imports.
- The features that kept these local are preserved through the kit's new
parameters: GroupedRow dimmed/container; CollapsingScaffold actions/snackbar;
and predictiveBack, passed true at the settings hub/sub-screens, the calendars
manager, and all full-screen pickers (the kit defaults it off, so it's opt-in).
- FullScreenPicker's keyboard fix (SOFT_INPUT_ADJUST_NOTHING) and OptionPicker's
header slot now live in the kit too.
- Add a localized `back` string (the kit scaffold's back-button label).
Bumps floret-kit 86f4480 -> d9e4e87. Compile + unit tests + lintDebug +
reproducible-release guard all green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Calendula's pastelize (raw provider colour -> theme-fitting pastel) was
byte-identical to the kit's components.pastelize, so delete the app-local copy
and repoint its ~13 call sites (views, widgets, swatch row, calendar chip) at
the shared one. CalendarColorChip — the calendar-glyph avatar — stays local and
now tints from the kit pastelize.
Compile + unit tests + lintDebug green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adopt the floret-kit components module for the three widgets whose kit form is
identical to Calendula's, deleting the app-local copies and repointing imports.
One behavioural guard: the kit's InlineTextField defaults capitalization to
Sentences (the family default), where Calendula's defaulted to None. The three
Calendula call sites that relied on the old default now pass
KeyboardCapitalization.None explicitly — notably the guest **email** field,
which must not auto-capitalize — so behaviour is unchanged.
The richer app-local primitives (GroupedRow with dimmed/container,
CollapsingScaffold with actions/snackbar/predictive-back) stay local for now:
they're supersets of the kit's versions, so consuming the kit forms would drop
features. Promoting those into the kit is a follow-up.
Compile + unit tests + lintDebug green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adopt floret-kit's identity module, shared with Agendula:
- CalendulaTheme becomes a thin wrapper over the kit's FloretExpressiveTheme
(dynamic colour, system light/dark, standard motion scheme). Calendula keeps
its identity locally: the seed-derived CalendulaLightFallback/DarkFallback and
CalendulaTypography. No call-site changes — CalendulaTheme keeps its name.
- The generic content transitions move to identity: calendarExpandEnter ->
expandEnter, calendarCollapseExit -> collapseExit, calendarItemEnter ->
itemEnter, calendarAnimateItem -> animateItemMotion, calendarFadeThrough ->
fadeThrough, plus Modifier.predictiveBack and rememberReduceMotion (same
names). Call sites across the screens repointed to floret.identity.
- CalendarTransitions.kt keeps only what's genuinely calendar-specific: the
directional month/week/day slide (calendarSlideTransition) and its specs
(rememberCalendarSlideSpec / rememberCalendarFadeSpec).
Compile + unit tests + lintDebug green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace Calendula's app-local reminder-override plumbing with floret-kit's
core-reminders, shared with Agendula:
- CalendarReminderOverride (Inherit/None/Minutes) -> kit ReminderOverride, with
the kit's applyReminderOverride() map extension.
- The hand-rolled parse/serialize of the per-calendar override string -> kit
ReminderOverrideCodec, configured with Calendula's on-disk dialect
(id=minutes entries joined by ;, "none" sentinel). Format is unchanged, so
stored prefs round-trip identically (SettingsPrefsTest still green).
- ReminderUnit enum + the custom-amount decomposition (decomposeReminder) ->
kit ReminderUnit + decomposeReminderMinutes/ReminderAmount.
App-specific glue stays put: resolveDefaultReminder (timed vs all-day),
reminderLeadTimeLabel / reminderUnitLabel (app string resources), and the
global-default picker helpers.
Compile + unit tests green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the app-local crash machinery (CrashReporter, CrashReportDialog,
CrashReportSubmit) with floret-kit's core-crash. The kit version is the same
privacy-respecting, on-device, no-network design (clipboard + prefilled issue
URL via ACTION_VIEW), parameterised by a CrashConfig: app label + the
issue-tracker URLs, supplied once at install() in CalendulaApp.
- CrashReportActivity stays app-local (thin standalone surface) but now imports
the kit's CrashReporter/CrashReportDialog/submitCrashReport.
- buildCrashReport moved to the kit and gained an appLabel parameter; the
privacy-allowlist test is repointed to the kit API (still pins Calendula's
exact six-line header — no identifiers, no calendar content).
- Calendula keeps its own crash strings (incl. German + "calendar content"
wording) as resource overrides, converted to the kit's %1$s app-label /
two-arg body-template arity so the shared code formats them correctly.
Compile + unit tests + lintDebug green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the app-local AppLanguage with the shared
de.jeanlucmakiola.floret.locale.AppLanguage. The kit version is identical
except supportedTags() takes the locales_config XML res id as a parameter
(the kit can't reference the app's R), so the call site now passes
R.xml.locales_config. Wire the core-locale module into the app.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Phase 1 of the shared-kit plan: Calendula becomes the second core-time consumer.
floret-kit is embedded as a git submodule and wired with includeBuild, so the
app depends on de.jeanlucmakiola.floret:core-time and Gradle substitutes the
local source module (built from source).
- Remove the app's TimeBridge (Instant <-> epoch millis) + its test; the helpers
now live in core-time. Repoint the 7 call sites to de.jeanlucmakiola.floret.time.
- Calendula's richer time formatting (TimeFormat, 24h CompositionLocal) stays
app-local by design.
Reproducible-build handling (Calendula publishes via official F-Droid repro):
- Pin the submodule to a foojay-free kit commit (the resolver would let a build
fetch a JDK -> non-reproducible; the kit never used a toolchain block).
- Harden scripts/check_reproducible_release.sh to scan the included build's
Gradle scripts for the toolchain-resolver invariant.
- Draft fdroiddata recipe: submodules: true (else F-Droid checks out an empty
floret-kit/ and the from-source build fails).
- CI build checkouts fetch submodules recursively.
Clean composite build + unit tests + lintDebug + repro guard all green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Per review, it shouldn't be a top-level Settings entry: the "Help translate"
link now sits at the top of the full-screen App language picker (OptionPicker
gains an optional header slot). Updated README + changelog wording to match.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Invite community translations: a new Settings > Help translate row (next to
App language) opens the project's Weblate engage page, plus a Translations
section in the README. Documented in the 2.11.0 changelog.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Turning automatic backup off only cancelled the periodic work, so a
run-now that kept failing (e.g. its folder was deleted) retried forever
and spammed failure notifications.
- Cancel the immediate "run now" work too when backup is disabled.
- Worker no-ops (no retry) when the toggle is off, so already-queued work
can't revive itself.
- Reconcile scheduled work against saved settings on every app launch —
re-arms after a reinstall and clears orphaned work once backup is off.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Scheduled one-way export of local calendars to a user-chosen folder,
overwriting calendula-backup.ics each run — the manual backup, automated.
Not a sync; stays INTERNET-free (provider reads + local file write).
- WorkManager periodic job (deps: work-runtime-ktx, documentfile); worker
pulls collaborators via a Hilt @EntryPoint, so no custom WorkerFactory
wiring. First periodic run is delayed one interval so it can't race the
immediate "run now" feedback run; the writer also overwrites the canonical
file and cleans up any "(1)" duplicates from earlier races.
- SettingsPrefs: enabled, interval (minutes, floored at 30), folder Uri,
last-run status; persisted SAF write grant.
- UI in Calendars > Backup: toggle, folder picker, amount+unit interval
dialog, last-run status line. Notifies after repeated failures.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the per-account toggle switch and nested manage row with a single
trailing overflow (⋮) menu holding both account-level actions:
- "Enable all" / "Disable all" — toggle every calendar in the group
- "Manage in app" (synced) / "Add calendar" (local)
The dropdown is styled to fit: rounded corners, a distinct floating
surface (surfaceContainerLowest + lifted shadow) so it stands clear of
the cards, and a divider separating the two actions. Shortened the manage
label to "Manage in app" and dropped the now-unused account a11y strings.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Settings:
- Event-form field rows show each field's own icon (shared
EventFormFieldVisuals, reused by the editor and settings)
- Move "Add Quick Settings tile" to a top-level Settings hub row
- Notifications: reliable-delivery + snooze moved above the
per-calendar block, which now folds behind one expandable section
Calendars manager:
- Local and synced calendars now use one collapsible group card
- Source-branded headers: each account shows its app's launcher icon
(Google Calendar, DAVx5, …) loaded from PackageManager; local shows
a device chip
- Per-account toggle-all switch (CalendarsViewModel.setAccountDisabled)
- Management action ("Add calendar" / "Manage in app") is a nested row
inside the expanded group; removed the dead "Add account" row
- A fully deactivated account dims its header, not just the switch
- Group headers use the normal row colour; their options sit one tone
darker (GroupedRow gains an optional container colour)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The agenda widget loaded and sliced its data in the provideGlance preamble
and captured it as a non-reactive local. updateAll() reliably recomposes a
live Glance session but does not reliably re-run that preamble, so a range
change was redrawn against the stale slice (intermittently — only when no
session was alive did it pick up the new range). This is the same platform
limitation the month widget already works around.
Mirror that pattern: load the widest selectable window once, store the range
in per-instance Glance state (AGENDA_RANGE_KEY), read it reactively via
currentState, and slice in the composition. The settings setter writes the
state into each instance and recomposes, so a range change now reflects via
plain recomposition regardless of session lifecycle.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The debounced observer was unreliable: drop(1) plus the ViewModel lifecycle
could swallow a change after re-entering the app, so a single range change
sometimes didn't refresh the widget. Refresh directly from the settings
setters instead (always fires on a real change), serialized through a mutex
so a rapid flip-and-flip-back can't run two updateAll calls at once and
strand the widget on the intermediate value.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Refreshing the widget directly from the setter raced when a setting was
flipped and flipped back quickly: two concurrent updateAll calls could
coalesce around a stale read and leave the widget on the intermediate value.
Observe the agenda-widget-range and week-start prefs instead and push a
single debounced updateAll once changes settle, so the widget always
converges to the final value.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The agenda widget only re-read agendaWidgetRange on the next data-change /
midnight / periodic refresh (or re-placement), so a settings change appeared
to do nothing until then. Push an updateAll from the settings setters:
the agenda widget on an agenda-widget-range change, and both widgets on a
week-start change (month weekday header + the agenda widget's "this week").
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Consolidate the separate "range title" and "range button" settings into a
single "Range bar" toggle, now that the header and switcher live on one bar.
On by default.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Move the session range pill from the bottom-left corner onto the same top
bar as the "Showing all upcoming events for …" header — header on the left,
pill on the right, each still independently toggleable.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reword the range header to "Showing all upcoming events for". Replace the
dry empty state ("Nothing scheduled" + subtitle) with a single warmer line
("You're all caught up"), drop the subtitle, and switch the icon to a
coffee cup. The empty title is shared with the agenda widget.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Move the "Showing all events for <window>" line out of the picker and into
the agenda view as an always-visible header above the list. Add two
settings (Agenda group, both ON by default) to toggle the range header
banner and the bottom-left range pill independently. The picker keeps its
two-list grouping but no longer carries the header.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Group the range options into calendar-aligned (today / this week / this
month) and rolling (next 7 / 30 days / custom) lists. Add a header showing
the concrete span currently in effect — a single date for Today, the month
and year for This month, otherwise a start–end span — so it's clear what
the agenda is showing. The header appears only where a window is supplied
(the agenda pill), not in Settings.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a tonal pill in the agenda's bottom-left corner showing the current
range; tapping it opens the existing AgendaRangePicker as a session-only
override. The override lives in AgendaViewModel (in-memory), so it survives
view switches and rotation but resets to the saved default when the app is
relaunched. The pill fills with the primary container while an override is
active, and a hint in the picker spells out the temporary nature.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Drop the uniform two-per-card grouping for meaningful sections: theme &
colour (2), calendar — default view, week start, time format, hour lines
(4), and agenda (2).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Integrate #3/#5/#6 (week-start, time-format, hour-lines) into the agenda
range branch. SettingsViewModel folds all five view prefs into one combine;
WidgetData keeps the range-based window and adds the resolved is24Hour.
Also, in the same Appearance section:
- regroup the rows into four titled-by-spacing groups (theme & colour /
calendar layout / timeline display / agenda) instead of one long card.
- add an explanatory description line under the title of each agenda-range
picker (PickerDescription).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bump versionName to 2.11.0 (versionCode 21100) and add the 2.11.0
changelog: any-day week start (#3), 12/24-hour time format (#6),
optional timeline hour lines (#5), and agenda range limits (#4).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Extend AgendaRange with calendar-aligned windows alongside the rolling
ones: ThisWeek runs through the end of the current week (respecting the
week-start preference — a Monday start means everything before next
Monday), ThisMonth through the last day of the current month. dayCount now
takes the anchor day and week-start; the agenda screen and widget resolve
the week-start preference and pass it through. Rolling options relabelled
("Today", "Next 7 days", "Next 30 days") to read distinctly from the new
calendar-aligned ones.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add independent agendaScreenRange and agendaWidgetRange preferences, each a
rolling window: 1 day / 1 week / 1 month / custom 1–365 days (default Month).
Rolling (not calendar-aligned) so the span never degenerates near a period
boundary. The in-app Agenda screen and the agenda widget each read their own
setting, configured via a new AgendaRangePicker with an inline custom-days
editor.
Closes #4 (Codeberg)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a showHourLines preference (default off — the existing clean look).
When on, the week and day timelines draw a faint outline-variant line at
each hour boundary, sitting over the column background but beneath event
blocks. The toggle is provided app-wide via LocalShowHourLines and applied
through a reusable hourSeparatorLines() modifier.
Closes #5 (Codeberg)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a TimeFormatPref (Auto/12h/24h) preference. Auto follows the device's
24-hour system setting; the others force a clock app-wide. The resolved
convention is provided once at the app root via LocalUse24HourFormat, so
every in-app time label reads it without per-screen plumbing.
A shared, pure TimeFormat helper (formatTimeOfDay / formatMinuteOfDay /
formatHourLabel / timeOfDayFormatter) is now the single source for all
time-of-day rendering. Routing every site through it also fixes a
pre-existing inconsistency: the week/day timeline gutters and the agenda
screen + widget hard-coded 24h, while event detail/edit/search/reminders
followed the locale — so a 12h-locale user previously saw mixed formats.
Covered: week & day timelines (gutter + event blocks), agenda screen and
widget, event detail/edit, search, and reminder notifications.
Closes #6 (Codeberg)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the AUTO/MONDAY/SUNDAY week-start enum with a sealed type
(Auto + Day(DayOfWeek)) so users can pick any of the seven days as the
first day of the week. The picker now lists "Automatic" plus all seven
localised weekday names; labels come from java.time display names rather
than per-day string resources.
Stored values round-trip by DayOfWeek.name, so the legacy MONDAY/SUNDAY
preferences migrate transparently. Garbage values fall back to Auto.
Closes #3 (Codeberg)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Introduce a second, heavier visibility level above the per-view hide
filter. A disabled calendar is removed from the app's surfaces entirely —
its events drop out of all views and search, and it disappears from the
drawer filter list, the event-form calendar picker and the import target
picker. It stays listed only in Settings → Calendars, where a per-row
switch toggles it, so it can always be brought back.
- CalendarPrefs: disabledCalendarIds set + setter, mirroring the hidden
set (DataStore comma-separated string); never touches the system
VISIBLE/SYNC_EVENTS flags, so it's app-local and reversible.
- CalendarRepositoryImpl: instances()/searchEvents() exclude
calendarId ∈ (hidden ∪ disabled). distinctUntilChanged() on instances
collapses the transient duplicate emission both DataStore-derived sets
produce when either is toggled.
- FilterViewModel: drop disabled calendars from the drawer filter list.
- EventEditViewModel: exclude disabled from writableCalendars; a
last-used preselect on a now-disabled calendar falls back to the first
remaining writable one.
- ImportViewModel: exclude disabled from the import target list.
- CalendarsScreen/ViewModel: per-row enable/disable Switch on both the
local and synced groups; disabled rows render dimmed (new GroupedRow
`dimmed` flag) while keeping the toggle live.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add an optional way for users to support development:
- About card: tonal "Support development" button opening ko-fi.com
via ACTION_VIEW (no INTERNET permission), EN + DE strings
- F-Droid metadata: Donate field in self-hosted and official drafts
- README: short Support section with the Ko-fi link
No perks/rewards attached — kept as a plain donation link.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Centralise the app's motion vocabulary in CalendarTransitions.kt and route
every surface through it so animation is consistent app-wide:
- Shared expand/collapse, list-item and fade-through helpers; the event-edit
expand pattern is now the shared one (no duplicate).
- Settings reminder-override rows and the reminder Custom field now expand/
collapse instead of bare-fading.
- Search and agenda rows animate (fade/relocate) via animateItem.
- Month/week/day slide and the onboarding gates honour the new helpers.
- View switching (month/week/day/agenda) now fades through instead of
snapping — lateral navigation per M3, while in-view paging keeps its slide.
Add full predictive-back support:
- enableOnBackInvokedCallback in the manifest.
- New Modifier.predictiveBack(onBack) drives the standard preview transform
(scale/shift/round) following the back gesture; applied to detail, edit,
search, settings (+ sub-screens & calendar manager via CollapsingScaffold),
the calendar editor and import — each keeping its existing back semantics.
Reduced-motion guardrail throughout: rememberReduceMotion() (reads the OS
"remove animations" setting, which Compose ignores by default) collapses
spatial motion to a quick fade and skips the back preview.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
On-device review follow-ups:
- Month widget grid is now tappable. Day numbers open that day and event
bars open the event's detail, both rooted in the month view so back
returns to the grid. Previously only the prev/next/today header
controls responded — the grid cells were never clickable.
- Pill/drawer view switches now build a visit history instead of
collapsing to the default view. Back retraces the views you moved
through (a not-yet-visited view is pushed; revisiting one collapses the
loop back to it), down to the default, then exits. Widget launches
still reset to their own view context.
Refs #1, #2 (reported by @devinside).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace CalendarHost's single global view slot with a top-level view
back stack rooted at a user-configurable default view. A lateral move
(pill/drawer/widget) replaces the non-home top; a date tap drills the
day view on top; a base-level BackHandler pops one level until only the
home view remains, then the system exits.
Widgets now carry their source view (EXTRA_SOURCE_VIEW) so a launch
roots the stack in that widget's view: backing out of a day/event opened
from the agenda widget returns to Agenda, and from the month widget to
Month, instead of always landing on Week. Reminder taps keep the
separate detail-key channel and leave the base view untouched.
Add a Default view setting (Settings -> Appearance) backing the stack's
home view; defaults to Week so existing users see no change.
Resolves the two Codeberg reports from @devinside:
- #1 [FR] Option to set default view
- #2 [Bug] Widget UX improvement
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bump versionName to 2.8.0 (versionCode 20800) — the merge of this to main
is what cuts the release. Move the accumulated 2.8.0 work out of
[Unreleased] into a dated CHANGELOG section and regenerate the F-Droid
per-version changelog.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The search ViewModel is activity-scoped and outlives the overlay, so a reopened
search showed the previous query/results — reset the query when the screen
re-enters (peeking a result keeps it, as the screen stays composed under the
detail). Add imePadding so the idle/empty message re-centres in the area above
the keyboard instead of staying centred on the full page behind it.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>