Merge remote-tracking branch 'origin/main'
# Conflicts: # README.md
This commit is contained in:
54
CHANGELOG.md
Normal file
54
CHANGELOG.md
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
All notable changes to floret-kit are documented here. The format follows
|
||||||
|
[Keep a Changelog](https://keepachangelog.com/).
|
||||||
|
|
||||||
|
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.1.0`). Entries are grouped by module.
|
||||||
|
|
||||||
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- **`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 over `kotlin.time.Instant`.
|
||||||
|
- `TimeBridge` — `Long` epoch-millis ↔ `Instant`, for content-provider I/O.
|
||||||
|
- **`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 a `CrashConfig` supplied at `install()`; each app keeps its
|
||||||
|
own thin themed `CrashReportActivity`.
|
||||||
|
- **`identity`** (Android library) — the family's Material 3 Expressive theme
|
||||||
|
factory `FloretExpressiveTheme(lightScheme, darkScheme, …)` (dynamic colour on
|
||||||
|
API 31+, system light/dark fallback, the standard motion scheme) plus
|
||||||
|
`rememberNavSlideSpec()`. 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.
|
||||||
|
- `FullScreenPicker` / `OptionPicker` — the full-screen single-select picker.
|
||||||
|
- `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.
|
||||||
|
|
||||||
|
### Conventions
|
||||||
|
|
||||||
|
- **No `foojay` toolchain resolver** anywhere — it can fetch a JDK at build time,
|
||||||
|
which reproducible / official F-Droid builds reject. Modules set `jvmTarget`
|
||||||
|
directly. See [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md).
|
||||||
|
- AGP 9.x provides built-in Kotlin compilation, so Android modules apply only the
|
||||||
|
Android library + Compose plugins (no separate `kotlin.android`).
|
||||||
77
CONTRIBUTING.md
Normal file
77
CONTRIBUTING.md
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
# Contributing to floret-kit
|
||||||
|
|
||||||
|
floret-kit is the shared Material 3 Expressive design system + plumbing for the
|
||||||
|
Floret app family. Before changing it, skim
|
||||||
|
[`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) (how it's built and consumed) and
|
||||||
|
[`docs/ROADMAP.md`](docs/ROADMAP.md) (what's in scope). This file is the
|
||||||
|
practical how.
|
||||||
|
|
||||||
|
## The rules
|
||||||
|
|
||||||
|
1. **Share mechanics, not look.** Extract the machinery; leave seed colours,
|
||||||
|
palettes, identity chips, and wording to the apps. If a piece can't be made
|
||||||
|
app-agnostic without contortion, it doesn't belong here yet.
|
||||||
|
2. **No app coupling.** A kit module never imports an app package, an app's `R`,
|
||||||
|
or a domain type. App-specific values arrive as **constructor/config
|
||||||
|
parameters** or **callbacks** (see `core-crash`'s `CrashConfig`,
|
||||||
|
`InlineTextField`'s `capitalization`).
|
||||||
|
3. **String resources are fallbacks.** Kit strings are English defaults an app
|
||||||
|
overrides via resource merge. Don't put app-specific or per-app-divergent
|
||||||
|
wording in the kit.
|
||||||
|
4. **Bake divergence into parameters, don't pick a silent winner.** Where two
|
||||||
|
apps differ, expose the difference. When a shared primitive adopts one app's
|
||||||
|
version as canonical, that's a deliberate, documented convergence.
|
||||||
|
5. **No `foojay` toolchain resolver** — not in any `*.gradle.kts`, not even as a
|
||||||
|
token in a comment. It breaks reproducible / official F-Droid builds. Set
|
||||||
|
`jvmTarget` directly. See [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) §5.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- JDK 17
|
||||||
|
- Android SDK (compileSdk 37) for the Android modules — set `ANDROID_HOME` or add
|
||||||
|
a gitignored `local.properties` with `sdk.dir`. Pure-JVM modules need neither.
|
||||||
|
|
||||||
|
## Build & test
|
||||||
|
|
||||||
|
```sh
|
||||||
|
./gradlew :core-time:test # JVM module (JUnit 5 + Truth)
|
||||||
|
./gradlew :core-crash:testDebugUnitTest
|
||||||
|
./gradlew :identity:assembleDebug
|
||||||
|
./gradlew build # everything
|
||||||
|
```
|
||||||
|
|
||||||
|
In practice the kit is exercised through a consuming app's composite build (e.g.
|
||||||
|
`agendula`'s `./gradlew assembleDebug`), which compiles the included modules from
|
||||||
|
source.
|
||||||
|
|
||||||
|
## Adding a module
|
||||||
|
|
||||||
|
1. Create `:<module>` and add `include(":<module>")` to
|
||||||
|
[`settings.gradle.kts`](settings.gradle.kts).
|
||||||
|
2. Add a `<module>/build.gradle.kts`:
|
||||||
|
- **JVM:** `plugins { alias(libs.plugins.kotlin.jvm) }`, a `kotlin { compilerOptions { jvmTarget = JVM_17 } }` block, JUnit 5 test deps.
|
||||||
|
- **Android:** `plugins { alias(libs.plugins.android.library); alias(libs.plugins.kotlin.compose) }` (no `kotlin.android` — AGP 9.x is built-in), `namespace = "de.jeanlucmakiola.floret.<area>"`, `compileSdk 37` / `minSdk 29`, `jvmTarget = JVM_17`.
|
||||||
|
3. Put sources under `src/main/kotlin/de/jeanlucmakiola/floret/<area>/`. The root
|
||||||
|
build's `subprojects {}` sets the shared `group`/`version`, so no per-module
|
||||||
|
coordinates are needed.
|
||||||
|
4. Add an entry to [`CHANGELOG.md`](CHANGELOG.md), the README module table, and
|
||||||
|
(if status-worthy) [`docs/ROADMAP.md`](docs/ROADMAP.md).
|
||||||
|
|
||||||
|
## Adopting the kit in an app
|
||||||
|
|
||||||
|
1. `git submodule add <kit-url> floret-kit`.
|
||||||
|
2. `includeBuild("floret-kit")` in the app's `settings.gradle.kts`.
|
||||||
|
3. `implementation("de.jeanlucmakiola.floret:<module>")` in the app module.
|
||||||
|
4. Ensure CI checks out submodules (`submodules: recursive`) and the included
|
||||||
|
build can find the SDK (`ANDROID_HOME`). For official F-Droid reproducible
|
||||||
|
builds, add `submodules: true` to the fdroiddata recipe and have the repro
|
||||||
|
guard scan the submodule.
|
||||||
|
5. The app pins a kit commit via the submodule; bump it deliberately to adopt kit
|
||||||
|
changes.
|
||||||
|
|
||||||
|
## Validate before pushing
|
||||||
|
|
||||||
|
- `python3 -c "import yaml; yaml.safe_load(open('<workflow>.yml'))"` after any
|
||||||
|
workflow edit — indentation bites.
|
||||||
|
- A consuming app's `lintDebug` + `testDebugUnitTest` + `assembleDebug` should be
|
||||||
|
green after a kit change you mean to land.
|
||||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2026 Jean-Luc Makiola
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
34
README.md
34
README.md
@@ -25,20 +25,38 @@ implementation("de.jeanlucmakiola.floret:core-time")
|
|||||||
```
|
```
|
||||||
|
|
||||||
Gradle's dependency substitution maps `de.jeanlucmakiola.floret:<module>` to the
|
Gradle's dependency substitution maps `de.jeanlucmakiola.floret:<module>` to the
|
||||||
local source module — edit the kit and the app picks it up immediately.
|
local source module — edit the kit and the app picks it up immediately. Android
|
||||||
|
modules need an SDK location for the included build: set `ANDROID_HOME` (CI) or
|
||||||
|
add a gitignored `<app>/floret-kit/local.properties` with `sdk.dir` (locally).
|
||||||
|
See [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) for the full consumption model.
|
||||||
|
|
||||||
## Modules
|
## Modules
|
||||||
|
|
||||||
| Module | What it holds |
|
| Module | Type | What it holds |
|
||||||
|--------|---------------|
|
|--------|------|---------------|
|
||||||
| `core-time` | Pure-Kotlin date/time helpers: local-day windows (`DayWindow`) for smart-list logic and locale/zone-aware display formatting (`Instant.formatDate()` / `formatTime()` / `formatDateTime()`). No Android, no deps. |
|
| `core-time` | JVM | Date/time helpers: `DayWindow` local-day windows, locale/zone-aware `Instant` formatting, `TimeBridge` (millis ↔ `Instant`). No Android, no deps. |
|
||||||
| `core-reminders` | Pure-Kotlin reminder-lead plumbing: the lead-time unit model (`ReminderUnit`, `decomposeReminderMinutes`), the per-target override model (`ReminderOverride` + `reminderLeadFor` / `applyReminderOverride`) and the stored-format codec (`ReminderOverrideCodec`, separators configurable per app). No Android, no deps. Each app layers its own DataStore + string labels on top. |
|
| `core-reminders` | JVM | Reminder-lead plumbing: the lead-time unit model (`ReminderUnit`, `decomposeReminderMinutes`), the per-target override model (`ReminderOverride` + `reminderLeadFor` / `applyReminderOverride`) and the stored-format codec (`ReminderOverrideCodec`, separators configurable per app). Each app layers its own DataStore + string labels on top. |
|
||||||
| `core-locale` | Per-app language plumbing (`AppLanguage`): read the shipped languages from the app's `res/xml/locales_config.xml`, get/set the applied language via `AppCompatDelegate`, and render each language's autonym. The app passes its own `locales_config` resource id; appcompat only, no Compose. |
|
| `core-locale` | Android | Per-app language plumbing (`AppLanguage`): read the shipped languages from the app's `res/xml/locales_config.xml`, get/set the applied language via `AppCompatDelegate`, render each language's autonym. The app passes its own `locales_config`; appcompat only, no Compose. |
|
||||||
|
| `core-crash` | Android | Privacy-respecting on-device crash capture + report dialog + issue-tracker hand-off. Parameterized per app via `CrashConfig`. |
|
||||||
|
| `identity` | Android | The M3 Expressive theme factory `FloretExpressiveTheme(…)`, `rememberNavSlideSpec()`, the content transitions (`expandEnter`/`collapseExit`/`itemEnter`/…) and the `predictiveBack` peek. Each app supplies its own seed/palette. |
|
||||||
|
| `components` | Android | Shared Compose vocabulary + recipes: `GroupedSurface`/`GroupedRow`, `InlineTextField`, `OptionCard`, `CollapsingScaffold`, `OptionPicker`, `pastelize()`, `DialogControls`, `OnboardingScaffold`, `OptionalFormSection`, `AboutCard`, `LanguagePickerRow`. |
|
||||||
|
|
||||||
_More modules (identity/theme, components, screen recipes, provider/prefs/crash plumbing) land as they're extracted from the apps._
|
_The principle: the **mechanics** are shared, the **look** stays per-app. See
|
||||||
|
[`docs/ROADMAP.md`](docs/ROADMAP.md) for what's extracted, deferred, and
|
||||||
|
deliberately not shared._
|
||||||
|
|
||||||
|
## Docs
|
||||||
|
|
||||||
|
See [`docs/`](docs/) — start with [`docs/README.md`](docs/README.md)
|
||||||
|
([ARCHITECTURE](docs/ARCHITECTURE.md), [ROADMAP](docs/ROADMAP.md)) and
|
||||||
|
[`CONTRIBUTING.md`](CONTRIBUTING.md) to add or change a module.
|
||||||
|
[`CHANGELOG.md`](CHANGELOG.md) tracks what landed.
|
||||||
|
>>>>>>> origin/main
|
||||||
|
|
||||||
## Build
|
## Build
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
./gradlew :core-time:test
|
./gradlew :core-time:test # JVM module tests
|
||||||
|
./gradlew :core-crash:testDebugUnitTest
|
||||||
|
./gradlew build # everything (Android modules need an SDK; see above)
|
||||||
```
|
```
|
||||||
|
|||||||
147
docs/ARCHITECTURE.md
Normal file
147
docs/ARCHITECTURE.md
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
# floret-kit — architecture
|
||||||
|
|
||||||
|
How the kit is structured, built, and consumed **as it stands today**. For what
|
||||||
|
is and isn't extracted, see [`ROADMAP.md`](ROADMAP.md); for the practical how of
|
||||||
|
adding a module, see [`../CONTRIBUTING.md`](../CONTRIBUTING.md).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. The thesis in one sentence
|
||||||
|
|
||||||
|
floret-kit is a Gradle build of small, focused modules — plumbing and a Material
|
||||||
|
3 Expressive design layer — that the family's apps draw from instead of
|
||||||
|
re-deriving the same code. The whole design hangs off one rule:
|
||||||
|
|
||||||
|
> Share the **mechanics**, keep the **look** per-app.
|
||||||
|
|
||||||
|
Theme machinery, content-provider plumbing, and component primitives are shared;
|
||||||
|
seed colours, palettes, identity chips, domain types, and app-specific wording
|
||||||
|
stay in each app. The kit never knows about a specific app's domain.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. How an app consumes the kit
|
||||||
|
|
||||||
|
Each app embeds this repo as a **git submodule** and wires it in as a Gradle
|
||||||
|
**composite build**. The kit is therefore always built **from source** — there
|
||||||
|
are no published binaries — which keeps every consuming app reproducible (a
|
||||||
|
requirement for official F-Droid).
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
// <app>/settings.gradle.kts
|
||||||
|
includeBuild("floret-kit")
|
||||||
|
```
|
||||||
|
```kotlin
|
||||||
|
// <app>/app/build.gradle.kts
|
||||||
|
implementation("de.jeanlucmakiola.floret:core-time")
|
||||||
|
implementation("de.jeanlucmakiola.floret:components")
|
||||||
|
```
|
||||||
|
|
||||||
|
Every module sets `group = "de.jeanlucmakiola.floret"` (via the root build's
|
||||||
|
`subprojects {}`), so Gradle's dependency substitution maps
|
||||||
|
`de.jeanlucmakiola.floret:<module>` to the local `project(":<module>")` of the
|
||||||
|
included build. Editing the kit is picked up by the app's next build with no
|
||||||
|
publish step.
|
||||||
|
|
||||||
|
Each app **pins a specific kit commit** through its submodule gitlink. So a
|
||||||
|
breaking change in the kit can't reach an app until that app deliberately
|
||||||
|
re-pins — apps stay on independent cadences (important while Calendula ships and
|
||||||
|
Agendula moves fast).
|
||||||
|
|
||||||
|
### SDK location for the included build
|
||||||
|
|
||||||
|
An included Android build needs to locate the Android SDK independently of the
|
||||||
|
host app. Provide it via:
|
||||||
|
|
||||||
|
- **CI:** an `ANDROID_HOME` env var (the apps set it at the job level), or
|
||||||
|
- **locally:** a gitignored `<app>/floret-kit/local.properties` with `sdk.dir`.
|
||||||
|
|
||||||
|
Pure-JVM modules (`core-time`) need neither.
|
||||||
|
|
||||||
|
### Submodules in CI / F-Droid
|
||||||
|
|
||||||
|
Consuming apps must check out submodules (`submodules: recursive` in the
|
||||||
|
checkout step) so `floret-kit/` isn't empty. For an app published via **official
|
||||||
|
F-Droid reproducible builds** (Calendula), the fdroiddata recipe additionally
|
||||||
|
needs `submodules: true` on the build entry, and the app's reproducibility guard
|
||||||
|
should scan the submodule's Gradle scripts (see §5).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Module layering
|
||||||
|
|
||||||
|
| Layer | Modules | Notes |
|
||||||
|
|---|---|---|
|
||||||
|
| Plumbing | `core-time` (JVM) | Pure logic; no Android, no deps. |
|
||||||
|
| Plumbing | `core-crash` (Android) | Self-contained subsystem; parameterized per app. |
|
||||||
|
| Design — identity | `identity` (Android) | Theme factory + motion. App supplies seed/palette. |
|
||||||
|
| Design — components | `components` (Android) | Shared Compose primitives. App composes screens. |
|
||||||
|
|
||||||
|
Dependencies between modules are kept minimal: `core-time` depends on nothing in
|
||||||
|
the kit; `identity` and `components` depend only on Compose + (for components)
|
||||||
|
`material-icons-core`. There is no app-level or cross-domain coupling.
|
||||||
|
|
||||||
|
Each module's package is `de.jeanlucmakiola.floret.<area>` (e.g.
|
||||||
|
`de.jeanlucmakiola.floret.components`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Module types & the build
|
||||||
|
|
||||||
|
Two kinds of module:
|
||||||
|
|
||||||
|
- **JVM library** (`core-time`) — `org.jetbrains.kotlin.jvm`, `jvmTarget = 17`,
|
||||||
|
JUnit 5 + Truth tests. The right home for pure logic; cheapest to build and
|
||||||
|
test.
|
||||||
|
- **Android library** (`core-crash`, `identity`, `components`) —
|
||||||
|
`com.android.library` + the Compose plugin, `compileSdk 37` / `minSdk 29`.
|
||||||
|
|
||||||
|
> **AGP 9.x provides built-in Kotlin compilation.** Android modules therefore
|
||||||
|
> apply only `com.android.library` and `org.jetbrains.kotlin.plugin.compose` —
|
||||||
|
> **not** a separate `org.jetbrains.kotlin.android` plugin (applying it errors).
|
||||||
|
> They set `jvmTarget` directly in a `kotlin { compilerOptions { … } }` block.
|
||||||
|
|
||||||
|
Versions live in [`gradle/libs.versions.toml`](../gradle/libs.versions.toml),
|
||||||
|
kept in lockstep with the apps' catalogs.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Reproducibility rules
|
||||||
|
|
||||||
|
Because consuming apps build the kit from source — and at least one publishes via
|
||||||
|
official F-Droid reproducible builds — the kit follows the same invariants:
|
||||||
|
|
||||||
|
1. **No `foojay` toolchain resolver**, anywhere (not even as a token in a
|
||||||
|
comment). It can fetch a JDK over the network at build time, which the offline
|
||||||
|
F-Droid source scanner rejects. Modules set `jvmTarget` directly instead of
|
||||||
|
using a Java toolchain block that would need a resolver.
|
||||||
|
2. **No published artifacts.** Built from source, pinned by commit.
|
||||||
|
3. Android library modules emit no APK-level metadata of their own (no
|
||||||
|
`buildConfig`); the consuming app controls APK-level reproducibility
|
||||||
|
(`vcsInfo`/`dependenciesInfo`).
|
||||||
|
|
||||||
|
A consuming app's reproducibility guard should scan the submodule's `*.gradle.kts`
|
||||||
|
for the resolver too, since the included build is part of the from-source graph.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. The design-divergence contract
|
||||||
|
|
||||||
|
The design modules (`identity`, `components`) carry the family's look, so they
|
||||||
|
extract **mechanics** and leave **identity** to the app:
|
||||||
|
|
||||||
|
- `identity` exposes a theme **factory**; each app passes its own seed-derived
|
||||||
|
`lightScheme`/`darkScheme` and typography. Seed colours and hand-tuned
|
||||||
|
fallbacks are never shared constants.
|
||||||
|
- `components` are app-agnostic primitives. Where two apps differ, the
|
||||||
|
difference is an explicit **parameter** (e.g. `InlineTextField`'s
|
||||||
|
`capitalization`) or a **callback**, never a silently-picked winner. Apps keep
|
||||||
|
their own identity chips/icons and compose their own screens.
|
||||||
|
- **String resources** in the kit are English **fallbacks**; an app overrides
|
||||||
|
them (with its localized values) via resource merge. App-specific wording
|
||||||
|
stays in the app.
|
||||||
|
|
||||||
|
When a primitive is shared by adopting one app's version as canonical (e.g.
|
||||||
|
`components`' `GroupedSurface` follows Agendula's cleaner abstraction), that is a
|
||||||
|
deliberate, signed-off convergence — the other app's surfaces change when it
|
||||||
|
re-pins, reviewable at that point.
|
||||||
26
docs/README.md
Normal file
26
docs/README.md
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# floret-kit — documentation
|
||||||
|
|
||||||
|
floret-kit is the shared **Material 3 Expressive design system + plumbing** for
|
||||||
|
the Floret family of Android apps
|
||||||
|
([Calendula](https://gitea.jeanlucmakiola.de/makiolaj/calendula),
|
||||||
|
[Agendula](https://gitea.jeanlucmakiola.de/makiolaj/agendula), and future
|
||||||
|
siblings). It is consumed from source as a git submodule + Gradle composite
|
||||||
|
build. See the top-level [`../README.md`](../README.md) for the pitch.
|
||||||
|
|
||||||
|
## Index
|
||||||
|
|
||||||
|
| Doc | What it covers |
|
||||||
|
|---|---|
|
||||||
|
| [`ARCHITECTURE.md`](ARCHITECTURE.md) | How the kit is built and **consumed** — the module layering, JVM vs Android-library modules, the submodule + composite-build model, and the reproducibility rules. Start here. |
|
||||||
|
| [`ROADMAP.md`](ROADMAP.md) | **What's extracted, what's deferred, and what's deliberately not shared** — the module status and the divergence decisions from the cross-app survey. |
|
||||||
|
| [`../CONTRIBUTING.md`](../CONTRIBUTING.md) | The practical how — adding a module, the no-app-coupling rules, build/test, and how an app adopts the kit. |
|
||||||
|
| [`../CHANGELOG.md`](../CHANGELOG.md) | Keep a Changelog; what each module added. |
|
||||||
|
|
||||||
|
## The one principle
|
||||||
|
|
||||||
|
> Share the **mechanics**, keep the **look** per-app.
|
||||||
|
|
||||||
|
Theme machinery, plumbing, and component primitives are shared; seed colours,
|
||||||
|
palettes, identity chips, and app-specific wording stay in each app. This is what
|
||||||
|
lets the family read as one design language without homogenising apps that are
|
||||||
|
meant to have distinct personalities.
|
||||||
68
docs/ROADMAP.md
Normal file
68
docs/ROADMAP.md
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
# floret-kit — roadmap
|
||||||
|
|
||||||
|
What's extracted, what's deferred, and what's deliberately **not** shared. The
|
||||||
|
defer/skip calls come from a cross-app survey of both Calendula and Agendula —
|
||||||
|
the guiding rule is to share only what is genuinely common, and to keep
|
||||||
|
divergent or app-specific code where it lives.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Extracted (done)
|
||||||
|
|
||||||
|
| Module | Type | Consumers |
|
||||||
|
|---|---|---|
|
||||||
|
| `core-time` | JVM | Agendula (and Calendula's pending branch) |
|
||||||
|
| `core-crash` | Android | Agendula |
|
||||||
|
| `identity` | Android | Agendula |
|
||||||
|
| `components` | Android | Agendula |
|
||||||
|
|
||||||
|
See [`../CHANGELOG.md`](../CHANGELOG.md) for the contents of each.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Deferred / not yet worth it
|
||||||
|
|
||||||
|
- **`core-prefs`** (DataStore base: `ThemeMode`, `dynamicColor`, the typed
|
||||||
|
wrapper, `toEnum()`) and **`core-di`** (`@IoDispatcher` + provider) — genuinely
|
||||||
|
shared but **low value**: pure relocation, no new capability, the shared
|
||||||
|
surface is tiny once the store name and bindings stay app-local. Sensible to
|
||||||
|
defer until a third app makes the duplication hurt.
|
||||||
|
- **`ReminderFormatting`** (`ReminderUnit` + `reminderLeadTimeLabel` +
|
||||||
|
`decomposeReminder`) — the labels are `@Composable` over app-specific
|
||||||
|
`R.plurals`, and the presets differ per app. Extract later behind a
|
||||||
|
**label/preset callback API**, not before.
|
||||||
|
|
||||||
|
## Deferred — needs migration first
|
||||||
|
|
||||||
|
- **`core-provider`** (the ContentProvider seam: `ColumnReader`, failures,
|
||||||
|
observer→Flow) — the two apps' `ColumnReader`s diverged (Calendula index-based,
|
||||||
|
Agendula name-based + null-safe). True sharing means migrating Calendula to the
|
||||||
|
better name-based design first (~a few hundred LOC), so it waits until Calendula
|
||||||
|
is actively on the kit.
|
||||||
|
|
||||||
|
## Not shared (by design)
|
||||||
|
|
||||||
|
- **A generic Settings scaffold** — the two apps' settings *domains* are too
|
||||||
|
divergent for a config-driven `SettingsScaffold` to stay readable. The win is
|
||||||
|
captured instead at the **component** level (shared `GroupedRow`/section
|
||||||
|
primitives + `CollapsingScaffold` + `OptionPicker`); each app composes its own
|
||||||
|
settings from those.
|
||||||
|
- **`core-reminders`** (unified) — Agendula self-schedules alarms (pull),
|
||||||
|
Calendula is provider-event-driven (push). Opposite architectures over
|
||||||
|
incompatible data models; forcing a shared abstraction would be leaky. A tiny
|
||||||
|
`core-notification` (idempotent channel setup, `POST_NOTIFICATIONS` checks)
|
||||||
|
*might* land later; schedulers stay app-local.
|
||||||
|
- Domain-specific UI (date/time fields tied to a domain, calendar/timeline
|
||||||
|
widgets, recurrence text, domain pickers) and all string wording — these encode
|
||||||
|
each app's model and resist parameterization.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Consumers & cadence
|
||||||
|
|
||||||
|
- **Agendula** consumes the kit on `main` (active development).
|
||||||
|
- **Calendula** (ships via official F-Droid reproducible builds) has a pending
|
||||||
|
adoption branch. Bringing it onto the kit is where the canonical-`GroupedSurface`
|
||||||
|
convergence becomes visible, and where the F-Droid reproducibility checklist
|
||||||
|
(`submodules: true` in the recipe; the repro guard scanning the submodule)
|
||||||
|
applies — see [`ARCHITECTURE.md`](ARCHITECTURE.md) §2 and §5.
|
||||||
Reference in New Issue
Block a user