diff --git a/common/ui/src/main/java/com/tangem/common/ui/news/ArticleCard.kt b/common/ui/src/main/java/com/tangem/common/ui/news/ArticleCard.kt index 43f01a3e93..a6a7a2a078 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/news/ArticleCard.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/news/ArticleCard.kt @@ -2,7 +2,10 @@ package com.tangem.common.ui.news import android.content.res.Configuration import androidx.compose.foundation.* -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.CardColors import androidx.compose.material3.Text @@ -11,6 +14,8 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.vectorResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow @@ -21,15 +26,12 @@ import com.tangem.core.ui.components.SpacerH import com.tangem.core.ui.components.SpacerHMax import com.tangem.core.ui.components.block.BlockCard import com.tangem.core.ui.components.block.TangemBlockCardColors -import com.tangem.core.ui.components.label.Label import com.tangem.core.ui.components.label.entity.LabelUM import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview -import com.tangem.utils.StringsSigns -import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableSet @@ -65,52 +67,61 @@ private fun TrendingArticle( colors: CardColors, modifier: Modifier = Modifier, ) { - Column( + Box( modifier = modifier .clip(TangemTheme.shapes.roundedCornersXMedium) .background(colors.containerColor) - .clickable { onArticleClick() } - .padding(vertical = 24.dp, horizontal = 16.dp), - horizontalAlignment = Alignment.CenterHorizontally, + .clickable { onArticleClick() }, ) { - Text( - modifier = Modifier - .background( - color = TangemTheme.colors.icon.accent.copy(alpha = 0.1f), - shape = RoundedCornerShape(8.dp), - ) - .padding(horizontal = 8.dp, vertical = 4.dp), - text = stringResourceSafe(R.string.feed_trending_now), - style = TangemTheme.typography.caption1, - color = TangemTheme.colors.icon.accent, + Image( + modifier = Modifier.matchParentSize(), + painter = painterResource(R.drawable.bg_trending_article), + contentDescription = null, + contentScale = ContentScale.FillWidth, ) + Column( + modifier = Modifier.padding(vertical = 24.dp, horizontal = 16.dp), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Text( + modifier = Modifier + .background( + color = TangemTheme.colors.icon.accent.copy(alpha = 0.1f), + shape = RoundedCornerShape(8.dp), + ) + .padding(horizontal = 8.dp, vertical = 4.dp), + text = stringResourceSafe(R.string.feed_trending_now), + style = TangemTheme.typography.caption1, + color = TangemTheme.colors.icon.accent, + ) - SpacerH(12.dp) + SpacerH(12.dp) - Text( - text = articleConfigUM.title, - color = if (articleConfigUM.isViewed) { - TangemTheme.colors.text.tertiary - } else { - TangemTheme.colors.text.primary1 - }, - style = TangemTheme.typography.h3, - textAlign = TextAlign.Center, - ) + Text( + text = articleConfigUM.title, + color = if (articleConfigUM.isViewed) { + TangemTheme.colors.text.tertiary + } else { + TangemTheme.colors.text.primary1 + }, + style = TangemTheme.typography.h3, + textAlign = TextAlign.Center, + ) - SpacerH(8.dp) + SpacerH(8.dp) - ArticleInfo( - score = articleConfigUM.score, - createdAt = articleConfigUM.createdAt.resolveReference(), - ) + ArticleInfo( + score = articleConfigUM.score, + createdAt = articleConfigUM.createdAt.resolveReference(), + ) - SpacerH(32.dp) + SpacerH(32.dp) - Tags( - modifier = Modifier.padding(horizontal = 46.dp), - tags = articleConfigUM.tags.toImmutableList(), - ) + Tags( + modifier = Modifier.padding(horizontal = 46.dp), + tags = articleConfigUM.tags.toImmutableList(), + ) + } } } @@ -182,46 +193,14 @@ private fun DefaultArticle( overflow = TextOverflow.Ellipsis, ) - SpacerHMax(modifier = Modifier.padding(bottom = 16.dp)) + SpacerH(16.dp) + + SpacerHMax() Tags(tags = articleConfigUM.tags.toImmutableList()) } } -@Composable -private fun Tags(tags: ImmutableList, modifier: Modifier = Modifier) { - FlowRow( - modifier = modifier, - horizontalArrangement = Arrangement.spacedBy(4.dp), - verticalArrangement = Arrangement.spacedBy(8.dp), - maxItemsInEachRow = MAX_TAGS_COUNT_TO_SHOW + 1, - maxLines = 1, - itemVerticalAlignment = Alignment.Bottom, - ) { - val displayedTags = if (tags.size > MAX_TAGS_COUNT_TO_SHOW) { - tags.take(MAX_TAGS_COUNT_TO_SHOW) - } else { - tags - } - - displayedTags.forEach { tag -> - Label(state = tag) - } - - if (tags.size > MAX_TAGS_COUNT_TO_SHOW) { - val remainingItems = tags.size - MAX_TAGS_COUNT_TO_SHOW - Label( - state = LabelUM( - text = TextReference.Str("${StringsSigns.PLUS}$remainingItems"), - maxLines = 1, - ), - ) - } - } -} - -private const val MAX_TAGS_COUNT_TO_SHOW = 3 - @Preview(showBackground = true, widthDp = 360) @Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable diff --git a/common/ui/src/main/java/com/tangem/common/ui/news/Tags.kt b/common/ui/src/main/java/com/tangem/common/ui/news/Tags.kt new file mode 100644 index 0000000000..a163c8ee15 --- /dev/null +++ b/common/ui/src/main/java/com/tangem/common/ui/news/Tags.kt @@ -0,0 +1,128 @@ +package com.tangem.common.ui.news + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.Placeable +import androidx.compose.ui.layout.SubcomposeLayout +import androidx.compose.ui.layout.SubcomposeMeasureScope +import androidx.compose.ui.unit.Constraints +import androidx.compose.ui.unit.dp +import com.tangem.core.ui.components.label.Label +import com.tangem.core.ui.components.label.entity.LabelUM +import com.tangem.core.ui.extensions.TextReference +import com.tangem.utils.StringsSigns +import kotlinx.collections.immutable.ImmutableList + +private val TAG_SPACING = 4.dp + +/** + * A layout that displays a list of tags, automatically calculating how many can fit + * in a single line. If not all tags fit, it shows an overflow indicator (e.g., "+N"). + * + * @param tags The immutable list of [LabelUM] to display. + * @param modifier The modifier to be applied to the layout. + */ +@Composable +internal fun Tags(tags: ImmutableList, modifier: Modifier = Modifier) { + if (tags.isEmpty()) return + + SubcomposeLayout(modifier = modifier) { constraints -> + val spacingPx = TAG_SPACING.roundToPx() + + val tagPlaceables = subcompose(ContentSlot.Tags) { + tags.forEach { tag -> + Label(state = tag) + } + }.map { it.measure(constraints) } + + val layoutInfo = calculateLayoutInfo( + maxWidth = constraints.maxWidth, + spacingPx = spacingPx, + tagPlaceables = tagPlaceables, + ) + + val overflowPlaceable = layoutInfo.overflowCount.takeIf { it > 0 }?.let { + subcompose(ContentSlot.Overflow) { + OverflowLabel(count = it) + }.first().measure(constraints) + } + + val visiblePlaceables = tagPlaceables.take(layoutInfo.visibleCount) + val allVisibleItems = visiblePlaceables + listOfNotNull(overflowPlaceable) + + val width = calculateRowWidth( + placeables = allVisibleItems, + spacingPx = spacingPx, + ).coerceIn(constraints.minWidth, constraints.maxWidth) + + val height = allVisibleItems.maxOfOrNull { it.height } ?: 0 + + layout(width = width, height = height) { + var xPosition = 0 + allVisibleItems.forEachIndexed { index, placeable -> + if (index > 0) { + xPosition += spacingPx + } + val yPosition = height - placeable.height + placeable.placeRelative(xPosition, yPosition) + xPosition += placeable.width + } + } + } +} + +/** + * Calculates how many tags can be displayed and how many are overflowing. + */ +private fun SubcomposeMeasureScope.calculateLayoutInfo( + maxWidth: Int, + spacingPx: Int, + tagPlaceables: List, +): TagsLayoutInfo { + val totalWidthWithoutOverflow = calculateRowWidth(tagPlaceables, spacingPx) + + if (totalWidthWithoutOverflow <= maxWidth) { + return TagsLayoutInfo(visibleCount = tagPlaceables.size, overflowCount = 0) + } + + for (count in tagPlaceables.indices.reversed()) { + val visibleTags = tagPlaceables.take(count) + val overflowCount = tagPlaceables.size - count + + val tempOverflowPlaceable = subcompose("overflow_temp_$overflowCount") { + OverflowLabel(count = overflowCount) + }.first().measure(Constraints()) + + val widthWithOverflow = calculateRowWidth( + placeables = visibleTags + tempOverflowPlaceable, + spacingPx = spacingPx, + ) + + if (widthWithOverflow <= maxWidth) { + return TagsLayoutInfo(visibleCount = count, overflowCount = overflowCount) + } + } + + return TagsLayoutInfo(visibleCount = 0, overflowCount = tagPlaceables.size) +} + +@Composable +private fun OverflowLabel(count: Int) { + Label( + state = LabelUM( + text = TextReference.Str("${StringsSigns.PLUS}$count"), + maxLines = 1, + ), + ) +} + +private fun calculateRowWidth(placeables: List, spacingPx: Int): Int { + if (placeables.isEmpty()) return 0 + val tagsWidth = placeables.sumOf { it.width } + val spacingWidth = spacingPx * (placeables.size - 1).coerceAtLeast(0) + return tagsWidth + spacingWidth +} + +private data class TagsLayoutInfo(val visibleCount: Int, val overflowCount: Int) + +private enum class ContentSlot { Tags, Overflow } \ No newline at end of file diff --git a/core/ui/src/main/res/drawable/bg_trending_article.webp b/core/ui/src/main/res/drawable/bg_trending_article.webp new file mode 100644 index 0000000000..42951498d8 Binary files /dev/null and b/core/ui/src/main/res/drawable/bg_trending_article.webp differ diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/feed/FeedList.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/feed/FeedList.kt index 88ebc8dc1f..43dcd4a50e 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/feed/FeedList.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/feed/FeedList.kt @@ -423,7 +423,7 @@ private fun Charts( ) { BlockCard( modifier = modifier, - colors = TangemBlockCardColors.copy(containerColor = TangemTheme.colors.background.action), + colors = TangemBlockCardColors.copy(containerColor = TangemTheme.colors.background.primary), ) { Column(modifier = Modifier.fillMaxWidth()) { when (marketChart) {