Compare commits

...

2 Commits

Author SHA1 Message Date
566caf4305 Merge origin/main: component superset alongside core-time date util
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 22:07:10 +02:00
d9e4e877cc components: grow GroupedRow + CollapsingScaffold + pickers to the family superset
Promote the richer variants Calendula had kept app-local into the shared kit, as
backward-compatible additions (every new parameter defaults to the prior
behaviour, so Agendula's call sites are unchanged):

- GroupedRow: add `dimmed` (fade headline/summary to M3 disabled emphasis while
  the trailing control stays live — for present-but-off rows) and `container`
  (tonal-colour override, e.g. a category tint).
- CollapsingScaffold: add `actions` (trailing app-bar items), `snackbarHost`,
  and opt-in `predictiveBack` (wires the back-gesture preview to onBack for
  full-screen surfaces that want it).
- FullScreenPicker: thread opt-in `predictiveBack` to the scaffold, and apply
  SOFT_INPUT_ADJUST_NOTHING so a picker's own imePadding lifts the focused field
  instead of leaving a black gap above the keyboard.
- OptionPicker: forward `predictiveBack` and add an optional `header` slot above
  the option rows.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 20:49:34 +02:00
3 changed files with 59 additions and 6 deletions

View File

@@ -3,6 +3,7 @@ package de.jeanlucmakiola.floret.components
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.consumeWindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.imePadding
@@ -25,6 +26,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import de.jeanlucmakiola.floret.identity.predictiveBack
/**
* The family's collapsing scaffold for settings hubs, sub-screens and the
@@ -32,6 +34,11 @@ import androidx.compose.ui.unit.dp
* button, and a vertically scrolling content column. [content] lays out the
* connected grouped rows. The back content description uses the kit's `back`
* string, which an app can override with its own (localized) `back`.
*
* Optional extras, all defaulting to the bare scaffold so existing callers are
* unchanged: [actions] adds trailing app-bar items; [snackbarHost] hosts
* transient messages; [predictiveBack] (off by default) wires the gesture
* preview to [onBack] for full-screen surfaces that want it.
*/
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@@ -39,12 +46,16 @@ fun CollapsingScaffold(
title: String,
onBack: () -> Unit,
modifier: Modifier = Modifier,
predictiveBack: Boolean = false,
actions: @Composable RowScope.() -> Unit = {},
snackbarHost: @Composable () -> Unit = {},
content: @Composable ColumnScope.() -> Unit,
) {
val scrollBehavior =
TopAppBarDefaults.exitUntilCollapsedScrollBehavior(rememberTopAppBarState())
Scaffold(
modifier = modifier
.then(if (predictiveBack) Modifier.predictiveBack(onBack = onBack) else Modifier)
.fillMaxSize()
.background(MaterialTheme.colorScheme.surface)
.nestedScroll(scrollBehavior.nestedScrollConnection),
@@ -59,12 +70,14 @@ fun CollapsingScaffold(
)
}
},
actions = actions,
scrollBehavior = scrollBehavior,
colors = TopAppBarDefaults.largeTopAppBarColors(
scrolledContainerColor = MaterialTheme.colorScheme.surface,
),
)
},
snackbarHost = snackbarHost,
) { innerPadding ->
Column(
modifier = Modifier

View File

@@ -82,6 +82,12 @@ fun GroupedSurface(
* One row in a grouped list: an M3 [ListItem] over a [GroupedSurface] (the
* tonal, position-shaped, press-morphing family container). Insets 16dp like
* the lists overview.
*
* [selected] highlights the row in the secondary container. [dimmed] fades the
* headline and summary to the M3 disabled emphasis while leaving the [trailing]
* control at full opacity — for rows that are present but switched off.
* [container] overrides the row's tonal colour (e.g. a category tint); ignored
* when [selected].
*/
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@@ -91,6 +97,8 @@ fun GroupedRow(
modifier: Modifier = Modifier,
summary: String? = null,
selected: Boolean = false,
dimmed: Boolean = false,
container: Color? = null,
minHeight: Dp = 72.dp,
leading: @Composable (() -> Unit)? = null,
trailing: @Composable (() -> Unit)? = null,
@@ -104,13 +112,23 @@ fun GroupedRow(
supportingColor = MaterialTheme.colorScheme.onSecondaryContainer,
trailingIconColor = MaterialTheme.colorScheme.onSecondaryContainer,
)
} else if (dimmed) {
// M3 disabled emphasis (0.38α) on the text/leading; the trailing control
// stays full-opacity so a toggle reads as live even on a faded row.
val muted = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.38f)
ListItemDefaults.colors(
containerColor = Color.Transparent,
headlineColor = muted,
leadingIconColor = muted,
supportingColor = muted,
)
} else {
ListItemDefaults.colors(containerColor = Color.Transparent)
}
val containerColor = if (selected) {
MaterialTheme.colorScheme.secondaryContainer
} else {
MaterialTheme.colorScheme.surfaceContainerHigh
val containerColor = when {
selected -> MaterialTheme.colorScheme.secondaryContainer
container != null -> container
else -> MaterialTheme.colorScheme.surfaceContainerHigh
}
GroupedSurface(
position = position,

View File

@@ -1,13 +1,17 @@
package de.jeanlucmakiola.floret.components
import android.view.WindowManager
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Check
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.SideEffect
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import androidx.compose.ui.window.DialogWindowProvider
/**
* Full-screen scaffold for selection pickers: a full-bleed [Dialog] that reuses
@@ -19,6 +23,7 @@ import androidx.compose.ui.window.DialogProperties
fun FullScreenPicker(
title: String,
onDismiss: () -> Unit,
predictiveBack: Boolean = false,
content: @Composable ColumnScope.() -> Unit,
) {
Dialog(
@@ -28,7 +33,21 @@ fun FullScreenPicker(
decorFitsSystemWindows = false,
),
) {
CollapsingScaffold(title = title, onBack = onDismiss, content = content)
// The dialog window pans by default when the keyboard opens, which —
// combined with the content's own imePadding — leaves a fixed black gap
// above the keyboard. Switch it to ADJUST_NOTHING so the window stays
// full-screen and imePadding alone lifts the focused field.
val view = LocalView.current
SideEffect {
(view.parent as? DialogWindowProvider)?.window
?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING)
}
CollapsingScaffold(
title = title,
onBack = onDismiss,
predictiveBack = predictiveBack,
content = content,
)
}
}
@@ -46,8 +65,11 @@ fun <T> OptionPicker(
onSelect: (T) -> Unit,
onDismiss: () -> Unit,
leading: (@Composable (T) -> Unit)? = null,
header: (@Composable ColumnScope.() -> Unit)? = null,
predictiveBack: Boolean = false,
) {
FullScreenPicker(title = title, onDismiss = onDismiss) {
FullScreenPicker(title = title, onDismiss = onDismiss, predictiveBack = predictiveBack) {
header?.invoke(this)
options.forEachIndexed { index, option ->
val isSelected = option == selected
GroupedRow(