diff --git a/components/src/main/kotlin/de/jeanlucmakiola/floret/components/CollapsingScaffold.kt b/components/src/main/kotlin/de/jeanlucmakiola/floret/components/CollapsingScaffold.kt index d8c662d..65ee50d 100644 --- a/components/src/main/kotlin/de/jeanlucmakiola/floret/components/CollapsingScaffold.kt +++ b/components/src/main/kotlin/de/jeanlucmakiola/floret/components/CollapsingScaffold.kt @@ -19,6 +19,7 @@ import androidx.compose.material3.LargeTopAppBar import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.rememberTopAppBarState import androidx.compose.runtime.Composable @@ -30,10 +31,16 @@ import de.jeanlucmakiola.floret.identity.predictiveBack /** * The family's collapsing scaffold for settings hubs, sub-screens and the - * full-screen pickers: a [LargeTopAppBar] that collapses on scroll, a back - * 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`. + * full-screen pickers. By default a [LargeTopAppBar] whose title shrinks into + * the bar as the content scrolls — for settings and sub-screens, where the large + * header sets the page — over 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`. + * + * Set [largeTopBar] to false for a pinned, single-line [TopAppBar] instead: the + * title sits in the bar from the start with no expanded header to scroll past. + * Preferred for selection pickers, where the tall header is only empty space + * above a short list. * * Optional extras, all defaulting to the bare scaffold so existing callers are * unchanged: [actions] adds trailing app-bar items; [snackbarHost] hosts @@ -46,13 +53,17 @@ fun CollapsingScaffold( title: String, onBack: () -> Unit, modifier: Modifier = Modifier, + largeTopBar: Boolean = true, predictiveBack: Boolean = false, actions: @Composable RowScope.() -> Unit = {}, snackbarHost: @Composable () -> Unit = {}, content: @Composable ColumnScope.() -> Unit, ) { - val scrollBehavior = + val scrollBehavior = if (largeTopBar) { TopAppBarDefaults.exitUntilCollapsedScrollBehavior(rememberTopAppBarState()) + } else { + TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()) + } Scaffold( modifier = modifier .then(if (predictiveBack) Modifier.predictiveBack(onBack = onBack) else Modifier) @@ -60,22 +71,35 @@ fun CollapsingScaffold( .background(MaterialTheme.colorScheme.surface) .nestedScroll(scrollBehavior.nestedScrollConnection), topBar = { - LargeTopAppBar( - title = { Text(title) }, - navigationIcon = { - IconButton(onClick = onBack) { - Icon( - Icons.AutoMirrored.Rounded.ArrowBack, - contentDescription = stringResource(R.string.back), - ) - } - }, - actions = actions, - scrollBehavior = scrollBehavior, - colors = TopAppBarDefaults.largeTopAppBarColors( - scrolledContainerColor = MaterialTheme.colorScheme.surface, - ), - ) + val navigationIcon = @Composable { + IconButton(onClick = onBack) { + Icon( + Icons.AutoMirrored.Rounded.ArrowBack, + contentDescription = stringResource(R.string.back), + ) + } + } + if (largeTopBar) { + LargeTopAppBar( + title = { Text(title) }, + navigationIcon = navigationIcon, + actions = actions, + scrollBehavior = scrollBehavior, + colors = TopAppBarDefaults.largeTopAppBarColors( + scrolledContainerColor = MaterialTheme.colorScheme.surface, + ), + ) + } else { + TopAppBar( + title = { Text(title) }, + navigationIcon = navigationIcon, + actions = actions, + scrollBehavior = scrollBehavior, + colors = TopAppBarDefaults.topAppBarColors( + scrolledContainerColor = MaterialTheme.colorScheme.surface, + ), + ) + } }, snackbarHost = snackbarHost, ) { innerPadding -> diff --git a/components/src/main/kotlin/de/jeanlucmakiola/floret/components/GroupedList.kt b/components/src/main/kotlin/de/jeanlucmakiola/floret/components/GroupedList.kt index 70e0fab..f11b6d6 100644 --- a/components/src/main/kotlin/de/jeanlucmakiola/floret/components/GroupedList.kt +++ b/components/src/main/kotlin/de/jeanlucmakiola/floret/components/GroupedList.kt @@ -51,6 +51,10 @@ fun groupedShape(position: Position, full: Dp, small: Dp): Shape = when (positio * all but the last. Corners morph rounder on press — the expressive shape play. * The caller supplies [content] and any horizontal inset via [modifier], so the * same primitive serves both the 16dp-inset lists and full-width detail cards. + * + * Set [gapBelow] to false to suppress that 2dp separation — for a caller that + * owns uniform spacing itself, e.g. [ReorderableColumn], where every slot must + * share the same pitch for the drag maths to line up. */ @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -59,6 +63,7 @@ fun GroupedSurface( modifier: Modifier = Modifier, onClick: (() -> Unit)? = null, color: Color = MaterialTheme.colorScheme.surfaceContainerHigh, + gapBelow: Boolean = true, content: @Composable () -> Unit, ) { val interaction = remember { MutableInteractionSource() } @@ -66,9 +71,10 @@ fun GroupedSurface( val full by animateDpAsState(if (pressed) 36.dp else 22.dp, label = "fullCorner") val small by animateDpAsState(if (pressed) 36.dp else 6.dp, label = "smallCorner") val shape = groupedShape(position, full, small) - val gap = when (position) { - Position.Top, Position.Middle -> Modifier.padding(bottom = 2.dp) - Position.Bottom, Position.Alone -> Modifier + val gap = when { + !gapBelow -> Modifier + position == Position.Top || position == Position.Middle -> Modifier.padding(bottom = 2.dp) + else -> Modifier } val base = modifier.fillMaxWidth().then(gap) if (onClick != null) { @@ -87,7 +93,8 @@ fun GroupedSurface( * 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]. + * when [selected]. [gapBelow] (see [GroupedSurface]) suppresses the inter-row + * gap for a caller that owns its own uniform spacing, e.g. [ReorderableColumn]. */ @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -100,6 +107,7 @@ fun GroupedRow( dimmed: Boolean = false, container: Color? = null, minHeight: Dp = 72.dp, + gapBelow: Boolean = true, leading: @Composable (() -> Unit)? = null, trailing: @Composable (() -> Unit)? = null, onClick: (() -> Unit)? = null, @@ -135,6 +143,7 @@ fun GroupedRow( modifier = modifier.padding(horizontal = 16.dp), onClick = onClick, color = containerColor, + gapBelow = gapBelow, ) { ListItem( headlineContent = { Text(title) }, diff --git a/components/src/main/kotlin/de/jeanlucmakiola/floret/components/InlineTextField.kt b/components/src/main/kotlin/de/jeanlucmakiola/floret/components/InlineTextField.kt index 538d1c5..f36aa93 100644 --- a/components/src/main/kotlin/de/jeanlucmakiola/floret/components/InlineTextField.kt +++ b/components/src/main/kotlin/de/jeanlucmakiola/floret/components/InlineTextField.kt @@ -36,6 +36,7 @@ fun InlineTextField( textStyle: TextStyle = MaterialTheme.typography.titleMedium, singleLine: Boolean = true, minLines: Int = 1, + enabled: Boolean = true, keyboardType: KeyboardType = KeyboardType.Text, capitalization: KeyboardCapitalization = KeyboardCapitalization.Sentences, imeAction: ImeAction = ImeAction.Default, @@ -43,15 +44,17 @@ fun InlineTextField( onImeAction: (() -> Unit)? = null, ) { val resolvedStyle = textStyle.copy( - color = if (textStyle.color.isSpecified) { - textStyle.color - } else { - MaterialTheme.colorScheme.onSurface + color = when { + // A disabled field reads as dimmed, like a locked value. + !enabled -> MaterialTheme.colorScheme.onSurfaceVariant + textStyle.color.isSpecified -> textStyle.color + else -> MaterialTheme.colorScheme.onSurface }, ) BasicTextField( value = value, onValueChange = onValueChange, + enabled = enabled, textStyle = resolvedStyle, singleLine = singleLine, minLines = minLines,