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.
14 KiB
Changelog
All notable changes to floret-kit are documented here. The format follows Keep a Changelog.
floret-kit is consumed from source — each app embeds it as a git submodule
and builds it through a Gradle composite build, pinning a specific commit. There
are no binary releases; "version" tracks the shared version in the root build
(currently 0.3.0). Entries are grouped by module.
[Unreleased]
Added
-
components—GroupedSectionHeader, the label above a run ofGroupedRows: small,primary, inset toGroupedListInsetso it shares the cards' left margin. Converged from Calendula's settings hub, where the recipe was written as a privateSectionHeader, and now Agendula's, which had grown a flat run of a dozen rows with nothing separating how the app behaves from what it is. -
components—GroupedRowtakes acontentColor, the pair to the existingcontainer: a row given a tint now names theon*role that goes with it instead of leaving its text ononSurface, which only happens to read on a container the theme did not pick. Wanted by a status row that colours itself by outcome (Agendula's export receipt:secondaryContaineron success,errorContaineron failure). -
components—ScaledViewPreviewandViewPreviewFrame, the preview picker's two halves: the first measures real screen composables at a phone viewport and scales the finished layout down (swallowing touches and collapsing semantics, so a preview reads as a picture rather than a control), the second is the tonal plate it sits on, cross-fading between options and takingGroupedListInsetand the grouped rows' outer radius so preview and options read as one stack. Converged from Calendula's view/month-style choosers, where the pattern was invented, and now Agendula's onboarding step for the quick-add bar; the viewport height the app scales from is a parameter rather than a calendar-shaped constant.
[0.3.0] - 2026-08-10
Added
-
components—OnboardingScaffold, the shared shell for a first-run flow: hero, body and a call-to-action stack, so the family's onboarding screens read as one family while keeping their own mark. A multi-step flow passesprogress, whichOnboardingProgressdraws as one capsule per step with the caller's own localised label, andnavigationIconfor a step that can be stepped back from — the navigation 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.topSpacinggives a step whose hero is a large picture rather than a mark room to breathe;contentPaddingis the scrolling column's horizontal inset, which a step built from grouped rows passes 0 for since those carry their own; andscrollingActionsmoves the actions off the bottom bar onto the end of the content, for a step taller than the screen where a pinned bar eats the room the content needs and hides that there is more below.BenefitRowis the icon/title/body line those screens list their points with. -
components—SnackChip, the family's compact confirmation: a content-sized pill sliding up from the bottom with an optional trailing action, for the case where the surface behind the confirmation is the confirmation (a list that just lost a row, a calendar that just moved an event) and a full-width snackbar would cover it. Placement and timing stay with the caller — the bottom inset to clear differs per screen, and what an expiring undo window commits is the app's business. Converged from Agendula's swipe-delete undo chip and Calendula's drag-to-reschedule confirmation; the shared version keeps the polite live region so the chip is announced, takes amaxWidthso a message can't run into a bottom-end FAB, and takescolor/contentColorso an app can put it on the FAB's own fill. A message replaced in place crossfades and the pill resizes with it, so an answered confirmation ("Moved" → "Move undone") reads as one chip changing rather than two chips swapping. -
components—GroupedRowtakes anAnnotatedStringtitle and summary as well as plainStrings, so a row can carry its own emphasis: marking the run a search matched, without every caller having to rebuild the row. TheStringoverload delegates to it, so existing callers are unchanged. -
components—GroupedSurface/GroupedRowtake anonLongClick(with the platform long-press haptic), for a list that enters a selection mode from a row. -
components—GroupedListInset, the 16dp a grouped list keeps from the screen edge, exported. It was a private literal, so everything sitting outside the cards but belonging to them — section headers, hints, picker descriptions — repeated the number and had drifted to 24dp in places, leaving pages with two ragged left margins. -
components—OptionPickertakes a per-option summary, put under the label, for a row that names something it doesn't spell out (a range's dates, a reminder's firing time, what "follow the system" resolves to today). Every option then shows its effect at once and stays comparable, without the cost of a live preview. The KDoc 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. -
components—SelectedCheck, the trailing check that marks a chosen row in a full-screen picker, made public. It was private toOptionPicker, so every consumer hand-rolled a copy — Calendula alone carried five, all drawing a different check than the kit's. -
components—AboutCardtakes aPositionand renders throughGroupedSurface, so it can be the top of a group with rows continuing underneath rather than only standing alone. Defaults toPosition.Alone.
Fixed
-
core-time— all-day values rendered a day early west of UTC. All-day items are date-only in iCalendar and stored as UTC midnight of their calendar date, butformatDate/formatDateTime/formatDateTimeCompactresolved every instant againstZoneId.systemDefault().formatDatenow takes anallDayflag (defaultfalse, so existing call sites are unaffected) and resolves those against UTC; callers must pass it at the call site. -
components— full-screen picker status/navigation-bar icons matched the XML theme rather than the running one. A ComposeDialogowns its own window, seeded from the XML theme, so a picker drawing under the bars showed dark icons on a dark surface.FloretExpressiveThemenow exposes its resolved light/dark decision asLocalFloretDarkThemeand the dialog window's bar appearance follows it, tracking the in-app choice even when it diverges from the system night qualifier.
[0.2.1] - 2026-07-17
Added
components—CollapsingScaffoldtakes ascrollableflag (defaulttrue, so every existing caller is unchanged). Set it tofalsewhen the content brings its own scrolling container: the scaffold's ownverticalScrollcomposes each row up front and can't host aLazyColumn(nesting a same-axis scrollable throws), which ruled out any surface with more options than fit an eager pass. Opting out hands the content a plain column and leaves the insets, IME padding and top-bar collapse as they were.FullScreenPickerforwards the flag. Consumed by Calendula's ~600-entry time-zone picker from 2.16.0.
[0.2.0] - 2026-07-17
Added
components—CustomAmountEditor, the amount + unit editor behind an "Custom…" picker option (Calendula's custom snooze duration). Consumed by Calendula since 2.15.0.core-locale— the locale-aware formatting half of the module, so apps stop hand-rolling"$day. $month $year"templates that bake one language's field order (and its separators, and its ordinal dot) into every language they ship:localizedDateFormatter(locale, skeleton)— aDateTimeFormatterfor a field skeleton ("LLLLy", "dMMMy", "EEEdMMM") laid out in the locale's own order, via Android's best-pattern matching. Drop a field from the skeleton to drop it from the output — the locale still supplies the arrangement.currentLocale()— the display locale, observed fromLocalConfiguration. SinceAppLanguage.apply()changes the language inside a running process, a locale captured once goes stale; this keys recomposition to the change.
Changed
core-localenow carries Compose (runtime + ui only) forcurrentLocale(). The module previously declared itself Compose-free; applying a language mid-process is its own API, so the hook that makes the UI notice belongs beside it rather than in a Compose module depending back on this one.core-crashis the precedent for acore-*module carrying Compose.
Known gaps
core-time'sInstant.formatDate()family now overlapscore-locale. Its privatedateNoYearFormatterstrips the year by regex-ing the pattern string, whichlocalizedDateFormatter(locale, "dMMM")does properly; and its formatters resolveLocale.getDefault()once at class-init ("locale changes within a running process are rare enough to ignore"), whichAppLanguage.apply()makes untrue. Left alone for now — it is unused by Calendula but Agendula-visible. Reconcile when Agendula next re-pins.
[0.1.0] - 2026-07-07
Added
-
core-locale(Android library) — per-app language plumbing (AppLanguage): read the shipped languages from the app'sres/xml/locales_config.xml, get/set the applied language viaAppCompatDelegate, render each language's autonym. The app passes its ownlocales_configresource id; the system-default choice isnull. (Shipped in 0.1.0 but omitted from this changelog at the time; recorded here retroactively.) -
core-time(JVM library) — pure-Kotlin date/time helpers, no Android and no third-party dependencies:DayWindow.today()— local-day boundaries for smart-list logic.Instant.formatDate()/formatTime()/formatDateTime()— locale- and zone-aware display formatting overkotlin.time.Instant.TimeBridge—Longepoch-millis ↔Instant, for content-provider I/O.LocalDate.isoWeekNumber()— ISO-8601 week-of-week-based-year, the calendar-week number for week/month grids (one scheme, shared).
-
core-crash(Android library) — privacy-respecting, on-device crash capture and reporting. Writes an allowlist-only report (app/Android/device version, locale, time, stack trace — nothing else) to private storage and chains to the platform handler; uploads nothing. Includes startup crash-loop detection, a report dialog that shows the full text before it leaves the device, and an issue-tracker hand-off. Per-app specifics (display name, issue URLs) come from aCrashConfigsupplied atinstall(); each app keeps its own thin themedCrashReportActivity. -
identity(Android library) — the family's Material 3 Expressive theme factoryFloretExpressiveTheme(lightScheme, darkScheme, …)(dynamic colour on API 31+, system light/dark fallback, the standard motion scheme) plus the shared motion helpers —rememberNavSlideSpec(), the content transitions,Modifier.predictiveBack()andModifier.animateContentSizeMotion()(eases a block's own height changes, e.g. a text field wrapping to a new line), each reduced-motion-aware. Each app passes its own seed-derived colour schemes and typography — the mechanics are shared, the look is not. -
components(Android library) — the shared Compose component vocabulary:GroupedSurface/GroupedRow+Position/positionOf— the canonical press-morphing tonal grouped-row primitive (the Android-15 settings pattern).InlineTextField— the borderless tonal text input (capitalization is a parameter).OptionCard— the sanctioned selection-dialog pick.CollapsingScaffold— the collapsing settings/sub-screen scaffold; setlargeTopBar = falsefor a pinned single-line bar (the picker variant).FullScreenPicker/OptionPicker— the full-screen single-select picker, now over a pinned single-line bar (a picker is a short list, so no tall collapsing header to scroll past).FullScreenPickertakes an optionalactionsslot (app-bar trailing items) for pickers that carry a commit or extra action — a custom-value Add/OK, a Reset.ReorderableColumn— dependency-free drag-to-reorder for the short, fixed grouped-card Settings lists; pairs withGroupedRow(gapBelow = false), which hands the column uniform control of row spacing.DebugRibbon— a stark, un-themed "DEBUG" corner ribbon; gate onBuildConfig.DEBUGat the call site.InlineTextFieldgainedenabled(a disabled field dims to a locked value).pastelize()— softens a raw provider colour to a theme-fitting pastel.
App-agnostic by design: apps compose their own screens and keep their own identity chips/icons. String resources here (e.g.
back) are fallbacks an app can override. -
core-reminders(JVM library) — the app-agnostic reminder-override model:ReminderOverride(Inherit/None/Minutes), whereMinutescarries a list of lead-times so a target can hold several reminders at once (an app that offers a single reminder just uses a one-element list). Map helpers (reminderOverrideFor,reminderLeadsFor,applyReminderOverride,reminderOverrideForMinutes,normalizeReminders) plusReminderOverrideCodec(three configurable separators; single legacy values round-trip byte-identically) and the lead-time unit model (ReminderUnit,decomposeReminderMinutes). The labels/presets and the alarm scheduler stay per-app.
Conventions
- No
foojaytoolchain resolver anywhere — it can fetch a JDK at build time, which reproducible / official F-Droid builds reject. Modules setjvmTargetdirectly. Seedocs/ARCHITECTURE.md. - AGP 9.x provides built-in Kotlin compilation, so Android modules apply only the
Android library + Compose plugins (no separate
kotlin.android).