Compare commits

3 Commits

Author SHA1 Message Date
2124227a7f feat(components): add CustomAmountEditor, bump to 0.2.0
Extract the shared presets-plus-custom editor card — tonal surface with the
connected shape, an optional single-choice unit toggle, and an amount field
with a live preview + confirm — used by the reminder, agenda-range and snooze
pickers into one reusable, presentational component.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 18:01:42 +02:00
78c5fd1fd4 release: floret-kit 0.1.0
Finalize the 0.1.0 CHANGELOG entry (first tagged release). Consumed from
source via git submodule; tagging gives apps a stable, traceable pin.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 15:58:54 +02:00
e1919cab83 components: FullScreenPicker takes an optional actions slot
Forwards a trailing app-bar `actions` lambda through to CollapsingScaffold,
so a full-screen picker can carry a commit/extra action — a custom-value
Add/OK, a Reset — instead of being select-and-dismiss only.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 14:23:07 +02:00
4 changed files with 93 additions and 2 deletions

View File

@@ -10,6 +10,8 @@ are no binary releases; "version" tracks the shared `version` in the root build
## [Unreleased] ## [Unreleased]
## [0.1.0] - 2026-07-07
### Added ### Added
- **`core-time`** (JVM library) — pure-Kotlin date/time helpers, no Android and - **`core-time`** (JVM library) — pure-Kotlin date/time helpers, no Android and
@@ -46,7 +48,9 @@ are no binary releases; "version" tracks the shared `version` in the root build
`largeTopBar = false` for a pinned single-line bar (the picker variant). `largeTopBar = false` for a pinned single-line bar (the picker variant).
- `FullScreenPicker` / `OptionPicker` — the full-screen single-select picker, - `FullScreenPicker` / `OptionPicker` — the full-screen single-select picker,
now over a pinned single-line bar (a picker is a short list, so no tall now over a pinned single-line bar (a picker is a short list, so no tall
collapsing header to scroll past). collapsing header to scroll past). `FullScreenPicker` takes an optional
`actions` slot (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 - `ReorderableColumn` — dependency-free drag-to-reorder for the short, fixed
grouped-card Settings lists; pairs with `GroupedRow(gapBelow = false)`, which grouped-card Settings lists; pairs with `GroupedRow(gapBelow = false)`, which
hands the column uniform control of row spacing. hands the column uniform control of row spacing.

View File

@@ -10,5 +10,5 @@ plugins {
subprojects { subprojects {
group = "de.jeanlucmakiola.floret" group = "de.jeanlucmakiola.floret"
version = "0.1.0" version = "0.2.0"
} }

View File

@@ -1,8 +1,11 @@
package de.jeanlucmakiola.floret.components package de.jeanlucmakiola.floret.components
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
@@ -10,8 +13,12 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowDropDown import androidx.compose.material.icons.filled.ArrowDropDown
import androidx.compose.material3.DropdownMenu import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.FilledTonalButton
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.SegmentedButton
import androidx.compose.material3.SegmentedButtonDefaults
import androidx.compose.material3.SingleChoiceSegmentedButtonRow
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@@ -57,6 +64,83 @@ fun DialogAmountField(
} }
} }
/**
* The expanded "Custom" editor card shared by the family's presets-plus-custom
* pickers (reminder lead time, agenda range, snooze delay). A tonal surface with
* the connected-shape that meets the "Custom" row above it (tight top corners,
* full bottom), holding — top to bottom — an optional single-choice unit toggle,
* then a row of [DialogAmountField] + a live [preview] of what the amount
* resolves to + a tonal confirm.
*
* Purely presentational: the caller owns the amount/unit state and computes
* [preview] and [confirmEnabled] from its own domain, so this component carries
* no units, ranges, or strings of its own. Pass [unitLabels] empty for an
* amount-only editor (no toggle row). [onConfirm] fires on the confirm button.
*/
@Composable
fun CustomAmountEditor(
amountText: String,
onAmountChange: (String) -> Unit,
preview: String,
setLabel: String,
confirmEnabled: Boolean,
onConfirm: () -> Unit,
modifier: Modifier = Modifier,
placeholder: String = "10",
unitLabels: List<String> = emptyList(),
selectedUnit: Int = 0,
onUnitChange: (Int) -> Unit = {},
) {
Surface(
color = MaterialTheme.colorScheme.surfaceContainerHigh,
// A Position.Bottom shape: tight top corners meeting the row, full bottom.
shape = RoundedCornerShape(topStart = 6.dp, topEnd = 6.dp, bottomStart = 22.dp, bottomEnd = 22.dp),
modifier = modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
) {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp),
) {
// Unit toggle first (when present) so it stays visible above the
// keyboard once the amount field below it is focused and scrolled in.
if (unitLabels.isNotEmpty()) {
SingleChoiceSegmentedButtonRow(modifier = Modifier.fillMaxWidth()) {
unitLabels.forEachIndexed { index, label ->
SegmentedButton(
selected = index == selectedUnit,
onClick = { onUnitChange(index) },
shape = SegmentedButtonDefaults.itemShape(index, unitLabels.size),
label = { Text(label) },
)
}
}
}
// Amount, a live preview of what it resolves to, and confirm — all on
// one row, sitting just above the keyboard.
Row(verticalAlignment = Alignment.CenterVertically) {
DialogAmountField(
value = amountText,
onValueChange = onAmountChange,
placeholder = placeholder,
)
Spacer(Modifier.width(16.dp))
Text(
text = preview,
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.weight(1f),
)
Spacer(Modifier.width(16.dp))
FilledTonalButton(onClick = onConfirm, enabled = confirmEnabled) {
Text(setLabel)
}
}
}
}
}
/** Tonal dropdown trigger + menu shared by the custom reminder/recurrence steps and pickers. */ /** Tonal dropdown trigger + menu shared by the custom reminder/recurrence steps and pickers. */
@Composable @Composable
fun DialogUnitDropdown( fun DialogUnitDropdown(

View File

@@ -2,6 +2,7 @@ package de.jeanlucmakiola.floret.components
import android.view.WindowManager import android.view.WindowManager
import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.RowScope
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Check import androidx.compose.material.icons.rounded.Check
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
@@ -25,6 +26,7 @@ fun FullScreenPicker(
title: String, title: String,
onDismiss: () -> Unit, onDismiss: () -> Unit,
predictiveBack: Boolean = false, predictiveBack: Boolean = false,
actions: @Composable RowScope.() -> Unit = {},
content: @Composable ColumnScope.() -> Unit, content: @Composable ColumnScope.() -> Unit,
) { ) {
Dialog( Dialog(
@@ -48,6 +50,7 @@ fun FullScreenPicker(
onBack = onDismiss, onBack = onDismiss,
largeTopBar = false, largeTopBar = false,
predictiveBack = predictiveBack, predictiveBack = predictiveBack,
actions = actions,
content = content, content = content,
) )
} }