- Admin settings tab (admin-only) now holds user management plus a server-wide
default theme editor (live preview + discard) and logo/favicon branding.
- New singleton InstanceSettings model + /api/instance router (public GET for
the login screen; admin-gated theme PUT and logo/favicon upload/delete,
reusing the avatar PIL/validation pattern).
- Instance default theme is the base users inherit and the target a per-colour
"Reset" returns to (baseColor: instance default -> built-in).
- Custom favicon overrides the primary-colour tinting; custom logo replaces the
top-left glyph+text, hard-capped so it only scales down and never breaks layout.
Branding + defaults load before login (public endpoint).
- Theme export now uses the recognised .theme.json extension (old .theme still
imports); import stays partial/unknown-key aware.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Theme:
- Export/import themes via UI as <date>_<time>.theme (JSON, readable keys,
link to THEME.md). Import is partial-aware: only present params are written
(server accepts partial); prompts before importing files with unknown params.
- Dynamic favicon + theme-color tinted to the primary colour on load/save.
- New themable colours: general hover-highlight, day hover/selected/bg,
today background, plus two unified sidebar action-icon colours
(inactive/active) covering bell, hide, delete and read-only icons.
- All new colours are per-setting syncable; documented in THEME.md.
UX:
- Styled confirm dialog (#modal-confirm) replaces window.confirm() for
calendar delete and account disconnect.
- Birthday/local calendars and iCal subscriptions can now be hidden from the
sidebar via Settings (new sidebar_hidden column + hide toggle).
Backend: additive nullable columns + idempotent migrations for user_settings
colours and local_calendars/ical_subscriptions.sidebar_hidden.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- LICENSE: GNU GPLv3
- fastlane/metadata (de-DE + en-US): title, descriptions, changelog, icon —
used by both the own F-Droid repo and the official listing
- fdroid/com.scarriffle.calendarr.yml: ready-to-submit fdroiddata build recipe
- FDROID.md: how to run the own repo (fdroid.scarriffle.com) and submit to
official F-Droid, incl. the signing-key caveat
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The all-day strip repeated a multi-day event once per day; now events span their
start→end columns and pack into lanes (like the month view) via a GeometryReader
layout.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Lay out all-day events as bars across their start→end day columns, packed into
lanes, instead of listing a multi-day event separately in each day's column
(mirrors the month view and iOS).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
_cal_dict now labels shared (non-owned) calendars by the sharer/group name
(the value already computed for shared_by) instead of the owner's real calendar
name. Fixes recipients seeing the true name in calendar-management lists (and
anywhere else the name is shown); all clients pick it up automatically.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- gear now opens a full-screen Menu screen (Profile / Appearance / Accounts /
Groups / Sync / Switch server / Logout), replacing the bottom-sheet menu that
sat partly under the system nav buttons. Each entry opens its own full-screen
page and returns to the menu on close.
- settings 'Darstellung' is the appearance table only again (no long scroll to
reach navigation)
- much more compact settings: dropdowns use a small outlined chip instead of a
full-height OutlinedTextField, smaller sync icons, bodyMedium row labels
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- move the top-bar hamburger to the LEFT (drawer opens from the left)
- much denser drawer: compact header, and calendar rows now use a small eye
toggle instead of the bulky Switch, with tighter padding/text
- tapping the gear now opens the full-screen Settings directly (no bottom-sheet
menu that sat under the system nav buttons, no redundant sync — that's in the
drawer header). Accounts / Groups / Profile / Switch server / Logout moved
into a 'More' section at the bottom of Settings.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Mirror the iOS Contacts birthday importer: read the address book's birthday
events and reconcile them into the user's birthday calendar, scoped to this
device via external_uid 'contact:<deviceId>:<contactId>' (add/update/delete),
then report the device.
- READ_CONTACTS permission + ContactsReader (Event TYPE_BIRTHDAY, handles
year/no-year date formats)
- API: GET /birthdays, POST /birthdays/sync-report; local-event create/update
now carry external_uid (+ update carries rrule/birth_year)
- CalendarViewModel.syncContactBirthdays reconciles like iOS; device-local
enable flag + stable device id in SettingsStore
- birthday dialog gains an 'Import from contacts' action (requests permission);
auto-syncs on launch when enabled
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Mirror the iOS side drawer on Android with a ModalNavigationDrawer opened by the
hamburger or an edge-swipe (replaces the calendar filter bottom sheet):
- header with avatar/name/server + manual sync, full menu (gear), and close
- view + group switcher chips
- flat calendar list: tap toggles visibility, long-press opens a context menu
(hide/banish + reminders), and a Sort toggle reveals up/down reordering.
Order is device-local (SettingsStore.calendarOrder, mirrors the web).
- month-switching is now a named dropdown (scroll vs pages) like web/iOS
- new device-local 'Hide menu button' setting (drawer opens via edge-swipe)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- remove the show-all / hide-all buttons (rarely needed with a few calendars;
the web has none). The calendar section now shows a 'Calendars' header + a
slim Sort toggle.
- larger header (avatar, name, action icons) with more padding
- more vertical breathing room between header / view switcher / groups / list
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Testing feedback:
- calendars are now one flat, drag-reorderable list (no per-source grouping);
order is device-local (CalendarStore.calendarOrder, mirrors the web cal_order).
Reordering is done via a 'Sort' toggle (native edit mode with drag handles).
- banish + reminder-mute moved from row swipe actions to a long-press context
menu, which frees the horizontal swipe: swiping the drawer left closes it.
- the hamburger moved to the LEFT (drawer opens left); new device-local setting
'Hide menu button' (open via edge-swipe only), not synced.
- the settings entry moved from the footer into the header as an icon, next to a
restored manual-sync icon and the close button; the footer button is gone.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
listRowBackground(.clear) so only the two bordered button pills show, without
the surrounding grouped-list capsule.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the boolean "month view as pages" toggle with a Monatswechsel dropdown:
Continuous scroll vs. Page by page — same names/format as the web client.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
New 'Monatswechsel' setting (synced flag already existed as month_view_paged):
- Continuous scroll (week): current behaviour — wheel scrolls by a week,
buttons/swipe jump 4 weeks.
- Page by page (month): wheel navigation is disabled; buttons and horizontal
swipe jump a whole calendar month.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Testing feedback:
- show-all / hide-all are now side by side (bordered), not two big stacked rows
- the busy quick-access strip becomes a single "Einstellungen" button that
opens the full menu (which already holds accounts/profile/groups/server/sync/
logout)
- extra horizontal + bottom padding so the footer button is not clipped by the
iPhone's rounded corners / home indicator
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the top-bar menu popup with an off-canvas left drawer, opened by the
hamburger or a narrow left-edge swipe (kept off the content area so month/week
swipe stay free). The drawer hosts the calendar visibility list, a view
switcher, group switching, and quick access to Settings/Accounts/Profile/
Groups/Server plus sync and logout.
- extract the filter list into reusable CalendarFilterContent (shared by the
drawer and the still-present CalendarFilterSheet); the sheet is now a thin
wrapper
- new CalendarDrawer view; CalendarHostView hosts it as a ZStack overlay with a
scrim and edge-swipe gesture, and presents drawer destinations as sheets
- opt-in surface colour: surfaceColor (device-local) tints the flat top bar;
empty keeps the translucent .bar material. Settings row with an Auto reset.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Redesign the settings screen into uniform rows (sync icon | name | value):
dropdowns replace the chip pickers, every colour gets a reset to a canonical
default, a per-row sync icon toggles that setting's cross-device sync, and a
global switch flips all. Enabling a flag adopts this device's current value.
- AppSettings/SettingsStore: add text/bg/line colours, cache_months,
month_view_paged, and the account-wide sync_flags map (+ DEFAULT_SYNC,
effective-merge on pull)
- CalendarRepository.updateSettings: partial PUT of only the synced values plus
the flag map (unsynced columns stay untouched server-side)
- Theme: fully dynamic — primary/accent tint, background/text/line drive
background/onBackground/outline (matching web); today/divider/label unchanged
- add hide-profile (directory_hidden) to the profile block (parity with web/iOS)
- language + text/line contrast stay device-local
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The settings modal's own left nav and header were on --bg-app; point them at
--bg-sidebar / --bg-topbar so they track surface_color like the main sidebar.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- new user_settings.surface_color (nullable, device-local default, not synced):
drives the web sidebar / top bar / surfaces; NULL derives from bg_color as
before. Added to schema, migration, GET/PUT, NULLABLE_OVERRIDES, DEFAULT_SYNC.
- web: surface colour row in the settings table; applyTheme derives the whole
surface family from it
- web: per-row sync icon now has real styling — ON shows the primary-tinted
glyph on a pill, OFF shows a slashed, muted glyph (was previously unstyled,
so synced/not-synced looked identical)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Generalise the two-tier SettingsSync into a server-driven flag map: each
syncable key has an account-wide flag (fetched from the server); pull applies
only on-flagged keys, push (read-modify-write) sends only on-flagged keys plus
the flag map. Per-row link icon toggles a setting's sync; a global switch flips
all. Enabling a flag adopts this device's current value as the shared one.
- AppSettings: fix bg_color mapping (was background_color), add cache_months,
month_view_paged, sync_flags
- SettingsView: uniform rows (sync icon | name | value), ContrastSelector and
the button pickers replaced by menu dropdowns, every colour gets a reset to a
canonical default, global "sync all" toggle; account settings (privacy,
group calendar, hide profile) kept in their own block; language and text/line
contrast kept device-local (iOS-specific)
- unify default line colour to #3A3A52 across views
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- style the settings-table dropdowns for the dark theme (were white)
- share-icon picker on one even row (no lopsided wrap)
- more breathing room between colour hex and swatch
- user management: promote/demote admin (new PUT /users/{id}/admin,
guarded against self-change and removing the last admin)
- calendar management table: clip/ellipsis cells so narrow columns no
longer overlap (full-width detail rows still wrap)
- centralise default colours: utils.applyTheme now derives every fallback
from settings-sync.DEFAULT_COLORS (single place for coders to edit)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the ad-hoc appearance panel with a uniform table (sync icon | name |
value): dropdowns instead of button groups, every colour with a reset to a
canonical default, a per-row sync toggle, and a global "sync all" master
switch.
- new settings-sync.js: canonical default colours, declarative table
definition, browser-local value copy, effective-value resolution
- calendar.js: generalised sync engine (pull applies only synced keys from the
server, save pushes only synced keys + the flag map), unified the two save
buttons into one, dropped the dead text/line-contrast code, moved default
duration into the appearance table, effective UI language on load
- account settings (private visibility, group calendar, hide profile) stay in a
dedicated profile block, not the sync table
- app.css: settings table / sync toggle / sync icon styles
- i18n: sync-all / sync-this / share-icon strings; sw cache bump
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Shared reference for Web/iOS/Android: canonical keys, default flags, the
GET/PUT sync_flags API, and the pull/push rules each client implements.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Introduce an account-wide sync-flag map that is the single server-side
authority for which settings each client sends/fetches, plus value columns
for two newly-syncable device-local prefs.
- models.UserSettings: sync_flags (JSON), cache_months, month_view_paged
- main._migrate(): idempotent ALTER TABLE for the three new columns
- settings_router: DEFAULT_SYNC map + _resolve_sync_flags(); GET returns
fully-resolved sync_flags (+ cache_months, month_view_paged); PUT accepts
and merges a partial sync_flags map and the two new value fields
Rollout defaults: settings that already lived on the server sync ON; the
four newly-syncable prefs (language, share icon, cache range, month paging)
sync OFF until the user opts in.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- add "Geburtstagskalender" to the sidebar +calendar dropdown and a "+ Geburtstage"
button to Settings > Calendars; both call ensureBirthdayCalendar (server enforces
one per account) and toast "already exists" if present
- Settings birthday section: when none exists, just a hint pointing to "+ Geburtstage"
(creation lives in the add area now, not a separate activate button)
- resizable calendar table: sensible default column widths instead of raw measured
- i18n de/en
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Contacts sync fills only an EXISTING birthday calendar (birthdayCalendar),
never auto-creates it — deleting it no longer resurrects it on sync
- Accounts + menu gains "Geburtstagskalender" (only when none exists) to create
the single birthday calendar explicitly
- the Birthdays-from-Contacts section shows a "create one first" hint when there
is no birthday calendar; enabling sync only requests Contacts access
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- POST /local/calendars with is_birthday returns the existing birthday calendar
if one exists (idempotent, no second created)
- PUT /local/calendars rejects (422) marking a second calendar as is_birthday
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Column widths are draggable via a handle on each header's right edge and
persisted in localStorage (re-applied on re-render). Uses fixed table layout
locked to measured px widths so resizing one column doesn't reflow the others.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Repeated Contacts syncs must never create duplicate birthdays. Enforce it on
the server, independent of the client's reconcile:
- POST /local/events is now idempotent on external_uid: if an event with the
same (calendar_id, external_uid) exists, update it in place instead of
inserting a new row
- startup cleanup removes existing duplicates sharing the same
(calendar_id, external_uid), keeping the earliest — auto-heals old data
- unique DB index on (calendar_id, external_uid) as a hard guarantee
external_uid is the stable "contact:<deviceId>:<contactId>", so the same contact
from the same device always maps to exactly one event.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The reading-column max-width added for the profile panel also constrained the
wide calendar table, forcing a half-width horizontal scroll. Exempt
#settings-panel-accounts so the table uses the full width.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Play requires targeting a recent API level for new apps; bump compileSdk/
targetSdk to 35 and set the release version name to 1.0.0.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- one birthday calendar per user: BirthdayDialog targets the single calendar and
offers activation when none exists; VM birthdayCalendar()/ensureBirthdayCalendar()
- removed the birthday toggle from the generic new-calendar dialog
- exclude the birthday calendar from the group-visible ("shared calendar") picker
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- one birthday calendar per user; BirthdaysImporter auto-creates/targets it
(no picker) and reconciles per-device (external_uid contact:<deviceId>:<id>)
so multiple devices don't clash
- Contacts birthday sync now runs on every "Sync with server" (syncFromServer),
not just app launch and the manual button
- report the device after each sync (POST /api/birthdays/sync-report) for the
web device list
- Accounts: Birthdays-from-Contacts section drops the target picker, keeps the
reminder picker; removed the birthday toggle from the generic new-calendar sheet
- New-birthday sheet targets the single calendar, offers activation if none
- exclude the birthday calendar from the group-visible ("shared calendar") picker
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- new BirthdaySyncDevice table + /api/birthdays/sync-report & /devices so the
web can show "birthdays come from these devices" (iOS reports its device on
each Contacts sync)
- settings: reject setting a birthday calendar as the group-visible calendar
(it can still be shared directly)
- web: Settings > Calendars > Birthdays shows the device list; exclude birthday
calendars from the group-visible picker
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- CalEvent parses is_birthday and exposes renderTitle (server age)
- EventLabel renders a cake icon + render title in month/week/day/agenda/preview
- LocalCalendar model + repository/api gain is_birthday, notify-days, rrule,
birth_year; AccountsScreen can create a birthday calendar with a notify picker
- FAB long-press menu -> "New birthday" opens a minimal name+date dialog
(year-unknown option) targeting a birthday calendar
- L10n strings
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- constrain settings panels to a 680px reading column so fields no longer
stretch edge-to-edge on wide screens (main "looks bad" cause)
- fix the app-password "Erstellen" button rendering centred below the input:
.app-pw-create inherited flex-direction:column from .form-group; force row so
it sits inline to the right of the field
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- one dedicated birthday calendar per user; enable it in Settings > Calendars
(or from the New-birthday dialog) — it then shows in the sidebar like any
calendar (colour/visibility)
- New-birthday dialog: no target picker; day/month selects + a year field that
hides when "year unknown"; fixed the year-unknown checkbox layout
- create split-button is now one seamless pill; the caret opens the menu
- Settings > Calendars gains a Birthdays section (enable + notify-days-before)
- removed the birthday toggle from the generic new-calendar modal
- profile settings: right-align the section save button for consistency
- bump service-worker cache to v25 so assets refresh
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- views render display_title (age) and a cake icon for is_birthday events
(month/week/agenda + quarter tooltip) via shared eventTitle/birthdayIconSvg
- create split-button caret opens a menu: new event / new birthday
- new birthday modal: name + date + "year unknown" + target birthday calendar,
saved as an all-day FREQ=YEARLY local event with birth_year
- local-calendar modal gains a "birthday calendar" toggle + "notify N days before"
- i18n de/en strings
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- LocalCalendar/CalEvent gain is_birthday + birthday fields; API sends
rrule/external_uid/birth_year and can create/update birthday calendars
- BirthdaysImporter: mirrors Contacts birthdays into a chosen birthday
calendar, reconciling by external_uid (leaves manual entries untouched)
- AccountsView: "birthday calendar" toggle + notify-days when creating a
calendar, and a "Birthdays from Contacts" section (enable + target + sync now)
- FAB long-press menu -> "New birthday" opens a minimal name+date mask
(BirthdayEditorSheet) targeting birthday calendars, with year-unknown option
- Event bars render display_title (age) and a cake icon via EventLabel
- NSContactsUsageDescription added to both app build configs
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Birthday calendars are ordinary local calendars flagged is_birthday, so they
flow to all clients via the merge read and inherit sharing/colors/reminders.
- models: LocalCalendar.is_birthday + birthday_notify_days_before;
LocalEvent.external_uid (Contacts dedup) + birth_year
- build_local_event_dict: server-computed display_title "Name (age)" per
occurrence, is_birthday flag for the client cake icon, and a reminder injected
from birthday_notify_days_before so mobile schedulers fire it
- groups combined view keeps the birthday display_title instead of overwriting it
- local_router: calendar flags + event fields on create/update, plus
GET /calendars/{id}/birthdays for importer reconcile by external_uid
- additive SQLite migrations
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
New device-local setting "Monatsansicht seitenweise wischen" (@AppStorage, no
server sync). Off keeps the continuous vertical scroll feed; on switches the
month view to a paged TabView — one month per screen, six height-filling week
rows, swipe left/right to change month (bounded ±monthsBack/Ahead range to keep
the TabView light). WeekRow gains a fillHeight flag so the same row renders at a
fixed height (scroll) or fills the page evenly (paged); its inner cells/divider
now size to the actual geometry. Paged selection is two-way bound to the store's
current date so prev/next/today and the title stay in sync.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
New device-local setting "Monatsansicht seitenweise wischen" (SettingsStore,
no server sync). When off, the month view stays the continuous vertical scroll
feed. When on, it becomes a HorizontalPager — one month per screen, six
height-filling week rows, swipe left/right to change month. The prev/next/today
signals route to the pager (animateScrollToPage) instead of the list, and the
visible month is derived from pagerState.currentPage. WeekRow now takes a
rowModifier so the same row renders at a fixed height (scroll) or weight(1f)
(paged). Setting is re-read when the settings screen closes.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>