feat(components): let a grouped row take a long-press

GroupedSurface/GroupedRow gain onLongClick (with the platform long-press
haptic), for lists that enter a selection mode from a row — Calendula's
search results (#80).
This commit is contained in:
2026-08-02 20:55:57 +02:00
parent 71a4f371b6
commit 3134098965
@@ -1,6 +1,8 @@
package de.jeanlucmakiola.floret.components
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsPressedAsState
import androidx.compose.foundation.layout.fillMaxWidth
@@ -13,12 +15,15 @@ import androidx.compose.material3.ListItemDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.ripple
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
@@ -65,13 +70,17 @@ fun groupedShape(position: Position, full: Dp, small: Dp): Shape = when (positio
* 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.
*
* [onLongClick] adds a long-press to the same surface (with the platform's
* long-press haptic), for lists that enter a selection mode from a row.
*/
@OptIn(ExperimentalMaterial3Api::class)
@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
@Composable
fun GroupedSurface(
position: Position,
modifier: Modifier = Modifier,
onClick: (() -> Unit)? = null,
onLongClick: (() -> Unit)? = null,
color: Color = MaterialTheme.colorScheme.surfaceContainerHigh,
gapBelow: Boolean = true,
content: @Composable () -> Unit,
@@ -87,10 +96,24 @@ fun GroupedSurface(
else -> Modifier
}
val base = modifier.fillMaxWidth().then(gap)
if (onClick != null) {
Surface(onClick = onClick, color = color, shape = shape, interactionSource = interaction, modifier = base) { content() }
} else {
Surface(color = color, shape = shape, modifier = base) { content() }
val haptics = LocalHapticFeedback.current
when {
onLongClick != null -> Surface(
color = color,
shape = shape,
modifier = base.combinedClickable(
interactionSource = interaction,
indication = ripple(),
onClick = { onClick?.invoke() },
onLongClick = {
haptics.performHapticFeedback(HapticFeedbackType.LongPress)
onLongClick()
},
),
) { content() }
onClick != null ->
Surface(onClick = onClick, color = color, shape = shape, interactionSource = interaction, modifier = base) { content() }
else -> Surface(color = color, shape = shape, modifier = base) { content() }
}
}
@@ -105,6 +128,7 @@ fun GroupedSurface(
* [container] overrides the row's tonal colour (e.g. a category tint); ignored
* when [selected]. [gapBelow] (see [GroupedSurface]) suppresses the inter-row
* gap for a caller that owns its own uniform spacing, e.g. [ReorderableColumn].
* [onLongClick] (see [GroupedSurface]) adds a long-press to the row.
*/
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@@ -121,6 +145,7 @@ fun GroupedRow(
leading: @Composable (() -> Unit)? = null,
trailing: @Composable (() -> Unit)? = null,
onClick: (() -> Unit)? = null,
onLongClick: (() -> Unit)? = null,
) {
val itemColors = if (selected) {
ListItemDefaults.colors(
@@ -152,6 +177,7 @@ fun GroupedRow(
position = position,
modifier = modifier.padding(horizontal = GroupedListInset),
onClick = onClick,
onLongClick = onLongClick,
color = containerColor,
gapBelow = gapBelow,
) {