Commit Graph

215 Commits

Author SHA1 Message Date
98d76deee5 fix(edit): correct the recurrence picker's read-out and end handling
Review follow-ups to the picker redesign, plus the bugs it exposed.

The live read-out now renders customResult itself — the exact string OK
would save — instead of rebuilding a parallel rule with `interval ?: 1`
and `end ?: Never` fallbacks, which let it confidently describe a rule
that differed from the selected controls whenever a field was invalid.

Shrink the invalid space behind it: a blank amount field reads as its
visible placeholder (1 / 10) rather than as an error, and backing out of
the date picker falls back to "never" instead of stranding a dateless
"on a date". Only an out-of-range 0 remains invalid, and that now says so
rather than greying out OK with no cause.

UNTIL displayed the day after the one picked for zones behind UTC:
toRRule deliberately writes the end of the chosen *local* day expressed
in UTC (the provider applies UNTIL coarsely), so the read side must
convert back before taking the date. Fixes the detail screen too, and
untilLocalDate is extracted so it can be tested.

Also: hoist a remember() out of a conditional (a slot that appears and
disappears breaks positional memoisation), match GroupedSurface's 22dp
corners instead of a drifted local 20dp copy, move the cards onto
GroupedSurface, drop the segmented row's icon slot so longer unit labels
fit, and reserve two lines so the stack stops shifting as the phrase
grows with each weekday.

Extract SettingsPrefs.firstDayOfWeek(scope), replacing three copies that
had drifted onto different initialValues.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 15:20:15 +02:00
94355bf340 fix: address code-review findings on the 2.16.0 branch
All checks were successful
Translations / check (pull_request) Successful in 5s
CI / ci (pull_request) Successful in 5m43s
Correctness:
- Recurring writes: move the series DTSTART by the *wall-clock* shift applied
  to the edited occurrence and re-resolve it in the event's zone, instead of by
  a millisecond delta. The old delta baked in whichever UTC offset applied on
  the edited occurrence's date, so pinning a recurring event to another zone —
  or editing an occurrence on the far side of a DST boundary from the series
  anchor — shifted the whole series by an hour. Also snaps the anchor to a UTC
  midnight when the event becomes all-day.
- Detail card and edit form now resolve a pinned zone's abbreviation/offset at
  the *event's* instant, not at "now", so a July event no longer reads
  "CET · 10:00 AM" when opened in January.
- Agenda: the zone used to label multi-day rows now travels on
  AgendaUiState.Success rather than a process-lifetime file-level constant, so
  labelling can't disagree with the grouping after a device time-zone change.
- Week title: spell out the year when the week straddles New Year, via a new
  forceYear flag on formatCalendarTitle.

Performance:
- Build the ~600-entry zone catalogue off the main thread (produceState +
  Dispatchers.Default); resolve the device row's summary on its own so it still
  renders complete on the first frame.
- Pre-normalize each TimeZoneOption's search keys at construction, turning
  ~2400 NFD normalizations per keystroke into plain prefix/substring checks.
  Hoist the combining-mark Regex out of the hot path.
- Key the edit form's local-time line on the fields it reads instead of
  recomputing it on every keystroke.
- Move LauncherNameManager's PackageManager binder calls off the main thread.

Cleanup:
- Drop a duplicate Public icon import and the unused
  event_edit_timezone_clear string.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 13:10:39 +02:00
03f6b7c8c1 feat(settings): optional "Calendar" launcher name (#44)
Add a Settings → Appearance toggle that switches the app's launcher label
between "Calendula" and "Calendar", for users on launchers that can't rename
apps themselves.

The launcher entry moves off MainActivity onto two <activity-alias> components
(DefaultNameAlias / CalendarNameAlias); exactly one is enabled at a time via
PackageManager.setComponentEnabledSetting. MainActivity keeps every other intent
filter; the android.app.shortcuts meta-data moves onto both aliases so the
long-press shortcut still publishes. Component-enabled state is the single source
of truth — no persisted preference.

The ComponentName uses the applicationId for the package (carrying the
.debug/.releasetest suffix) and the namespace for the class, since manifest
".Alias" names resolve against the namespace; switching to Calendar enables the
target alias before disabling the other to avoid a zero-entry launcher transient.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-19 16:39:08 +02:00
9a1d753abc Merge pull request 'feat(views): optional jump-to-today button in the toolbar (#60)' (!84) from feat/today-button-in-toolbar into release/v2.16.0
Reviewed-on: #84
2026-07-19 10:26:09 +00:00
46afa830b3 feat(views): optional jump-to-today button in the toolbar (#60)
Add a "Today button in toolbar" appearance toggle (default off). When on,
each calendar view (month/week/day/agenda) shows a persistent go-to-today
icon button in its top bar instead of the fade-in extended FAB, and the
FAB pill is suppressed. Same jump action, just always present.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-19 12:12:54 +02:00
529dc9c374 Merge remote-tracking branch 'origin/release/v2.16.0' into fix/agenda-multiday 2026-07-19 11:50:29 +02:00
b5c2930609 feat(agenda): spell out multi-day time line instead of arrows
The "→" arrow convention read as unclear. Spell each day out instead:
the first day names the start ("Starts 14:00"), the last day the end
("Ends 10:00"), and whole days in between read as "All day". All-day
multi-day events stay "All day" on every day. Single-day rows unchanged.

Adds agenda_span_starts / agenda_span_ends (owes Weblate backfill).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-19 11:42:15 +02:00
55ffaad94f Translated using Weblate (German)
All checks were successful
Translations / check (pull_request) Successful in 5s
CI / ci (pull_request) Successful in 4m37s
Currently translated at 97.4% (426 of 437 strings)

Translation: Calendula/Strings
Translate-URL: https://weblate.dev.jeanlucmakiola.de/projects/calendula/strings/de/
2026-07-19 09:37:33 +00:00
Weblate
5bdde146c1 Translated using Weblate (German)
Currently translated at 97.4% (426 of 437 strings)

Translation: Calendula/Strings
Translate-URL: https://weblate.dev.jeanlucmakiola.de/projects/calendula/strings/de/
2026-07-19 09:37:33 +00:00
Weblate
97f87fb927 Translated using Weblate (French)
Currently translated at 52.8% (231 of 437 strings)

Translation: Calendula/Strings
Translate-URL: https://weblate.dev.jeanlucmakiola.de/projects/calendula/strings/fr/
2026-07-19 09:37:33 +00:00
augustin64
469166c0bb Translated using Weblate (French)
Currently translated at 52.8% (231 of 437 strings)

Translation: Calendula/Strings
Translate-URL: https://weblate.dev.jeanlucmakiola.de/projects/calendula/strings/fr/
2026-07-19 09:37:33 +00:00
Pablo
b10965babe Translated using Weblate (Russian)
Currently translated at 14.1% (62 of 437 strings)

Translation: Calendula/Strings
Translate-URL: https://weblate.dev.jeanlucmakiola.de/projects/calendula/strings/ru/
2026-07-19 09:37:33 +00:00
Weblate
e5c122a2ee Translated using Weblate (Polish)
Currently translated at 100.0% (437 of 437 strings)

Translation: Calendula/Strings
Translate-URL: https://weblate.dev.jeanlucmakiola.de/projects/calendula/strings/pl/
2026-07-19 09:37:33 +00:00
Bazyli Cyran
3aefc5c8f8 Translated using Weblate (Polish)
Currently translated at 100.0% (437 of 437 strings)

Translation: Calendula/Strings
Translate-URL: https://weblate.dev.jeanlucmakiola.de/projects/calendula/strings/pl/
2026-07-19 09:37:33 +00:00
Bazyli Cyran
ecc643ac81 Translated using Weblate (Polish)
Currently translated at 97.2% (417 of 429 strings)

Translation: Calendula/Strings
Translate-URL: https://weblate.dev.jeanlucmakiola.de/projects/calendula/strings/pl/
2026-07-19 09:37:33 +00:00
ca01f6e729 fix(detail): keep the zone card's hierarchy matching the When card
The When card reads date-large, time-small-beneath. The zone card led
with the original time at titleMedium and dropped the zone label to
bodyMedium, inverting that — which made the foreign time the loudest
thing on the screen and pulled attention off the local time the reader
actually acts on.

Put the label back on top and the original time small beneath it, so both
cards read the same way and the original stays available without
competing. The label already names the zone, so the range drops its "in
New York" tail (and the string with it).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 16:01:08 +02:00
0588609c75 fix(edit): use the family's text input, and show both times when zones differ
Two review fixes.

The zone picker's search box was a raw Material OutlinedTextField — the
only one left in the app, and against the convention DialogControls
states outright ("the family's InlineTextField over a tonal surface, not
Material's outlined field"). Rebuild it on InlineTextField over a tonal
surface, with the clear button inside the surface since the picker's top
bar is the title rather than a search field.

Showing a pinned event only in its own zone answered "what was it set
to?" while dropping "when is it for me?" — the user had to do the offset
arithmetic. Show both whenever they differ:

- the edit form keeps editing the event in its own zone (that's the time
  it was set at) and captions it with the local equivalent;
- the detail screen keeps local times primary and now leads the zone card
  with the original ("8:00 AM – 9:00 AM in New York") instead of naming
  the zone and nothing else.

EventForm.timesIn is pure, so the conversion — including crossing the
date line and each zone's own DST, which don't move together — is a
plain JUnit test rather than something only reviewable on a phone.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 15:54:33 +02:00
fefe1f3652 feat(edit): add a searchable time-zone picker (#31)
Surface the zone as an optional form field, right after the time fields
it qualifies: hidden on ordinary events, revealed automatically when the
event already carries a foreign zone, and withheld entirely while all-day
is on (a date-anchored event has no zone to show).

The picker is a full-screen one per the app's convention, but it can't
reuse OptionPicker: that composes every option eagerly, and ~600 zones
would all compose on open. It drives its own LazyColumn instead, which
needs the kit's new `scrollable = false` — the scaffold's own
verticalScroll would otherwise throw on a nested same-axis scrollable.
The device zone and recently-picked zones pin to the top so the common
case needs no typing; search is accent- and case-insensitive.

Recents persist in DataStore, capped at five, dropping ids the tz
database no longer knows.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 15:32:04 +02:00
50510a23da feat(colors): optional raw calendar colours + readable title contrast
Add a "Soften calendar colours" setting (Settings → Design, default on).
Turning it off paints calendar and event colours raw, exactly as the sync
source (DAVx5/CalDAV) publishes them, instead of the theme-fitting pastels
(#36).

Event/calendar colours now flow through shared eventFill()/eventInk()
helpers gated by a LocalSoftenColors composition local (widgets read the
pref directly). Event titles pick black or white text by the fill's WCAG
relative luminance, so a dark colour stays legible whether softened or raw
(#21) — previously always near-black.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 20:26:34 +02:00
3d59962112 i18n: offer French and Polish in the language picker
Both landed as community translations through Weblate (French ~33%, Polish
~57%), above the bar already shipped for zh-CN. Add them to locales_config.xml —
the single source of truth for the in-app language picker and Android's per-app
language settings.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 19:40:10 +02:00
a96d8b6de9 Merge remote-tracking branch 'origin/main' into release/v2.15.0 2026-07-15 19:39:08 +02:00
Bazyli Cyran
85fb091e22 Translated using Weblate (Polish)
All checks were successful
Translations / check (pull_request) Successful in 4s
CI / ci (pull_request) Successful in 4m31s
Currently translated at 58.7% (252 of 429 strings)

Translation: Calendula/Strings
Translate-URL: https://weblate.dev.jeanlucmakiola.de/projects/calendula/strings/pl/
2026-07-15 17:34:15 +00:00
2139f8729c Merge remote-tracking branch 'origin/main' into release/v2.15.0 2026-07-15 19:28:22 +02:00
b3386eff43 Merge branch 'release/v2.15.0' into feat/duplicate-events 2026-07-15 19:10:29 +02:00
79e74d9995 feat(detail): duplicate an event as a new one (#52)
Add a Duplicate action to the event detail top bar that opens the shared
event form seeded with a copy of the event as a new, unsaved event, so a
non-recurring event can be re-created with just the day and time changed
instead of re-entering every field.

The copy reuses the existing prefilled-create overlay (createEvent), so it
becomes an independent event with the default reminder applied. Recurrence
is dropped — a duplicate is a single event; the edit form still exposes a
recurrence picker for anyone who wants a series. The occurrence's own times
carry over unchanged. Duplicate is offered for any loaded event, including
read-only ones (WebCal, birthdays): the source calendar is kept only when
it's writable, otherwise the copy resolves to the first writable calendar.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 19:10:23 +02:00
b9329f6fb6 feat(agenda): anchor today with an empty-day card (#35)
Add an "Always show today" setting (Settings → Agenda, on by default) that
keeps today as the first entry in both the Agenda screen and its home-screen
widget even once nothing is left today. Under today's normal header a small
"No more events today" card appears — the coffee-cup empty-state motif in the
app, a rounded surface in the widget — so the first rows you see are clearly
today's rather than a future day's.

The anchor is a pure, JVM-tested helper (anchorTodayIfMissing) applied after
past-event filtering; in-app it only kicks in when the window starts on today,
never on a jumped-to date. The widget reads the pref reactively via per-instance
Glance state, mirroring the range/past-event settings.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 18:39:27 +02:00
9aa370d583 Merge remote-tracking branch 'origin/main' into release/v2.15.0
# Conflicts:
#	CHANGELOG.md
#	app/src/main/java/de/jeanlucmakiola/calendula/data/calendar/EventWriteMapper.kt
2026-07-15 17:23:31 +02:00
iker Contreras
8cd75716ae Translated using Weblate (Spanish)
All checks were successful
Translations / check (pull_request) Successful in 27s
CI / ci (pull_request) Successful in 10m52s
Currently translated at 100.0% (429 of 429 strings)

Translation: Calendula/Strings
Translate-URL: https://weblate.dev.jeanlucmakiola.de/projects/calendula/strings/es/
2026-07-14 02:41:02 +00:00
Thomas Tref
4feccf2008 Translated using Weblate (French)
All checks were successful
Translations / check (pull_request) Successful in 5s
CI / ci (pull_request) Successful in 4m14s
Currently translated at 30.5% (131 of 429 strings)

Translation: Calendula/Strings
Translate-URL: https://weblate.dev.jeanlucmakiola.de/projects/calendula/strings/fr/
2026-07-13 23:05:35 +00:00
38a35be0f0 fix(reminders): show the day for reminders on another day (#46)
All checks were successful
CI / ci (pull_request) Successful in 5m31s
Translations / check (pull_request) Successful in 5s
A reminder fired ahead of an event on a different day showed only the
event's time (e.g. "09:30 – 10:00"), making it look like it was
happening today. reminderTimeText now prefixes timed events with a
relative day: "Tomorrow"/"Yesterday", the short weekday for another day
this week, or the exact date for anything further out.

The this-week boundary honours the user's "week starts on" setting: the
resolved first day of week is threaded through from ReminderNotifier, so
e.g. a Sunday reads as next week under a Sunday-start locale. All-day
events keep their explicit date (never ambiguous), and cross-midnight
timed events keep both explicit dates.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 17:25:51 +02:00
95c5fe76e6 Merge pull request 'Release 2.14.1' (!72) from release/v2.14.1 into main
All checks were successful
Release — F-Droid repo + Gitea release / detect (push) Successful in 6s
Release — F-Droid repo + Gitea release / release (push) Successful in 8m5s
Reviewed-on: #72
2026-07-13 13:48:22 +00:00
6aacdd9111 feat(edit): offer default reminder on .ics import instead of auto-applying (#49)
All checks were successful
Translations / check (pull_request) Successful in 5s
CI / ci (pull_request) Successful in 5m21s
Split the two prefill paths that share openImported(): an ACTION_INSERT
intent still auto-applies the settings default (it carries no reminder
semantics), but a .ics file — which owns its reminders — no longer silently
decides. It keeps the file's reminders and raises a one-time prompt
("This event was imported with N reminder(s) — apply your default?") so the
user chooses. The prompt is skipped when there's no real choice: no default
configured, or the file already carries exactly it.

openImported() now takes an ImportSource; CalendarHost tags the overlay
Insert vs File. Accepting swaps in the default and reveals the section;
declining (or dismissing) keeps the file's own reminders.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 11:57:38 +02:00
Kachelkaiser
6736bae5ee Translated using Weblate (German)
All checks were successful
Translations / check (pull_request) Successful in 5s
CI / ci (pull_request) Successful in 10m17s
Currently translated at 100.0% (424 of 424 strings)

Translation: Calendula/Strings
Translate-URL: https://weblate.dev.jeanlucmakiola.de/projects/calendula/strings/de/
2026-07-10 23:01:07 +00:00
Sam Gamgee
21d1726d7a Translated using Weblate (Italian)
All checks were successful
Translations / check (pull_request) Successful in 6s
CI / ci (pull_request) Successful in 10m38s
Currently translated at 100.0% (410 of 410 strings)

Translation: Calendula/Strings
Translate-URL: https://weblate.dev.jeanlucmakiola.de/projects/calendula/strings/it/
2026-07-07 14:42:36 +00:00
a82df3f6d0 Merge branch 'main' into release/v2.14.0
All checks were successful
Translations / check (pull_request) Successful in 4s
CI / ci (pull_request) Successful in 5m17s
2026-07-07 14:37:07 +00:00
ef77eab627 Merge release/v2.14.0 into chore/floret-implementation
Brings the 2.14.0 feature set (Month week numbers, .ics restore +
per-calendar export picker, Day view on date-header tap, wrapped long
titles, Simplified Chinese) onto the floret-kit migration without
resurrecting the inline component copies the kit now owns.

Conflict resolutions (both were import-block collisions where the
migration repointed to de.jeanlucmakiola.floret.* while release kept the
old app-local imports):

- CalendarsScreen.kt: keep the kit imports (CollapsingScaffold,
  DialogAmountField/DialogUnitDropdown, collapseExit/expandEnter,
  predictiveBack) and repoint FullScreenPicker + positionOf to the kit
  (both moved out of ui.common). Keep the genuinely app-local
  LeadingAvatar/SourceLogo/curatedSourcePackage; drop the renamed
  calendarCollapseExit/calendarExpandEnter (0 uses).
- ImportScreen.kt: keep the app-local CalendarPickerGroups, use the kit's
  predictiveBack, drop the now-unused OptionCard import.

Semantic fixup: CalendarPickerGroups.kt (new in 2.14.0) relied on
same-package resolution of GroupedRow/Position, which the migration moved
to the kit — added the explicit floret.components imports.

Verified: :app:compileDebugKotlin, ./gradlew test (app + kit), and
scripts/check_translations.py all green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 09:45:52 +02:00
955d47ef12 chore: draw shared code from floret-kit (fresh re-migration onto current main)
Supersedes the stale chore/floret-kit-core-time branch: re-applies the
floret-kit migration on top of current main (122 commits ahead of the old
branch's base), pinning the kit at the multi-value-reminders + pinned-picker HEAD.

- Submodule + composite build (includeBuild), 6 module deps, CI submodules:
  recursive, reproducible-release scan extended to the kit, F-Droid recipe.
- Deletes the inline copies now owned by the kit (GroupedList, Picker scaffolds,
  InlineTextField, OptionCard, DialogControls, CrashReporter + dialog/submit,
  OnboardingScaffold, AppLanguage, TimeBridge, ReorderableColumn, DebugRibbon)
  and redraws them from components/identity/core-crash/core-locale/core-time.
- Reminder overrides drawn from core-reminders (multi-value ReminderOverride +
  codec); Calendula keeps its app-specific bits (all-day resolution, labels,
  presets, the multi-select ReminderDefaultPicker, its own CrashReportActivity).
- Theme draws FloretExpressiveTheme while keeping the user-typography param.

Build pending (deferred): run ./gradlew :app:compileDebugKotlin with ANDROID_HOME
(or floret-kit/local.properties) set.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 09:28:56 +02:00
8536774522 feat: surface Simplified Chinese; complete 2.14.0 changelog
All checks were successful
Translations / check (pull_request) Successful in 28s
CI / ci (pull_request) Successful in 5m40s
Add zh-CN to locales_config.xml so the community Simplified Chinese
translation (values-zh-rCN, already committed via Weblate, ~27%) is
selectable in the in-app language picker and Android's per-app-language
settings. Untranslated strings fall back to English.

Fill in the 2.14.0 changelog, which only documented #37: add the three
feature PRs that also landed on this branch — .ics restore + per-calendar
export (#32), Month week numbers (#25), edit-screen title wrapping (#33) —
plus a note for the new Chinese translation, the missing [#N] link refs,
and the re-synced fastlane en-US changelog.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 09:23:57 +02:00
Ulisse Perusin
5771b603f2 Translated using Weblate (Italian)
All checks were successful
Translations / check (pull_request) Successful in 5s
CI / ci (pull_request) Successful in 10m29s
Currently translated at 99.0% (406 of 410 strings)

Translation: Calendula/Strings
Translate-URL: https://weblate.dev.jeanlucmakiola.de/projects/calendula/strings/it/
2026-07-06 20:20:20 +00:00
Kachelkaiser
5725989aff Translated using Weblate (German)
Currently translated at 100.0% (410 of 410 strings)

Translation: Calendula/Strings
Translate-URL: https://weblate.dev.jeanlucmakiola.de/projects/calendula/strings/de/
2026-07-06 20:20:19 +00:00
a514b8b506 feat: show calendar-week numbers in Month view (#25)
Add an opt-in left gutter to the Month grid showing the ISO calendar-week
number, gated by a new "Week numbers" display setting (default off). The
number is computed on each row's first day — the same basis as the Week
view's badge — so the two views agree, and rendered as a low-emphasis
onSurfaceVariant label so it recedes across all six rows rather than
competing with the event bars. The weekday header reserves a matching
gutter so the day columns stay aligned.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 22:02:44 +02:00
2ce79942c4 fix: broaden restore availability and stabilize the export picker
- Restore is import, not export: offer it whenever any writable, non-
  managed calendar exists (local or synced), not only when there is a
  local calendar to back up. Previously the row lived inside the
  export-gated block and vanished for users with only a writable synced
  calendar, despite import supporting that target.
- Export-picker selection now uses rememberSaveable and is no longer
  keyed on the observer-driven calendars list, so a background provider
  re-emit (sync/recolor) can't silently reset the user's de-selections,
  and the choice survives rotation.
- Shared calendar picker: restore the displayName fallback for a synced
  calendar whose account name and type are both blank (was grouping them
  under an empty header).
- Drop imports left dead by the CalendarPickerGroups extraction.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 20:34:35 +02:00
9f7427e72f feat: pin import action to the top bar, fold count into title
Move the multi-event import's confirm button into the app-bar actions so
it's reachable without scrolling past a long calendar list, and put the
count in the title ('Importing 5 events') instead of a separate 'N events
in this file' line. Hoists the selected target calendar to the screen so
the top-bar action can read it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 19:39:54 +02:00
a98dd654a6 polish: explain skipped duplicates on the import-complete screen
The import de-dups by UID against the target calendar (idempotent restore),
so re-importing events already present shows a low 'Added' count. Add a note
under the title when any were skipped so the outcome doesn't read as broken.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 19:07:56 +02:00
a138e179dd feat: per-calendar export selector
Tapping Export with more than one exportable calendar now opens a picker
to choose which local calendars to include (all selected by default); a
single calendar exports straight through as before. Threads an optional
calendarIds filter through exportEvents/exportableEvents (null = all
eligible), so the auto-backup path is unaffected. The backup section is
now gated on there being at least one exportable (non-managed) calendar.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 18:46:24 +02:00
8fb4767888 feat: spice up the import-complete screen
Replace the plain centered text list with an M3 Expressive success state:
a tonal check badge that springs in, the headline, and big-number tonal
stat tiles for added / duplicate-skipped counts, with a full-width Done
button. Stat tiles carry the full-sentence plurals as accessibility
labels so TalkBack still reads 'Imported N events'.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 18:32:20 +02:00
bff683a403 feat: restore events from .ics file in backup section (#32)
Add a 'Restore from .ics file' row to the Calendars backup section, next
to Export. It opens a SAF document picker and routes the picked Uri into
the existing import flow (parse, dedup by UID, target-calendar picker,
summary) via CalendarHost's importUri — the same path an externally
opened .ics already takes, so no new import machinery is needed.

Closes #32.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 17:55:35 +02:00
Ulisse Perusin
721ca0d7e0 Translated using Weblate (Italian)
All checks were successful
Translations / check (pull_request) Successful in 5s
CI / ci (pull_request) Successful in 9m40s
Currently translated at 97.3% (399 of 410 strings)

Translation: Calendula/Strings
Translate-URL: https://weblate.dev.jeanlucmakiola.de/projects/calendula/strings/it/
2026-07-05 08:52:25 +00:00
Ulisse Perusin
2556eba84b Translated using Weblate (Italian)
All checks were successful
Translations / check (pull_request) Successful in 4s
CI / ci (pull_request) Successful in 4m2s
Currently translated at 94.1% (386 of 410 strings)

Translation: Calendula/Strings
Translate-URL: https://weblate.dev.jeanlucmakiola.de/projects/calendula/strings/it/
2026-07-05 07:49:35 +00:00
zh-cn
fc1ff97d22 Translated using Weblate (Chinese (Simplified Han script))
All checks were successful
Translations / check (pull_request) Successful in 27s
CI / ci (pull_request) Successful in 4m3s
Currently translated at 29.0% (119 of 410 strings)

Translation: Calendula/Strings
Translate-URL: https://weblate.dev.jeanlucmakiola.de/projects/calendula/strings/zh_Hans/
2026-07-05 04:20:20 +00:00