Updated on 2026-08-14

This commit is contained in:
Tangem 2026-06-23 19:22:52 +02:00
parent 91f7ed4b24
commit bf34edf3f7
3 changed files with 153 additions and 128 deletions

View file

@ -15,6 +15,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.text.style.TextAlign
@ -27,7 +28,6 @@ import androidx.compose.ui.unit.coerceAtLeast
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastForEachIndexed
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.conditional
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreviewRedesign
@ -108,47 +108,121 @@ fun TangemSegmentedPicker(
segmentHeight = segmentHeight.value,
separatorWidth = SEPARATOR_WIDTH,
)
Row(verticalAlignment = Alignment.CenterVertically) {
items.fastForEachIndexed { index, item ->
Segment(
item = item,
index = index,
isFixed = isFixed,
minSegmentWidth = minSegmentWidth,
selectedIndex = selectedIndex,
onClick = { onClick(item) },
modifier = Modifier
.onGloballyPositioned {
with(density) {
itemsWidths[index] = it.size.width.toDp()
segmentHeight.value = it.size.height.toDp().coerceAtLeast(segmentHeight.value)
}
},
)
if (index != items.lastIndex) {
val selected by selectedIndex
val alpha by animateFloatAsState(
targetValue = if (selected == index || selected == index + 1) 0f else 1f,
animationSpec = tween(durationMillis = 300),
label = "separatorAlpha",
SegmentsRow(
isFixed = isFixed,
minSegmentWidth = minSegmentWidth,
segmentCount = items.size,
content = {
items.fastForEachIndexed { index, item ->
Segment(
item = item,
index = index,
minSegmentWidth = minSegmentWidth,
selectedIndex = selectedIndex,
onClick = { onClick(item) },
modifier = Modifier
.onGloballyPositioned {
with(density) {
itemsWidths[index] = it.size.width.toDp()
segmentHeight.value = it.size.height.toDp().coerceAtLeast(segmentHeight.value)
}
},
)
Box(
Modifier
.alpha(alpha)
.width(SEPARATOR_WIDTH)
.height(20.dp)
.background(
color = TangemTheme.colors2.border.neutral.tertiary.copy(alpha = 0.1f),
),
)
if (index != items.lastIndex) {
Separator(index = index, selectedIndex = selectedIndex)
}
}
},
)
}
}
/**
* Lays out segments (with the inter-segment separators interleaved) in a single row.
*
* - When [isFixed] is `true` and the parent width is bounded, every segment is sized to at least its
* content width, and the remaining free space is distributed equally between segments so the row
* fills the full width without clipping any segment's text.
* - Otherwise segments wrap their content.
*
* Children must be supplied as `segment, separator, segment, separator, , segment` (even indices are
* segments, odd indices are separators).
*/
@Composable
private fun SegmentsRow(
isFixed: Boolean,
minSegmentWidth: Dp,
segmentCount: Int,
modifier: Modifier = Modifier,
content: @Composable () -> Unit,
) {
Layout(modifier = modifier, content = content) { measurables, constraints ->
val segMeasurables = measurables.filterIndexed { i, _ -> i % 2 == 0 }
val sepMeasurables = measurables.filterIndexed { i, _ -> i % 2 == 1 }
val sepPlaceables = sepMeasurables.map { it.measure(constraints.copy(minWidth = 0)) }
val totalSeparators = sepPlaceables.sumOf { it.width }
val minPx = if (minSegmentWidth != Dp.Unspecified) minSegmentWidth.roundToPx() else 0
val baseWidths = segMeasurables.map { maxOf(it.maxIntrinsicWidth(constraints.maxHeight), minPx) }
val widths = if (isFixed && constraints.hasBoundedWidth) {
val leftover = constraints.maxWidth - baseWidths.sum() - totalSeparators
if (leftover > 0) {
val extra = leftover / segmentCount
val remainder = leftover % segmentCount
baseWidths.mapIndexed { i, w -> w + extra + if (i < remainder) 1 else 0 }
} else {
baseWidths
}
} else {
baseWidths
}
val segPlaceables = segMeasurables.mapIndexed { i, m ->
m.measure(constraints.copy(minWidth = widths[i], maxWidth = widths[i]))
}
val layoutWidth = (widths.sum() + totalSeparators)
.coerceIn(constraints.minWidth, constraints.maxWidth)
val layoutHeight = (segPlaceables + sepPlaceables).maxOf { it.height }
.coerceIn(constraints.minHeight, constraints.maxHeight)
layout(layoutWidth, layoutHeight) {
var x = 0
segPlaceables.forEachIndexed { i, seg ->
seg.placeRelative(x, (layoutHeight - seg.height) / 2)
x += seg.width
sepPlaceables.getOrNull(i)?.let { sep ->
sep.placeRelative(x, (layoutHeight - sep.height) / 2)
x += sep.width
}
}
}
}
}
@Composable
private fun Separator(index: Int, selectedIndex: MutableState<Int>) {
val selected by selectedIndex
val alpha by animateFloatAsState(
targetValue = if (selected == index || selected == index + 1) 0f else 1f,
animationSpec = tween(durationMillis = 300),
label = "separatorAlpha",
)
Box(
Modifier
.alpha(alpha)
.width(SEPARATOR_WIDTH)
.height(20.dp)
.background(
color = TangemTheme.colors2.border.neutral.tertiary.copy(alpha = 0.1f),
),
)
}
private val SEPARATOR_WIDTH = 0.5.dp
@Composable
@ -199,10 +273,9 @@ private fun SegmentSelection(
}
@Composable
private fun RowScope.Segment(
private fun Segment(
item: TangemSegmentUM,
index: Int,
isFixed: Boolean,
selectedIndex: MutableState<Int>,
onClick: () -> Unit,
modifier: Modifier = Modifier,
@ -211,9 +284,6 @@ private fun RowScope.Segment(
Box(
modifier = modifier
.defaultMinSize(minWidth = minSegmentWidth)
.conditional(isFixed) {
weight(1f)
}
.clickable(
indication = null,
interactionSource = remember { MutableInteractionSource() },

View file

@ -1,32 +0,0 @@
package com.tangem.features.feed.ui.components
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.HorizontalDivider
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.res.TangemTheme
@Composable
internal fun ContainerWithDivider(
modifier: Modifier = Modifier,
showDivider: Boolean = false,
paddingValues: PaddingValues = PaddingValues(start = TangemTheme.dimens2.x3),
content: @Composable () -> Unit,
) {
Box(modifier = modifier) {
content()
if (showDivider) {
HorizontalDivider(
modifier = Modifier
.align(Alignment.BottomCenter)
.padding(paddingValues),
color = TangemTheme.colors2.graphic.neutral.quaternary,
thickness = 1.dp,
)
}
}
}

View file

@ -27,7 +27,6 @@ import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.res.TangemThemePreviewRedesign
import com.tangem.core.ui.utils.PreviewShimmerContainer
import com.tangem.features.feed.impl.R
import com.tangem.features.feed.ui.components.ContainerWithDivider
import com.tangem.features.feed.ui.market.detailed.state.LinksUM
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
@ -105,7 +104,6 @@ private fun LinksBlockV2(state: LinksUM, modifier: Modifier = Modifier) {
title = stringResourceSafe(id = R.string.markets_token_details_blockchain_site),
links = state.blockchainSite,
onLinkClick = state.onLinkClick,
lastBlock = true,
)
}
}
@ -159,44 +157,38 @@ private fun SubBlockV2(
links: ImmutableList<LinksUM.Link>,
onLinkClick: (LinksUM.Link) -> Unit,
modifier: Modifier = Modifier,
lastBlock: Boolean = false,
) {
if (links.isEmpty()) return
ContainerWithDivider(
modifier = modifier,
showDivider = !lastBlock,
Column(
modifier = modifier.padding(vertical = TangemTheme.dimens2.x2),
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x2),
) {
Column(
modifier = Modifier.padding(vertical = TangemTheme.dimens2.x2),
Text(
modifier = Modifier.padding(start = 10.dp, top = TangemTheme.dimens2.x4),
text = title,
style = TangemTheme.typography2.headingSemibold20,
color = TangemTheme.colors2.text.neutral.primary,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
FlowRow(
modifier = Modifier.padding(vertical = TangemTheme.dimens2.x2, horizontal = 3.dp),
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x2),
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x2),
) {
Text(
modifier = Modifier.padding(start = 10.dp, top = TangemTheme.dimens2.x4),
text = title,
style = TangemTheme.typography2.headingSemibold20,
color = TangemTheme.colors2.text.neutral.primary,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
FlowRow(
modifier = Modifier.padding(vertical = TangemTheme.dimens2.x2, horizontal = 3.dp),
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x2),
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x2),
) {
links.fastForEach { link ->
SecondaryTangemButton(
onClick = { onLinkClick(link) },
text = stringReference(link.title),
iconPosition = TangemButtonIconPosition.Start,
tangemIconUM = TangemIconUM.Icon(
iconRes = link.iconRes,
tintReference = { TangemTheme.colors2.graphic.neutral.primary },
),
size = TangemButtonSize.X9,
shape = TangemButtonShape.Rounded,
)
}
links.fastForEach { link ->
SecondaryTangemButton(
onClick = { onLinkClick(link) },
text = stringReference(link.title),
iconPosition = TangemButtonIconPosition.Start,
tangemIconUM = TangemIconUM.Icon(
iconRes = link.iconRes,
tintReference = { TangemTheme.colors2.graphic.neutral.primary },
),
size = TangemButtonSize.X9,
shape = TangemButtonShape.Rounded,
)
}
}
}
@ -237,7 +229,7 @@ private fun LinksBlockPlaceholderV2(modifier: Modifier = Modifier) {
Column(modifier = modifier) {
SubBlockPlaceholderV2()
SubBlockPlaceholderV2()
SubBlockPlaceholderV2(lastBlock = true)
SubBlockPlaceholderV2()
}
}
@ -269,30 +261,25 @@ private fun SubBlockPlaceholderV1(modifier: Modifier = Modifier, lastBlock: Bool
}
@Composable
private fun SubBlockPlaceholderV2(modifier: Modifier = Modifier, lastBlock: Boolean = false) {
ContainerWithDivider(
modifier = modifier,
showDivider = !lastBlock,
private fun SubBlockPlaceholderV2(modifier: Modifier = Modifier) {
Column(
modifier = modifier.padding(TangemTheme.dimens2.x2),
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x2),
) {
Column(
modifier = Modifier.padding(TangemTheme.dimens2.x2),
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x2),
TextShimmer(
modifier = Modifier.width(56.dp),
style = TangemTheme.typography2.bodySemibold16,
radius = TangemTheme.dimens2.x25,
)
Row(
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x2),
) {
TextShimmer(
modifier = Modifier.width(56.dp),
style = TangemTheme.typography2.bodySemibold16,
radius = TangemTheme.dimens2.x25,
)
Row(
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x2),
) {
repeat(times = 3) {
ChipShimmer(
modifier = Modifier
.height(36.dp)
.weight(1f),
)
}
repeat(times = 3) {
ChipShimmer(
modifier = Modifier
.height(36.dp)
.weight(1f),
)
}
}
}