Updated on 2026-08-14
This commit is contained in:
parent
91f7ed4b24
commit
bf34edf3f7
3 changed files with 153 additions and 128 deletions
|
|
@ -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() },
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue