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>
A magnifier in each calendar screen's top bar opens a search overlay: type a
query and matching events (title, location or description) appear, nearest-to-
today first; tapping a result opens its detail.
- Data: query the Events table directly with a LIKE selection on title /
description / location (wildcards escaped), so search is unbounded in time and
filtered provider-side. New SearchProjection + toSearchResult mapper reuse the
.ics export's DURATION handling for recurring masters. Hidden calendars are
filtered out, mirroring instances().
- SearchViewModel debounces the query (250 ms), needs >= 2 chars, and orders
results upcoming-ascending then past-descending.
- SearchScreen: autofocused inline field in the top bar, GroupedRow results
reusing the agenda row style, idle/empty states. Hosted as a CalendarHost
overlay below detail/edit so a tapped result's detail draws on top.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The create form and Settings/calendar-manager are sibling overlays in one Box,
with Settings drawn above the form. An external 'new event' request (QS tile,
launcher shortcut, widget) set the create state correctly but rendered the form
underneath an open Settings, forcing the user to back out first.
Dismiss the covering overlays (Settings, calendar manager, detail/edit, import)
when handling a Create or OpenDate nav request, so the requested destination is
revealed on top. Fixes the same latent bug for the shortcut and widget.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Completes v2.7 Branch 2. Wires the import core into the app:
- Manifest ACTION_VIEW/SEND for text/calendar; MainActivity parses the
incoming Uri (content/file only, so calendula:// deep-links don't match)
and routes it through RootScreen → CalendarHost like the other one-shot
intents.
- ImportViewModel reads + parses the file and routes by count: one event →
the prefilled create form for review (EventEditViewModel.openImported,
which freezes the reminder default so the file's reminders win); many →
ImportScreen with a writable-calendar picker, then a bulk import (UID
dedup) and a result summary.
- ImportScreen also surfaces parser warnings (skipped recurrence overrides,
ignored attendees, unknown-timezone fallback). Strings EN+DE.
Package is ui.imports (not ui.import — Java keyword). lint + test +
assembleDebug green. No v2.7 tag until on-device review.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bundles the unreleased Tier 2/3 work into one release:
- Home-screen widgets (Glance): an "Upcoming" agenda widget and a month-grid
widget, both reusing the in-app grouping/layout (groupAgendaDays,
layoutMonthWeeks) via a Hilt WidgetEntryPoint, honouring hidden-calendar
filters and refreshing on PROVIDER_CHANGED / date rollover.
- App shortcut: launcher long-press "New event", routed through the shared
WidgetNavRequest.Create channel into the create-event form.
- Agenda view and jump-to-date (already merged via #3/#4) are documented here
as part of the shipped version.
Bumps versionCode 20500 / versionName 2.5.0, moves the CHANGELOG Unreleased
section under [2.5.0], updates ROADMAP/STATE, and adds EN+DE strings.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The fourth top-level view, alongside Month/Week/Day. A forward-looking
LazyColumn of upcoming events grouped under sticky day headers, reusing
the v2.3 grouped-list language (GroupedRow cards, color-rail leading).
- AgendaViewModel loads a 60-day forward window from the anchor day
(today by default; goToToday/goToDate drive the FAB + drawer jump),
groups instances by local day (ongoing/multi-day clamped to the
anchor), sorts all-day-first then by start.
- AgendaScreen: same drawer + scaffold + view-switcher + FAB shell as
Day; sticky "Today · …"/"Tomorrow · …" headers, event rows with
time·location, plus empty/failure/loading states.
- Wired into CalendarView (ViewAgenda icon), IMPLEMENTED_VIEWS, and
CalendarHost; strings added (EN + DE).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Day/week: tap an empty slot to open the create form prefilled with that
day and the tapped hour (snapped to the hour, 1 h long). Threaded a start
time through CalendarHost → EventEditScreen → openNew; the FAB keeps its
default.
Local calendars: a full-screen editor from Settings → Calendars to
create/rename/recolor/delete device-only calendars (ACCOUNT_TYPE_LOCAL,
sync-adapter insert) with name, pastel-previewed colour, and a description
(stored in CAL_SYNC1). Synced calendars are listed read-only grouped by
account, each with a "manage in source app" deep-link resolved from the
account's own authenticator (DAVx5/ICSx5/…), plus an add-account shortcut;
a <queries> block makes the source apps launchable. Extracted a shared
InlineTextField into ui.common so the event form and calendar editor share
one borderless input style.
Tests: repository delegation + write-failure, mapper isLocal/description,
fake data source extended. Version bumped to 2.2.0 / 20200.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Calendula now posts event reminders itself (the Etar model): the provider
schedules the alarms and broadcasts EVENT_REMINDER, but a calendar app must
turn them into visible notifications — essential for users whose only
calendar app this is. A manifest-registered, exported receiver (data scheme
content://com.android.calendar) wakes us at reminder time; no foreground
service, no own alarm scheduling.
Delivery path (data/reminders/): EventReminderReceiver (Hilt, goAsync) →
ReminderAlertStore queries CalendarAlerts for STATE_SCHEDULED rows with
ALARM_TIME <= now → ReminderNotifier posts one notification per alert on a
dedicated high-importance channel, then best-effort marks rows FIRED
(needs WRITE_CALENDAR; without it a re-broadcast silently replaces — tag
per alert + setOnlyAlertOnce). Swiped notifications never return: FIRED
rows are never re-queried, so no dismiss-intent machinery. Research
(AOSP CalendarAlarmManager): the provider creates alert rows only for
METHOD_ALERT reminders, so the email-reminder filter happens upstream.
Tapping opens the event's detail screen: MainActivity is singleTop now,
parses eventId/begin/end extras (onCreate + onNewIntent) into Compose
state, and CalendarHost consumes the key exactly like an event tap.
Onboarding gained a one-time second step after the calendar grant (shared
OnboardingScaffold extracted from PermissionScreen): explains delivery,
warns that a second calendar app with notifications on duplicates
reminders, requests POST_NOTIFICATIONS (dialog on API 33+ only; minSdk 29).
"Not now" turns the feature off; reminders default ON. Settings mirrors
the toggle in a new Notifications section with the duplicate hint, and
re-requests the permission when enabling. Strings DE+EN.
Deliberately deferred (roadmap): snooze/dismiss actions, BOOT_COMPLETED /
exact-alarm scheduling, battery-exemption prompts.
Tests: reminderTimeText (all-day UTC-midnight reading, exclusive end day,
midnight-crossing ranges), reminders/onboarding pref round-trips.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The create form (v1.2) now edits: a pencil on the detail screen (writable
calendars only, contextual WRITE upgrade like delete) opens it prefilled via
EventDetail.toEditForm; populated sections always show, the calendar is
fixed, and a dirty-check writes only changed columns (pristine saves are
no-ops). Saving a dirty recurring event parks in SaveUiState.AwaitingScope
and asks how far the change reaches (Google model): "only this event" =
modified-occurrence exception via CONTENT_EXCEPTION_URI (empty optionals as
explicit NULLs since the provider clones the parent row), "this and all
following" = series split (insert new event first, then truncate), "all
events" = series-row update with the time delta applied to the series
DTSTART. A changed rule drops the exception option. Delete gained the same
middle scope.
Recurrence: EventForm.rrule + SimpleRecurrence (FREQ/INTERVAL/UNTIL/COUNT +
weekly BYDAY with locale-ordered weekday toggles) behind a picker on create
and edit; unrepresentable rules render humanized (shared ui/common
RecurrenceText) and survive verbatim. UNTIL validation flags rules ending
before the event starts.
Provider lessons baked in (verified on-device via adb probes): instance
caches regenerate only from an update's own values, so truncation sends the
full time-column set (truncateSeries) — RRULE-only updates left a stale
duplicate occurrence on the split day; UNTIL is written as the local end of
day in UTC (toRRule(zone), previousLocalDayEndUtcMillis) so UTC+x zones
can't leak an extra day. Reminder edits reconcile against actual provider
rows, keeping untouched rows' methods.
Tests: RecurrenceTest (parse/render/round-trip, truncation), update/exception
mapper paths, repository pass-throughs, prefill + populatedFields, raw-title
mapper.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Second slice of milestone 2 (write support):
- EventForm domain model + problems() validation (end-before-start,
no-calendar; blank titles and instant events stay legal)
- Full-screen EventEditScreen: title, all-day switch, M3 date/time pickers
(moving the start preserves the duration), calendar picker limited to
writable calendars, location, description. Save validates, requests the
WRITE upgrade contextually, and closes on success
- Calendar preselection: explicit pick > last-used (CalendarPrefs) > first
writable calendar
- insertEvent in the data source; EventWriteMapper (JVM-tested) normalises
all-day events to UTC midnights with exclusive DTEND, timed events to the
device zone
- CalendarFabColumn shared by month/week/day: persistent "+" FAB anchored on
the visible day, jump-to-today pill stacked above it
- Tests: EventForm validation, write-time mapping (incl. DST-safe epoch
check), repository createEvent delegation/error propagation
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
M3 — calendar filter: the navigation drawer now hosts the calendar list
inline (grouped by account, colour swatch + checkbox per calendar). Hidden
calendars are persisted app-side and filtered centrally in the repository,
so month/week/day re-filter live the moment a checkbox flips. Drawer trimmed
to Today, the calendar filter, and Settings, with leading icons and a clear
title/section type scale; the stubbed jump-to-date entry (M2) was removed.
M4 — settings: full-screen destination with appearance (theme System/Light/
Dark, Material You dynamic colour auto-disabled < API 31, week start Auto/Mon/
Sun), language (per-app locales via AppCompat, persisted to API 29), and an
about section (version, licence, source link). Theme is driven by one
activity-scoped settings source so changes apply app-wide at once. Week start
now drives the month grid and week view; Auto follows the locale.
Also:
- default view switched from month to week
- Settings screen handles system back (was closing the app)
- fix pre-existing NonObservableLocale/LocalContextConfigurationRead lint
errors in EventDetailScreen so CI lint is green again
- versionName/versionCode bumped to 0.5.0 / 5
Tests: repository hidden-filter (incl. live re-emit), SettingsPrefs round-trip
+ week-start resolution, filter grouping. lint + unit tests + assembleDebug green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Tapping an event in the week/day timeline opens a full-screen detail
destination (MD3 list→detail, not a bottom sheet) overlaying the calendar
with a slide transition. One card per field (when, calendar, location,
description, attendees, recurrence) with leading icons; location taps open
a maps intent. Loading/Failure/Success throughout.
Recurrence is humanized from the RRULE — e.g. "Every week on Tue and Thu
until 31 Dec 2026" — covering FREQ/INTERVAL/BYDAY/UNTIL/COUNT with
abbreviated, italicised day names and localized list formatting, falling
back to a generic label for rules it can't render.
Also:
- fix: recurring events failed to open (series row stores DURATION, not
DTEND, so the mapper dropped them as EventNotFound). The detail keeps
them and shows the tapped occurrence's own times from Instances.
- feat: month day cell → opens the day view anchored to that date.
- build: add material-icons-extended (R8 strips unused icons in release).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Day view as a one-column slice of the week view: shared TimedBlock/
AllDaySpan layout, per-day swipe navigation, hoisted noon-centred scroll,
animated all-day strip, and a compact top bar showing the full date.
- DayUiState / DayViewModel / DayScreen under ui/day
- reuse layoutDay/layoutAllDay/coversDay from the week package
- add Day to IMPLEMENTED_VIEWS; CalendarHost routes it explicitly
- day_today_action strings (en/de)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the throwaway debug screen with the first real calendar UI and a
functional Month <-> Week switcher, on Material 3 Expressive.
Month view (S1):
- Material 3 Expressive card-per-day grid; only the current month's weeks
render (neighbouring days left blank)
- per-day event dots with "+N" overflow, today via primaryContainer
- spring-based press feedback from the active motion scheme
- swipe + drawer navigation, Loading/Failure/Success states
Week view (S2):
- vertical time schedule with overlap-resolved lanes (per-day clipping,
midnight spanning, instant events)
- all-day / multi-day events as connected horizontal spans
- single scroll container (gutter + day columns stay aligned), columns
bundled in a rounded container, noon-centred on load
- top section colour-shifts with the app bar on scroll; swipe navigation,
three states
Shared / infra:
- CalendarHost holds the active view; RootScreen renders it post-permission
- ui/common building blocks: CalendarDrawer, CalendarFailure,
ViewSwitcherPill, pastelize, observable locale, M3 Expressive slide
transition (motionScheme fastSpatialSpec)
- unit tests for the week layout (lanes, clipping, all-day spans)
- build: compileSdk 37, material3 pinned to 1.5.0-alpha21 for Expressive
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>