Updated on 2026-08-14

This commit is contained in:
Tangem 2025-11-21 12:18:57 +01:00
parent ca9e191cb8
commit ba08d91950
16 changed files with 1760 additions and 12 deletions

View file

@ -0,0 +1,76 @@
package com.tangem.common.ui.news
import android.content.res.Configuration
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.currency.icon.CurrencyIcon
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
@Composable
internal fun ArticleBadge(articleTagUM: ArticleTagUM, modifier: Modifier = Modifier) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(4.dp),
modifier = modifier
.heightIn(min = 24.dp)
.background(
color = TangemTheme.colors.icon.informative.copy(alpha = 0.1f),
shape = RoundedCornerShape(8.dp),
)
.padding(horizontal = 8.dp, vertical = 4.dp),
) {
when (articleTagUM) {
is ArticleTagUM.Category -> Unit
is ArticleTagUM.Token -> {
CurrencyIcon(
state = articleTagUM.iconState,
shouldDisplayNetwork = false,
iconSize = 16.dp,
)
}
}
Text(
text = articleTagUM.title.resolveReference(),
style = TangemTheme.typography.caption1,
color = TangemTheme.colors.text.secondary,
)
}
}
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun ArticleBadgePreview() {
TangemThemePreview {
Row(horizontalArrangement = Arrangement.spacedBy(10.dp)) {
ArticleBadge(
articleTagUM = ArticleTagUM.Token(
TextReference.Str("BTC"),
iconState = CurrencyIconState.CoinIcon(
url = "",
fallbackResId = 0,
isGrayscale = false,
shouldShowCustomBadge = false,
),
),
)
ArticleBadge(
articleTagUM = ArticleTagUM.Category(TextReference.Str("Regulation")),
)
}
}
}

View file

@ -0,0 +1,256 @@
package com.tangem.common.ui.news
import android.content.res.Configuration
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawWithCache
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.R
import com.tangem.core.ui.components.SpacerH
import com.tangem.core.ui.components.block.BlockCard
import com.tangem.core.ui.extensions.TextReference
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.toImmutableList
import kotlinx.collections.immutable.toImmutableSet
import kotlinx.collections.immutable.toPersistentList
@Composable
fun ArticleCard(articleConfigUM: ArticleConfigUM, onArticleClick: () -> Unit, modifier: Modifier = Modifier) {
BlockCard(
modifier = modifier,
onClick = onArticleClick,
) {
if (articleConfigUM.isTrending) {
TrendingArticle(articleConfigUM = articleConfigUM)
} else {
DefaultArticle(articleConfigUM = articleConfigUM)
}
}
}
@Composable
private fun TrendingArticle(articleConfigUM: ArticleConfigUM) {
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)
Text(
text = articleConfigUM.title,
color = if (articleConfigUM.isViewed) {
TangemTheme.colors.text.tertiary
} else {
TangemTheme.colors.text.primary1
},
style = TangemTheme.typography.h3,
maxLines = 3,
overflow = TextOverflow.Ellipsis,
)
SpacerH(8.dp)
ArticleInfo(
score = articleConfigUM.score,
createdAt = articleConfigUM.createdAt,
)
SpacerH(32.dp)
Tags(
modifier = Modifier.padding(horizontal = 46.dp),
tags = articleConfigUM.tags.toImmutableList(),
)
}
}
@Composable
private fun DefaultArticle(articleConfigUM: ArticleConfigUM) {
Column(modifier = Modifier.padding(12.dp)) {
ArticleInfo(
score = articleConfigUM.score,
createdAt = articleConfigUM.createdAt,
)
SpacerH(8.dp)
Text(
text = articleConfigUM.title,
color = if (articleConfigUM.isViewed) {
TangemTheme.colors.text.tertiary
} else {
TangemTheme.colors.text.primary1
},
style = TangemTheme.typography.subtitle1,
maxLines = 3,
overflow = TextOverflow.Ellipsis,
)
SpacerH(16.dp)
Tags(tags = articleConfigUM.tags.toImmutableList())
}
}
@Composable
private fun ArticleInfo(score: Float, createdAt: String, modifier: Modifier = Modifier) {
val dotColor = TangemTheme.colors.text.secondary
Row(
modifier = modifier,
horizontalArrangement = Arrangement.spacedBy(4.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Image(
imageVector = ImageVector.vectorResource(R.drawable.ic_start_circle_12),
contentDescription = null,
)
Text(
text = score.toString(),
style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.secondary,
)
Spacer(
modifier = Modifier
.size(4.dp)
.drawWithCache {
val radius = size.minDimension / 2f
onDrawBehind {
drawCircle(
color = dotColor,
radius = radius,
)
}
},
)
Text(
text = createdAt,
style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.secondary,
)
}
}
@OptIn(ExperimentalLayoutApi::class)
@Composable
private fun Tags(tags: ImmutableList<ArticleTagUM>, modifier: Modifier = Modifier) {
val expandIndicator = remember {
ContextualFlowRowOverflow.expandIndicator {
val remainingItems = tags.size - shownItemCount
ArticleBadge(articleTagUM = ArticleTagUM.Category(TextReference.Str("${StringsSigns.PLUS}$remainingItems")))
}
}
ContextualFlowRow(
itemCount = tags.size,
modifier = modifier,
horizontalArrangement = Arrangement.spacedBy(4.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
maxLines = 1,
overflow = expandIndicator,
) { index ->
ArticleBadge(articleTagUM = tags[index])
}
}
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun TagsPreview() {
TangemThemePreview {
Tags(
tags = listOf(
ArticleTagUM.Category(TextReference.Str("Hype")),
ArticleTagUM.Category(TextReference.Str("BTC")),
ArticleTagUM.Category(TextReference.Str("Supply")),
ArticleTagUM.Category(TextReference.Str("Demand")),
ArticleTagUM.Category(TextReference.Str("Best rate")),
ArticleTagUM.Category(TextReference.Str("Breaking news")),
).toPersistentList(),
)
}
}
@Preview(showBackground = true, widthDp = 360, backgroundColor = 0xFF000000)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun ArticleCardsPreview() {
val tags = listOf(
ArticleTagUM.Category(TextReference.Str("Hype")),
ArticleTagUM.Category(TextReference.Str("BTC")),
ArticleTagUM.Category(TextReference.Str("Supply")),
ArticleTagUM.Category(TextReference.Str("Demand")),
ArticleTagUM.Category(TextReference.Str("Best rate")),
ArticleTagUM.Category(TextReference.Str("Breaking news")),
).toImmutableSet()
val config = ArticleConfigUM(
id = 1,
title = "Bitcoin ETFs log 4th straight day of inflows (+\$550M)",
score = 9.5f,
createdAt = "1h ago",
isTrending = true,
tags = tags,
isViewed = false,
)
TangemThemePreview {
Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
ArticleCard(
articleConfigUM = config,
onArticleClick = {},
)
SpacerH(20.dp)
ArticleCard(
articleConfigUM = config.copy(isViewed = true),
onArticleClick = {},
)
SpacerH(20.dp)
ArticleCard(
articleConfigUM = config.copy(isTrending = false),
onArticleClick = {},
)
SpacerH(20.dp)
ArticleCard(
articleConfigUM = config.copy(isTrending = false, isViewed = true),
onArticleClick = {},
)
}
}
}

View file

@ -0,0 +1,13 @@
package com.tangem.common.ui.news
import kotlinx.collections.immutable.ImmutableSet
data class ArticleConfigUM(
val id: Int,
val title: String,
val score: Float,
val createdAt: String,
val isTrending: Boolean,
val tags: ImmutableSet<ArticleTagUM>,
val isViewed: Boolean,
)

View file

@ -0,0 +1,18 @@
package com.tangem.common.ui.news
import androidx.compose.runtime.Immutable
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.core.ui.extensions.TextReference
@Immutable
sealed interface ArticleTagUM {
val title: TextReference
data class Category(override val title: TextReference) : ArticleTagUM
data class Token(
override val title: TextReference,
val iconState: CurrencyIconState,
) : ArticleTagUM
}