feat: add custom segmented buttons and refactor conversation screen actions

This commit is contained in:
2026-05-30 11:32:40 +03:00
parent 26a0630393
commit c8bd485724
5 changed files with 177 additions and 50 deletions
@@ -1,6 +1,7 @@
package dev.meloda.fast.ui.components
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.Indication
import androidx.compose.foundation.background
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.interaction.MutableInteractionSource
@@ -20,6 +21,8 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterial3ExpressiveApi::class)
@@ -30,27 +33,32 @@ fun FastIconButton(
onLongClick: (() -> Unit)? = null,
enabled: Boolean = true,
colors: IconButtonColors = IconButtonDefaults.iconButtonColors(),
containerColor: Color = colors.containerColor(enabled),
contentColor: Color = colors.contentColor(enabled),
size: Dp = IconButtonTokens.StateLayerSize,
shape: Shape = IconButtonTokens.StateLayerShape,
alignment: Alignment = Alignment.Center,
interactionSource: MutableInteractionSource? = null,
indication: Indication = ripple(),
content: @Composable () -> Unit
) {
Box(
modifier =
modifier
.minimumInteractiveComponentSize()
.size(IconButtonTokens.StateLayerSize)
.clip(IconButtonTokens.StateLayerShape)
.background(color = colors.containerColor(enabled))
.size(size)
.clip(shape)
.background(containerColor)
.combinedClickable(
onClick = onClick,
onLongClick = onLongClick,
enabled = enabled,
interactionSource = interactionSource,
indication = ripple()
indication = indication
),
contentAlignment = Alignment.Center
contentAlignment = alignment
) {
val contentColor = colors.contentColor(enabled)
CompositionLocalProvider(LocalContentColor provides contentColor, content = content)
CompositionLocalProvider(LocalContentColor provides contentColor) { content() }
}
}
@@ -0,0 +1,116 @@
package dev.meloda.fast.ui.components
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.sizeIn
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.CornerBasedShape
import androidx.compose.foundation.shape.CornerSize
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.VerticalDivider
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import dev.meloda.fast.ui.util.ImmutableList
data class SegmentedButtonItem(
val key: String,
val iconResId: Int
)
@Composable
fun SegmentedButtonsRow(
items: ImmutableList<SegmentedButtonItem>,
onClick: (index: Int) -> Unit,
modifier: Modifier = Modifier,
containerShape: CornerBasedShape = RoundedCornerShape(24.dp),
containerColor: Color = MaterialTheme.colorScheme.background,
borderColor: Color = MaterialTheme.colorScheme.outlineVariant,
borderSize: Dp = 1.dp,
iconContainerWidth: Dp = 42.dp,
iconContainerHeight: Dp = 36.dp,
iconSize: Dp = 18.dp,
showDividers: Boolean = true
) {
SegmentedButtonsRow(
modifier = modifier.sizeIn(maxHeight = iconContainerHeight + borderSize),
items = items.mapIndexed { index, item ->
{
val first = index == 0
val last = index == items.lastIndex
if (showDividers && !first) {
VerticalDivider(modifier = Modifier.padding(vertical = iconContainerHeight / 4))
}
SegmentedButton(
onClick = { onClick(index) },
iconResId = item.iconResId,
modifier = Modifier.size(
iconContainerWidth,
iconContainerHeight
),
iconSize = iconSize,
shape = containerShape.copy(
topStart = if (!first) CornerSize(0.dp) else containerShape.topStart,
bottomStart = if (!first) CornerSize(0.dp) else containerShape.bottomStart,
topEnd = if (!last) CornerSize(0.dp) else containerShape.topEnd,
bottomEnd = if (!last) CornerSize(0.dp) else containerShape.bottomEnd
)
)
}
},
containerShape = containerShape,
containerColor = containerColor,
borderColor = borderColor,
borderSize = borderSize
)
}
@Composable
fun SegmentedButtonsRow(
items: ImmutableList<@Composable () -> Unit>,
modifier: Modifier = Modifier,
containerShape: CornerBasedShape = RoundedCornerShape(24.dp),
containerColor: Color = MaterialTheme.colorScheme.background,
borderColor: Color = MaterialTheme.colorScheme.outlineVariant,
borderSize: Dp = 1.dp,
) {
Row(
modifier = modifier
.background(containerColor, containerShape)
.border(borderSize, borderColor, containerShape)
) {
items.forEach { it.invoke() }
}
}
@Composable
fun SegmentedButton(
onClick: () -> Unit,
iconResId: Int,
modifier: Modifier = Modifier,
iconSize: Dp = 18.dp,
shape: Shape = CircleShape
) {
FastIconButton(
onClick = onClick,
modifier = modifier,
shape = shape
) {
Icon(
modifier = Modifier.size(iconSize),
painter = painterResource(iconResId),
contentDescription = null
)
}
}
@@ -1,6 +1,7 @@
package dev.meloda.fast.ui.util
import androidx.compose.runtime.Immutable
import dev.meloda.fast.ui.util.ImmutableList.Companion.toImmutableList
@Immutable
class ImmutableList<T>(val values: List<T>) : Collection<T> {
@@ -57,3 +58,9 @@ class ImmutableList<T>(val values: List<T>) : Collection<T> {
fun <T> emptyImmutableList(): ImmutableList<T> = ImmutableList(emptyList())
fun <T> immutableListOf(vararg elements: T) = ImmutableList(listOf(elements = elements))
inline fun <T> buildImmutableList(builderAction: MutableList<T>.() -> Unit): ImmutableList<T> {
val mutableList = mutableListOf<T>()
mutableList.apply(builderAction)
return mutableList.toImmutableList()
}