ColorScheme.toPureBlack() sets background, surface, surfaceDim and
surfaceContainerLowest to #000 and dims the other containers by scaling
their sRGB channels, so their hue tint carries over and grid tiles drawn
as containers over surface keep visible gaps. FloretExpressiveTheme takes
pureBlack to apply it to the dark scheme, dynamic or app-supplied.
core-prefs gains ThemeMode.BLACK (dark, pure black) and
ThemeMode.isPureBlack, stored under the same theme_mode key.
Brings the branches the sibling apps actually ship from back onto main, so floret-kit stops forking per project.
Merged in:
- `vouch/settings` (what vouch pins), which already contains `feat/clockula` (what clockula pins) and `chore/deps-2026-10`: core-prefs, core-di, CollapsingScaffold FAB/no-back slots, the glance module, LocaleManager on API 33+, and the dependency bump
- `feat/glance-module`: the three glance commits clockula's branch didn't have yet (progress ring/tile/check button, expressive shapes + header pill, WidgetDoneState)
Both merged without conflicts. Version ends up at 0.7.0 (untagged).
Heads-up for consumers: the deps bump moves the toolchain to Gradle 9.8.0 / AGP 9.4.1 / Kotlin 2.4.20. Since floret-kit is a composite build, a consumer must be on Gradle >= 9.6 to pin anything from here on. Calendula builds, tests and lints against this branch with just that toolchain bump, no source changes.
Co-authored-by: Jean-Luc Makiola <business@jeanlucmakiola.de>
Reviewed-on: https://codeberg.org/jlmakiola/floret-kit/pulls/11
The issue the crash flow prefills took its title and body from string
resources, so both followed the device language: a crash on a French
phone opened the tracker with a French issue, which is unreadable to
whoever has to triage it. The report itself was always English — only
the wrapper around it wasn't.
That text is posted rather than shown, so it is no longer a resource at
all: title and body are fixed English in CrashReportSubmit, and
CrashConfig drops issueTitle (apps pass one fewer argument and can
delete their own crash_report_issue_title / crash_report_body_* strings).
What the reporter sees in the app — the dialog, the toasts, the
clipboard label — stays translated, which is the line the kdoc and the
resource header now draw.
A connected list for a procedure the user has to carry out somewhere
else. Collapsible, so it can be shown again as a reference once it has
already had a screen of its own.
The header was invented here rather than converged from the app that
already had one. Calendula's SectionHeader is labelLarge at top 16 /
bottom 4, and separates one group from the next with the caller's own 8dp
spacer — a hub's first header follows the About card, not another group,
so the gap cannot live on the header. Matched exactly, so the two apps'
settings hubs are the same page.
A settings page with a dozen categories in one flat run has no way to say
which of them are about how the app behaves and which are about the app
itself. GroupedSectionHeader is that label: it starts on GroupedListInset,
the edge the cards it names start on, and owns the gap to the group above,
so a page writes header-then-rows and nothing else.
primary rather than onSurfaceVariant — after the Android-15 settings
pattern Position already follows, and because a header that matches the
summaries underneath it stops separating anything.
ScaledViewPreview measures real screen composables at a phone viewport and
scales the finished layout down; ViewPreviewFrame is the tonal plate it sits
on, cross-fading between options at the grouped rows' width and radius so
preview and options read as one stack.
Converged from Calendula's view and month-style choosers, where the pattern
was invented, and now Agendula's quick-add step. The viewport height is a
parameter here rather than a calendar-shaped constant.
GroupedRow's container had no `on*` pair, so a row given a tint kept its
text on onSurface — readable by luck rather than by the theme. Wanted by a
status row that colours itself by outcome.
KeyboardType.Password only tells the IME to drop suggestions; without a
visual transformation a borderless password field renders the secret in
the clear.
The per-app-language store re-parses the applied locale through ICU and
fills in the implied script, so a zh-CN choice reads back as zh-Hans-CN and
no longer matches the tag the picker offers — the selected row lost its
mark after a restart. currentTag(supported) maps it back.
Two new components since 0.2.1 — the onboarding shell (OnboardingScaffold,
OnboardingProgress, BenefitRow) that Calendula's first-run wizard is built
on, and SnackChip, the compact confirmation converged from Agendula's and
Calendula's undo chips — plus grouped-list and picker richness (long-press
rows, styled row text, exported GroupedListInset, per-option summaries,
public SelectedCheck, AboutCard in a group) and two fixes: all-day values
rendering a day early west of UTC, and full-screen picker bar icons
following the XML theme rather than the running one.
The changelog had gone unwritten since 0.2.1; this backfills every entry.
A step whose content is taller than the screen loses room to a pinned bar,
and the bar hides that there is more below it. scrollingActions moves the
call-to-action stack to the end of the scrolling column instead.
A step that can be stepped back from passes navigationIcon. The slot's
width is reserved whether or not it is filled, and mirrored on the other
side, so the progress stays centred as steps come and go.
contentPadding lets a step built from grouped rows drop the column's own
inset, since those rows carry their own.
A multi-step flow passes a progress slot, pinned above the scrolling
content, and OnboardingProgress draws it as one capsule per step with the
caller's own localised label. topSpacing lets a step whose hero is a large
picture rather than a mark keep the content on one screen.
GroupedRow took plain Strings, so a caller that needs emphasis inside the
title or summary — a search hit marking what it matched — had no way in short
of rebuilding the row. Adds an AnnotatedString overload and delegates the
String one to it.
GroupedSurface/GroupedRow gain onLongClick (with the platform long-press
haptic), for lists that enter a selection mode from a row — Calendula's
search results (#80).
A slide-in is clipped to the animating bounds, so at pill size the chip
looked painted on from one edge. It now springs from its anchored corner on
the expressive fast specs, which are also quicker than the default
visibility spring.
Agendula's swipe-delete chip and Calendula's reschedule confirmation were
the same pill twice. Placement and timing stay with the caller; the shared
one keeps the polite live region and takes a maxWidth so the message can't
run into a bottom-end FAB.
AboutCard drew its own Surface, so it could only stand alone. Taking a
Position and rendering through GroupedSurface lets it be the top of a group
with rows continuing underneath — for apps whose call to action reads better
as a row of its own than as a tonal button inside the card.
Defaults to Position.Alone, so existing callers are unchanged.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The 16dp a grouped list keeps from the screen edge was a private literal in
GroupedRow, so everything that sits outside the cards but belongs to them —
section headers, hints, picker descriptions — had to repeat the number and
had quietly drifted to 24dp in places, leaving pages with two ragged left
margins. Exporting it gives those callers the one value to take.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A picker row often names something it doesn't spell out — a range's dates, a
reminder's firing time, what "follow the system" resolves to today. The
summary slot puts that under the label, so every option shows its effect at
once and stays comparable, without the cost of a live preview.
The KDoc now also states the three rungs of picker richness (plain rows /
per-option summary / live preview) and the rule that a preview picker must
not close on selection, since closing hides the thing it exists to show.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A Compose Dialog owns its own Window whose status/navigation-bar icon
appearance is seeded from the XML theme, not from the host activity's
runtime edge-to-edge state. With decorFitsSystemWindows = false the
FullScreenPicker draws under the bars, exposing icons that don't match
the active theme — dark icons on a dark picker (near-invisible), and the
mirror-image nav-bar bug in light theme.
Expose the resolved light/dark decision from FloretExpressiveTheme as
LocalFloretDarkTheme and drive the dialog window's
isAppearanceLight{Status,Navigation}Bars from it, so the appearance
tracks the in-app light/dark choice even when it diverges from the
system night resource qualifier.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
All-day items are date-only in iCalendar and are stored as UTC midnight of
their calendar date — the convention the tasks and calendar providers read
back (OpenTasks resolves them through DateTime.toAllDay(), which discards the
time and resolves the remaining date against UTC).
formatDate/formatDateTime/formatDateTimeCompact resolved every instant against
ZoneId.systemDefault(), so an all-day value rendered as the *previous* day for
anyone west of UTC: a task stored as 2026-07-20T00:00Z showed as 19 Jul in New
York. East of UTC it happened to land on the right day, which is why this went
unnoticed.
formatDate now takes an allDay flag (defaulting to false, so existing call
sites are unaffected) and all-day values resolve against UTC.
Callers must pass allDay at the call site — agendula and calendula both have
sites that need updating.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The trailing check that marks a chosen row in a full-screen picker was
private to OptionPicker, so every consumer hand-rolled its own copy.
Calendula alone carried five, and they had drifted: the kit drew
Icons.Rounded.Check, all five app copies drew Icons.Default.Check.
Make it public with KDoc so pickers share one selection affordance
instead of re-deriving it. Rounded wins, as the kit's existing choice.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds components' `scrollable` opt-out on CollapsingScaffold (and its
FullScreenPicker passthrough), so a picker with more options than an
eagerly composed column can carry can drive its own LazyColumn.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>