Updated on 2026-08-14

This commit is contained in:
Tangem 2026-02-25 08:08:42 +01:00
parent 4f7b1b791b
commit 4f23a66742
29 changed files with 740 additions and 128 deletions

View file

@ -1,6 +1,6 @@
package com.tangem.features.feed.model.converter
import com.tangem.common.ui.news.ArticleConfigUM
import com.tangem.features.feed.ui.feed.components.articles.ArticleConfigUM
import com.tangem.core.ui.components.label.entity.LabelLeadingContentUM
import com.tangem.core.ui.components.label.entity.LabelUM
import com.tangem.core.ui.extensions.TextReference

View file

@ -1,6 +1,6 @@
package com.tangem.features.feed.model.news.list.statemanager
import com.tangem.common.ui.news.ArticleConfigUM
import com.tangem.features.feed.ui.feed.components.articles.ArticleConfigUM
import com.tangem.domain.models.news.ShortArticle
import com.tangem.domain.news.model.NewsListBatchingContext
import com.tangem.domain.news.model.NewsListConfig

View file

@ -1,6 +1,6 @@
package com.tangem.features.feed.model.news.list.statemanager
import com.tangem.common.ui.news.ArticleConfigUM
import com.tangem.features.feed.ui.feed.components.articles.ArticleConfigUM
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.datasource.api.common.response.ApiResponseError
import com.tangem.features.feed.model.news.list.analytics.NewsListAnalyticsEvent

View file

@ -9,8 +9,8 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.tangem.common.ui.markets.MarketsListItemPlaceholder
import com.tangem.common.ui.news.DefaultLoadingArticle
import com.tangem.common.ui.news.TrendingLoadingArticle
import com.tangem.features.feed.ui.feed.components.articles.DefaultLoadingArticle
import com.tangem.features.feed.ui.feed.components.articles.TrendingLoadingArticle
import com.tangem.core.ui.components.RectangleShimmer
import com.tangem.core.ui.components.SpacerH
import com.tangem.core.ui.components.block.BlockCard

View file

@ -2,27 +2,26 @@ package com.tangem.features.feed.ui.feed.components
import androidx.compose.animation.AnimatedContent
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
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.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.layout.onFirstVisible
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.unit.dp
import com.tangem.common.ui.news.ArticleCard
import com.tangem.common.ui.news.ArticleConfigUM
import com.tangem.common.ui.news.ShowMoreArticlesCard
import com.tangem.features.feed.ui.feed.components.articles.ArticleCard
import com.tangem.features.feed.ui.feed.components.articles.ArticleConfigUM
import com.tangem.core.ui.R
import com.tangem.core.ui.components.SpacerH
import com.tangem.core.ui.components.SpacerW
@ -30,16 +29,18 @@ import com.tangem.core.ui.components.UnableToLoadData
import com.tangem.core.ui.components.block.BlockCard
import com.tangem.core.ui.components.block.TangemBlockCardColors
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.core.ui.res.TangemTheme
import com.tangem.features.feed.ui.feed.state.FeedListCallbacks
import com.tangem.features.feed.ui.feed.state.NewsUM
import com.tangem.features.feed.ui.feed.state.NewsUMState
import com.tangem.features.feed.ui.feed.state.*
private const val FOURTH_ITEM_INDEX = 3
internal const val FOURTH_ITEM_INDEX = 3
private const val GRADIENT_START = 0f
private const val GRADIENT_END = 0.5f
private val LinearGradientFirstPart = Color(0xFF635EEC)
private val LinearGradientSecondPart = Color(0xFFE05AED)
private const val LINEAR_GRADIENT_FIRST_PART_V2 = 0xFFA3A0FF
private const val LINEAR_GRADIENT_SECOND_PART_V2 = 0xFFF79DFF
private const val LINEAR_GRADIENT_FIRST_PART_V1 = 0xFF635EEC
private const val LINEAR_GRADIENT_SECOND_PART_V1 = 0xFFE05AED
@Composable
internal fun NewsBlock(feedListCallbacks: FeedListCallbacks, news: NewsUM, trendingArticle: ArticleConfigUM?) {
@ -63,7 +64,23 @@ internal fun NewsBlock(feedListCallbacks: FeedListCallbacks, news: NewsUM, trend
@Suppress("LongMethod")
@Composable
private fun NewsContentBlock(feedListCallbacks: FeedListCallbacks, news: NewsUM, trendingArticle: ArticleConfigUM?) {
val listState = rememberLazyListState()
val isRedesignEnabled = LocalRedesignEnabled.current
val gradientStart = remember(isRedesignEnabled) {
if (isRedesignEnabled) {
Color(LINEAR_GRADIENT_FIRST_PART_V2)
} else {
Color(LINEAR_GRADIENT_FIRST_PART_V1)
}
}
val gradientEnd = remember(isRedesignEnabled) {
if (isRedesignEnabled) {
Color(LINEAR_GRADIENT_SECOND_PART_V2)
} else {
Color(LINEAR_GRADIENT_SECOND_PART_V1)
}
}
Column {
Header(
title = {
@ -88,8 +105,8 @@ private fun NewsContentBlock(feedListCallbacks: FeedListCallbacks, news: NewsUM,
withStyle(
SpanStyle().copy(
brush = Brush.linearGradient(
GRADIENT_START to LinearGradientFirstPart,
GRADIENT_END to LinearGradientSecondPart,
GRADIENT_START to gradientStart,
GRADIENT_END to gradientEnd,
),
),
) {
@ -122,48 +139,19 @@ private fun NewsContentBlock(feedListCallbacks: FeedListCallbacks, news: NewsUM,
}
}
LazyRow(
verticalAlignment = Alignment.CenterVertically,
contentPadding = PaddingValues(horizontal = 16.dp),
horizontalArrangement = Arrangement.spacedBy(12.dp),
state = listState,
) {
itemsIndexed(
items = news.content,
key = { index, _ -> index },
contentType = { _, _ -> "article" },
) { index, article ->
val articleModifier = if (index == FOURTH_ITEM_INDEX) {
Modifier.onFirstVisible(
minFractionVisible = 0.5f,
callback = feedListCallbacks.onSliderScroll,
)
} else {
Modifier
}
ArticleCard(
articleConfigUM = article,
onArticleClick = { feedListCallbacks.onArticleClick(article.id) },
modifier = articleModifier
.heightIn(min = 164.dp)
.width(216.dp),
colors = TangemBlockCardColors.copy(containerColor = TangemTheme.colors.background.action),
)
}
NewsSlider(
NewsSliderConfig(
callbacks = NewsSliderCallbacks(
onOpenAllNews = { feedListCallbacks.onOpenAllNews(true) },
onSliderScroll = feedListCallbacks.onSliderScroll,
onSliderEndReached = feedListCallbacks.onSliderEndReached,
onArticleClick = feedListCallbacks.onArticleClick,
),
content = news.content,
shouldShowSeeAllNewsItem = true,
),
)
item(contentType = "show_more") {
ShowMoreArticlesCard(
modifier = Modifier
.width(216.dp)
.heightIn(min = 164.dp)
.onFirstVisible(
minFractionVisible = 0.5f,
callback = feedListCallbacks.onSliderEndReached,
),
onClick = { feedListCallbacks.onOpenAllNews(true) },
)
}
}
SpacerH(32.dp)
}
}

View file

@ -0,0 +1,15 @@
package com.tangem.features.feed.ui.feed.components
import androidx.compose.runtime.Composable
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.features.feed.ui.feed.state.NewsSliderConfig
@Composable
internal fun NewsSlider(newsSliderConfig: NewsSliderConfig) {
val isRedesignEnabled = LocalRedesignEnabled.current
if (isRedesignEnabled) {
NewsSliderV2(newsSliderConfig)
} else {
NewsSliderV1(newsSliderConfig)
}
}

View file

@ -0,0 +1,67 @@
package com.tangem.features.feed.ui.feed.components
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.onFirstVisible
import androidx.compose.ui.unit.dp
import com.tangem.features.feed.ui.feed.components.articles.ArticleCard
import com.tangem.features.feed.ui.feed.components.articles.ShowMoreArticlesCard
import com.tangem.core.ui.components.block.TangemBlockCardColors
import com.tangem.core.ui.res.TangemTheme
import com.tangem.features.feed.ui.feed.state.NewsSliderConfig
@Composable
internal fun NewsSliderV1(newsSliderConfig: NewsSliderConfig) {
LazyRow(
verticalAlignment = Alignment.CenterVertically,
contentPadding = PaddingValues(horizontal = 16.dp),
horizontalArrangement = Arrangement.spacedBy(12.dp),
state = rememberLazyListState(),
) {
itemsIndexed(
items = newsSliderConfig.content,
key = { index, _ -> index },
contentType = { _, _ -> "article" },
) { index, article ->
val articleModifier = if (index == FOURTH_ITEM_INDEX) {
Modifier.onFirstVisible(
minFractionVisible = 0.5f,
callback = newsSliderConfig.callbacks.onSliderScroll,
)
} else {
Modifier
}
ArticleCard(
articleConfigUM = article,
onArticleClick = { newsSliderConfig.callbacks.onArticleClick(article.id) },
modifier = articleModifier
.heightIn(min = 164.dp)
.width(216.dp),
colors = TangemBlockCardColors.copy(containerColor = TangemTheme.colors.background.action),
)
}
if (newsSliderConfig.shouldShowSeeAllNewsItem) {
item(contentType = "show_more") {
ShowMoreArticlesCard(
modifier = Modifier
.width(216.dp)
.heightIn(min = 164.dp)
.onFirstVisible(
minFractionVisible = 0.5f,
callback = newsSliderConfig.callbacks.onSliderEndReached,
),
onClick = newsSliderConfig.callbacks.onOpenAllNews,
)
}
}
}
}

View file

@ -0,0 +1,112 @@
package com.tangem.features.feed.ui.feed.components
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.layout.onFirstVisible
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.dp
import com.tangem.features.feed.ui.feed.components.articles.ArticleCard
import com.tangem.features.feed.ui.feed.components.articles.ShowMoreArticlesCard
import com.tangem.core.ui.extensions.conditional
import com.tangem.core.ui.res.TangemTheme
import com.tangem.features.feed.ui.feed.state.NewsSliderConfig
private val dividerSpacerWidth = 20.dp
@Suppress("MagicNumber", "LongMethod")
@Composable
internal fun NewsSliderV2(newsSliderConfig: NewsSliderConfig) {
val density = LocalDensity.current
val dividerWidthPx = with(density) { 1.dp.roundToPx() }
val spacerWidthPx = with(density) { dividerSpacerWidth.roundToPx() }
LazyRow(
verticalAlignment = Alignment.CenterVertically,
contentPadding = PaddingValues(horizontal = 16.dp),
state = rememberLazyListState(),
) {
itemsIndexed(
items = newsSliderConfig.content,
key = { index, _ -> index },
contentType = { _, _ -> "article" },
) { index, article ->
val articleModifier = Modifier.conditional(
condition = index == FOURTH_ITEM_INDEX,
modifier = {
onFirstVisible(
minFractionVisible = 0.5f,
callback = newsSliderConfig.callbacks.onSliderScroll,
)
},
)
val shouldShowDivider = newsSliderConfig.shouldShowSeeAllNewsItem ||
index < newsSliderConfig.content.size - 1
// have to use layout cause LazyRow has not fixed height and divider can not be measured
Layout(
modifier = Modifier,
content = {
ArticleCard(
articleConfigUM = article,
onArticleClick = { newsSliderConfig.callbacks.onArticleClick(article.id) },
modifier = articleModifier
.fillMaxHeight()
.width(220.dp),
)
Spacer(modifier = Modifier.width(dividerSpacerWidth))
Box(
modifier = Modifier
.width(1.dp)
.background(TangemTheme.colors2.border.neutral.secondary),
)
Spacer(modifier = Modifier.width(dividerSpacerWidth))
},
) { measurables, constraints ->
val cardPlaceable = measurables[0].measure(constraints)
val height = cardPlaceable.height
if (shouldShowDivider) {
val leftSpacer = measurables[1].measure(Constraints.fixed(spacerWidthPx, height))
val divider = measurables[2].measure(Constraints.fixed(dividerWidthPx, height))
val rightSpacer = measurables[3].measure(Constraints.fixed(spacerWidthPx, height))
val totalWidth = cardPlaceable.width + leftSpacer.width + divider.width + rightSpacer.width
layout(totalWidth, height) {
cardPlaceable.place(0, 0)
leftSpacer.place(cardPlaceable.width, 0)
divider.place(cardPlaceable.width + leftSpacer.width, 0)
rightSpacer.place(cardPlaceable.width + leftSpacer.width + divider.width, 0)
}
} else {
layout(cardPlaceable.width, height) {
cardPlaceable.place(0, 0)
}
}
}
}
if (newsSliderConfig.shouldShowSeeAllNewsItem) {
item(contentType = "show_more") {
ShowMoreArticlesCard(
modifier = Modifier
.fillMaxHeight()
.width(216.dp)
.onFirstVisible(
minFractionVisible = 0.5f,
callback = newsSliderConfig.callbacks.onSliderEndReached,
),
onClick = newsSliderConfig.callbacks.onOpenAllNews,
)
}
}
}
}

View file

@ -0,0 +1,32 @@
package com.tangem.features.feed.ui.feed.components.articles
import androidx.compose.material3.CardColors
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.tangem.core.ui.components.block.TangemBlockCardColors
import com.tangem.core.ui.res.LocalRedesignEnabled
@Composable
fun ArticleCard(
articleConfigUM: ArticleConfigUM,
onArticleClick: () -> Unit,
modifier: Modifier = Modifier,
colors: CardColors = TangemBlockCardColors,
) {
val isRedesignEnabled = LocalRedesignEnabled.current
if (isRedesignEnabled) {
ArticleCardV2(
articleConfigUM = articleConfigUM,
onArticleClick = onArticleClick,
modifier = modifier,
)
} else {
ArticleCardV1(
articleConfigUM = articleConfigUM,
onArticleClick = onArticleClick,
modifier = modifier,
colors = colors,
)
}
}

View file

@ -0,0 +1,273 @@
package com.tangem.features.feed.ui.feed.components.articles
import android.content.res.Configuration
import androidx.compose.foundation.*
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
import androidx.compose.runtime.Composable
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
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.SpacerHMax
import com.tangem.core.ui.components.block.BlockCard
import com.tangem.core.ui.components.block.TangemBlockCardColors
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 kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
import kotlinx.collections.immutable.toImmutableSet
@Composable
internal fun ArticleCardV1(
articleConfigUM: ArticleConfigUM,
onArticleClick: () -> Unit,
modifier: Modifier = Modifier,
colors: CardColors = TangemBlockCardColors,
) {
if (articleConfigUM.isTrending) {
TrendingArticle(
articleConfigUM = articleConfigUM,
modifier = modifier,
onArticleClick = onArticleClick,
colors = colors,
)
} else {
DefaultArticle(
articleConfigUM = articleConfigUM,
modifier = modifier,
onArticleClick = onArticleClick,
colors = colors,
)
}
}
@Composable
private fun TrendingArticle(
articleConfigUM: ArticleConfigUM,
onArticleClick: () -> Unit,
colors: CardColors,
modifier: Modifier = Modifier,
) {
Box(
modifier = modifier
.clip(TangemTheme.shapes.roundedCornersXMedium)
.background(colors.containerColor)
.clickable { onArticleClick() },
) {
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)
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)
ArticleInfo(
score = articleConfigUM.score,
createdAt = articleConfigUM.createdAt.resolveReference(),
)
SpacerH(32.dp)
Tags(
modifier = Modifier.padding(horizontal = 46.dp),
tags = articleConfigUM.tags.toImmutableList(),
)
}
}
}
@Composable
internal fun ShowMoreArticlesCardV1(modifier: Modifier = Modifier, onClick: () -> Unit) {
BlockCard(
modifier = modifier,
onClick = onClick,
colors = TangemBlockCardColors.copy(containerColor = TangemTheme.colors.background.action),
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.fillMaxSize()
.padding(vertical = 31.dp, horizontal = 12.dp),
) {
Image(
imageVector = ImageVector.vectorResource(R.drawable.ic_show_more_news_48),
contentDescription = stringResourceSafe(R.string.common_show_more),
)
SpacerH(16.dp)
Text(
text = stringResourceSafe(R.string.news_all_news),
style = TangemTheme.typography.subtitle1,
color = TangemTheme.colors.text.primary1,
)
Text(
text = stringResourceSafe(R.string.news_stay_in_the_loop),
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.tertiary,
)
}
}
}
@Composable
private fun DefaultArticle(
articleConfigUM: ArticleConfigUM,
onArticleClick: () -> Unit,
colors: CardColors,
modifier: Modifier = Modifier,
) {
Column(
modifier = modifier
.clip(TangemTheme.shapes.roundedCornersXMedium)
.background(colors.containerColor)
.clickable { onArticleClick() }
.padding(12.dp),
) {
ArticleInfo(
score = articleConfigUM.score,
createdAt = articleConfigUM.createdAt.resolveReference(),
)
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)
SpacerHMax()
Tags(tags = articleConfigUM.tags.toImmutableList())
}
}
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun TagsPreview() {
TangemThemePreview {
Tags(
tags = persistentListOf(
LabelUM(TextReference.Str("Hype")),
LabelUM(TextReference.Str("BTC")),
LabelUM(TextReference.Str("Supply")),
LabelUM(TextReference.Str("Demand")),
LabelUM(TextReference.Str("Best rate")),
LabelUM(TextReference.Str("Breaking news")),
),
)
}
}
@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(
LabelUM(TextReference.Str("Hype")),
LabelUM(TextReference.Str("BTC")),
LabelUM(TextReference.Str("Supply")),
LabelUM(TextReference.Str("Demand")),
LabelUM(TextReference.Str("Breaking news")),
).toImmutableSet()
val config = ArticleConfigUM(
id = 1,
title = "Bitcoin ETFs log 4th straight day of inflows (+\$550M)",
score = 9.5f,
createdAt = TextReference.Str("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,382 @@
package com.tangem.features.feed.ui.feed.components.articles
import android.content.res.Configuration
import androidx.compose.foundation.*
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Icon
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.clip
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.AndroidUiModes.UI_MODE_NIGHT_NO
import androidx.compose.ui.tooling.preview.AndroidUiModes.UI_MODE_NIGHT_YES
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.SpacerW
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.stringReference
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.LocalIsInDarkTheme
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreviewRedesign
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
import kotlinx.collections.immutable.toImmutableSet
@Composable
internal fun ArticleCardV2(
articleConfigUM: ArticleConfigUM,
onArticleClick: () -> Unit,
modifier: Modifier = Modifier,
) {
if (articleConfigUM.isTrending) {
TrendingArticle(
modifier = modifier,
articleConfigUM = articleConfigUM,
onArticleClick = onArticleClick,
)
} else {
DefaultArticle(
modifier = modifier,
articleConfigUM = articleConfigUM,
onArticleClick = onArticleClick,
)
}
}
@Composable
private fun TrendingArticle(
articleConfigUM: ArticleConfigUM,
onArticleClick: () -> Unit,
modifier: Modifier = Modifier,
) {
TrendingArticleBackground(
modifier = modifier,
onClick = onArticleClick,
) {
Column(
modifier = Modifier.padding(16.dp),
horizontalAlignment = Alignment.Start,
) {
DayAndRatingInfo(rating = stringReference("${articleConfigUM.score}"))
SpacerH(8.dp)
Text(
text = articleConfigUM.title,
color = if (articleConfigUM.isViewed) {
TangemTheme.colors2.text.neutral.tertiary
} else {
TangemTheme.colors2.text.neutral.primary
},
style = TangemTheme.typography2.headingSemibold20,
textAlign = TextAlign.Start,
)
SpacerH(18.dp)
Text(
text = articleConfigUM.createdAt.resolveReference(),
style = TangemTheme.typography2.captionSemibold12,
color = TangemTheme.colors2.text.neutral.secondary,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
)
SpacerH(18.dp)
Tags(tags = articleConfigUM.tags.toImmutableList())
}
}
}
@Composable
internal fun ShowMoreArticlesCardV2(modifier: Modifier = Modifier, onClick: () -> Unit) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = modifier
.fillMaxSize()
.clip(RoundedCornerShape(20.dp))
.background(color = TangemTheme.colors2.surface.level3)
.clickable(onClick = onClick)
.border(
width = 1.dp,
color = TangemTheme.colors2.border.neutral.primary,
shape = RoundedCornerShape(20.dp),
)
.padding(vertical = 41.dp, horizontal = 16.dp),
) {
Image(
imageVector = ImageVector.vectorResource(R.drawable.ic_show_more_news_48),
contentDescription = stringResourceSafe(R.string.common_show_more),
)
SpacerH(10.dp)
Text(
text = stringResourceSafe(R.string.news_all_news),
style = TangemTheme.typography2.bodyRegular16,
color = TangemTheme.colors2.text.neutral.primary,
)
Text(
text = stringResourceSafe(R.string.news_stay_in_the_loop),
style = TangemTheme.typography2.captionSemibold12,
color = TangemTheme.colors2.text.neutral.secondary,
)
}
}
@Composable
private fun DefaultArticle(
articleConfigUM: ArticleConfigUM,
onArticleClick: () -> Unit,
modifier: Modifier = Modifier,
) {
Column(
modifier = modifier
.background(TangemTheme.colors2.surface.level2)
.clickable { onArticleClick() }
.padding(vertical = 16.dp, horizontal = 10.dp),
) {
Row(verticalAlignment = Alignment.CenterVertically) {
RatingInfo(rating = stringReference("${articleConfigUM.score}"))
}
SpacerH(8.dp)
Text(
text = articleConfigUM.title,
color = if (articleConfigUM.isViewed) {
TangemTheme.colors2.text.neutral.tertiary
} else {
TangemTheme.colors2.text.neutral.primary
},
style = TangemTheme.typography2.bodyRegular16,
minLines = 3,
maxLines = 3,
overflow = TextOverflow.Ellipsis,
)
Text(
modifier = Modifier.padding(vertical = 20.dp),
text = articleConfigUM.createdAt.resolveReference(),
style = TangemTheme.typography2.captionSemibold12,
color = TangemTheme.colors2.text.neutral.secondary,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
)
Tags(tags = articleConfigUM.tags.toImmutableList())
}
}
@Suppress("MagicNumber")
@Composable
private fun TrendingArticleBackground(
onClick: () -> Unit,
modifier: Modifier = Modifier,
content: @Composable BoxScope.() -> Unit,
) {
val isDarkTheme = LocalIsInDarkTheme.current
val bgColor = remember(isDarkTheme) {
if (isDarkTheme) {
Color(TRENDING_NIGHT_BG)
} else {
Color(TRENDING_LIGHT_BG)
}
}
Box(
modifier = modifier
.clip(RoundedCornerShape(20.dp))
.drawBehind {
drawRect(bgColor)
val w = size.width
val h = size.height
val radiusScale = (w + h) / 2f
drawRect(
brush = Brush.radialGradient(
colors = listOf(
Color(0xFF7C16F1).copy(alpha = .8f),
Color.Transparent,
),
center = Offset(w / 2f, 2.4f * h),
radius = radiusScale * 1.57f,
),
)
drawRect(
brush = Brush.radialGradient(
colors = listOf(
Color(0xFF3360FF).copy(alpha = .7f),
Color.Transparent,
),
center = Offset(w / 2f, 2.95f * h),
radius = radiusScale * 1.9f,
),
)
drawRect(
brush = Brush.radialGradient(
colors = listOf(
Color(0xFFFF9408).copy(alpha = .45f),
Color.Transparent,
),
center = Offset(-0.38f * w, 2.1f * h),
radius = radiusScale * 1.41f,
),
)
drawRect(
brush = Brush.radialGradient(
colors = listOf(
Color(0xFFFC2424).copy(alpha = .5f),
Color.Transparent,
),
center = Offset(1.188f * w, 2.37f * h),
radius = radiusScale * 1.41f,
),
)
}
.border(width = 1.dp, color = bgColor.copy(.1f))
.clickable(onClick = onClick),
content = content,
)
}
@Composable
private fun DayAndRatingInfo(rating: TextReference, modifier: Modifier = Modifier) {
Row(
modifier = modifier,
verticalAlignment = Alignment.CenterVertically,
) {
RatingInfo(rating)
SpacerW(8.dp)
Text(
text = stringResourceSafe(R.string.feed_trending_now),
style = TangemTheme.typography2.captionSemibold12,
color = TangemTheme.colors2.text.neutral.primary,
)
}
}
@Composable
private fun RatingInfo(rating: TextReference) {
Icon(
imageVector = ImageVector.vectorResource(R.drawable.ic_wrapped_circle_star_16),
tint = TangemTheme.colors2.fill.status.attention,
contentDescription = null,
)
SpacerW(2.dp)
Text(
text = rating.resolveReference(),
color = TangemTheme.colors2.text.status.attention,
style = TangemTheme.typography2.captionSemibold12,
)
}
private const val TRENDING_NIGHT_BG = 0xFF1F1F1F
private const val TRENDING_LIGHT_BG = 0xFFFFFFFF
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun TagsPreview() {
TangemThemePreviewRedesign {
Tags(
tags = persistentListOf(
LabelUM(TextReference.Str("Hype")),
LabelUM(TextReference.Str("BTC")),
LabelUM(TextReference.Str("Supply")),
LabelUM(TextReference.Str("Demand")),
LabelUM(TextReference.Str("Best rate")),
LabelUM(TextReference.Str("Breaking news")),
),
)
}
}
@Preview(widthDp = 360, uiMode = UI_MODE_NIGHT_YES)
@Preview(widthDp = 360, uiMode = UI_MODE_NIGHT_NO)
@Composable
private fun ArticleCardsPreview() {
val tags = listOf(
LabelUM(TextReference.Str("Hype")),
LabelUM(TextReference.Str("BTC")),
LabelUM(TextReference.Str("Supply")),
LabelUM(TextReference.Str("Demand")),
LabelUM(TextReference.Str("Breaking news")),
).toImmutableSet()
val config = ArticleConfigUM(
id = 1,
title = "Bitcoin ETFs log 4th straight day of inflows (+\$550M)",
score = 9.5f,
createdAt = TextReference.Str("1h ago"),
isTrending = true,
tags = tags,
isViewed = false,
)
TangemThemePreviewRedesign {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp)
.verticalScroll(rememberScrollState()),
) {
ArticleCardV2(
articleConfigUM = config,
onArticleClick = {},
)
SpacerH(20.dp)
ArticleCardV2(
articleConfigUM = config.copy(isViewed = true),
onArticleClick = {},
)
SpacerH(20.dp)
ArticleCardV2(
articleConfigUM = config.copy(isTrending = false),
onArticleClick = {},
)
SpacerH(20.dp)
ArticleCardV2(
articleConfigUM = config.copy(isTrending = false, isViewed = true),
onArticleClick = {},
)
SpacerH(20.dp)
ShowMoreArticlesCardV2(onClick = {})
}
}
}

View file

@ -0,0 +1,17 @@
package com.tangem.features.feed.ui.feed.components.articles
import androidx.compose.runtime.Immutable
import com.tangem.core.ui.components.label.entity.LabelUM
import com.tangem.core.ui.extensions.TextReference
import kotlinx.collections.immutable.ImmutableSet
@Immutable
data class ArticleConfigUM(
val id: Int,
val title: String,
val score: Float,
val createdAt: TextReference,
val isTrending: Boolean,
val tags: ImmutableSet<LabelUM>,
val isViewed: Boolean,
)

View file

@ -0,0 +1,55 @@
package com.tangem.features.feed.ui.feed.components.articles
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
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.res.TangemTheme
import kotlinx.collections.immutable.ImmutableList
@OptIn(ExperimentalLayoutApi::class)
@Composable
fun ArticleHeader(
title: String,
createdAt: String,
score: Float,
tags: ImmutableList<LabelUM>,
modifier: Modifier = Modifier,
) {
Column(modifier = modifier) {
ArticleInfo(
score = score,
createdAt = createdAt,
)
Spacer(modifier = Modifier.height(12.dp))
Text(
text = title,
style = TangemTheme.typography.h2,
color = TangemTheme.colors.text.primary1,
)
if (tags.isNotEmpty()) {
Spacer(modifier = Modifier.height(20.dp))
FlowRow(
horizontalArrangement = Arrangement.spacedBy(4.dp),
verticalArrangement = Arrangement.spacedBy(4.dp),
) {
tags.forEach { tag ->
Label(
state = tag,
)
}
}
}
}
}

View file

@ -0,0 +1,61 @@
package com.tangem.features.feed.ui.feed.components.articles
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
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.unit.dp
import com.tangem.core.ui.R
import com.tangem.core.ui.res.TangemTheme
@Composable
internal 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,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
color = TangemTheme.colors.text.secondary,
)
}
}

View file

@ -0,0 +1,64 @@
package com.tangem.features.feed.ui.feed.components.articles
import androidx.compose.foundation.layout.*
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.components.RectangleShimmer
import com.tangem.core.ui.components.SpacerH
import com.tangem.core.ui.components.block.BlockCard
import com.tangem.core.ui.components.block.TangemBlockCardColors
import com.tangem.core.ui.res.TangemTheme
@Composable
fun TrendingLoadingArticle(modifier: Modifier = Modifier) {
BlockCard(
modifier = modifier,
colors = TangemBlockCardColors.copy(containerColor = TangemTheme.colors.background.action),
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 24.dp, horizontal = 16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
RectangleShimmer(modifier = Modifier.size(width = 96.dp, height = 24.dp), radius = 8.dp)
SpacerH(12.dp)
RectangleShimmer(modifier = Modifier.size(width = 285.dp, height = 18.dp), radius = 4.dp)
SpacerH(6.dp)
RectangleShimmer(modifier = Modifier.size(width = 190.dp, height = 18.dp), radius = 4.dp)
SpacerH(14.dp)
RectangleShimmer(modifier = Modifier.size(width = 110.dp, height = 18.dp), radius = 4.dp)
SpacerH(32.dp)
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(4.dp)) {
RectangleShimmer(modifier = Modifier.size(width = 64.dp, height = 24.dp), radius = 8.dp)
RectangleShimmer(modifier = Modifier.size(width = 64.dp, height = 24.dp), radius = 8.dp)
RectangleShimmer(modifier = Modifier.size(width = 64.dp, height = 24.dp), radius = 8.dp)
}
}
}
}
@Composable
fun DefaultLoadingArticle(modifier: Modifier = Modifier) {
BlockCard(
modifier = modifier,
colors = TangemBlockCardColors.copy(containerColor = TangemTheme.colors.background.action),
) {
Column(modifier = Modifier.padding(12.dp)) {
RectangleShimmer(modifier = Modifier.size(width = 110.dp, height = 16.dp), radius = 4.dp)
SpacerH(12.dp)
RectangleShimmer(modifier = Modifier.size(width = 142.dp, height = 18.dp), radius = 4.dp)
SpacerH(6.dp)
RectangleShimmer(modifier = Modifier.size(width = 176.dp, height = 18.dp), radius = 4.dp)
SpacerH(6.dp)
RectangleShimmer(modifier = Modifier.size(width = 120.dp, height = 18.dp), radius = 4.dp)
SpacerH(16.dp)
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(4.dp)) {
RectangleShimmer(modifier = Modifier.size(width = 72.dp, height = 24.dp), radius = 8.dp)
RectangleShimmer(modifier = Modifier.size(width = 72.dp, height = 24.dp), radius = 8.dp)
}
}
}
}

View file

@ -0,0 +1,15 @@
package com.tangem.features.feed.ui.feed.components.articles
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.tangem.core.ui.res.LocalRedesignEnabled
@Composable
fun ShowMoreArticlesCard(modifier: Modifier = Modifier, onClick: () -> Unit) {
val isRedesignEnabled: Boolean = LocalRedesignEnabled.current
if (isRedesignEnabled) {
ShowMoreArticlesCardV2(modifier = modifier, onClick = onClick)
} else {
ShowMoreArticlesCardV1(modifier = modifier, onClick = onClick)
}
}

View file

@ -0,0 +1,128 @@
package com.tangem.features.feed.ui.feed.components.articles
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<LabelUM>, 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<Placeable>,
): 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<Placeable>, 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 }

View file

@ -2,7 +2,7 @@ package com.tangem.features.feed.ui.feed.preview
import com.tangem.common.ui.charts.state.MarketChartRawData
import com.tangem.common.ui.markets.models.MarketsListItemUM
import com.tangem.common.ui.news.ArticleConfigUM
import com.tangem.features.feed.ui.feed.components.articles.ArticleConfigUM
import com.tangem.core.ui.R
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.core.ui.components.label.entity.LabelLeadingContentUM

View file

@ -2,7 +2,7 @@ package com.tangem.features.feed.ui.feed.state
import androidx.compose.runtime.Immutable
import com.tangem.common.ui.markets.models.MarketsListItemUM
import com.tangem.common.ui.news.ArticleConfigUM
import com.tangem.features.feed.ui.feed.components.articles.ArticleConfigUM
import com.tangem.core.ui.extensions.TextReference
import com.tangem.features.feed.model.market.list.state.SortByTypeUM
import com.tangem.features.feed.ui.earn.state.EarnListUM

View file

@ -0,0 +1,20 @@
package com.tangem.features.feed.ui.feed.state
import androidx.compose.runtime.Immutable
import com.tangem.features.feed.ui.feed.components.articles.ArticleConfigUM
import kotlinx.collections.immutable.ImmutableList
@Immutable
internal data class NewsSliderConfig(
val shouldShowSeeAllNewsItem: Boolean,
val content: ImmutableList<ArticleConfigUM>,
val callbacks: NewsSliderCallbacks,
)
@Immutable
internal data class NewsSliderCallbacks(
val onOpenAllNews: () -> Unit,
val onSliderScroll: () -> Unit,
val onSliderEndReached: () -> Unit,
val onArticleClick: (id: Int) -> Unit,
)

View file

@ -2,30 +2,24 @@ package com.tangem.features.feed.ui.market.detailed.components
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.onFirstVisible
import androidx.compose.ui.unit.dp
import com.tangem.common.ui.news.ArticleCard
import com.tangem.core.ui.components.UnableToLoadData
import com.tangem.core.ui.components.block.TangemBlockCardColors
import com.tangem.core.ui.components.items.DescriptionItem
import com.tangem.core.ui.components.items.DescriptionPlaceholder
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
import com.tangem.features.feed.impl.R
import com.tangem.features.feed.ui.feed.components.NewsSlider
import com.tangem.features.feed.ui.feed.state.NewsSliderCallbacks
import com.tangem.features.feed.ui.feed.state.NewsSliderConfig
import com.tangem.features.feed.ui.market.detailed.state.MarketsTokenDetailsUM
import com.tangem.features.feed.ui.market.detailed.state.MarketsTokenDetailsUM.RelatedNews
private const val FOURTH_ITEM_INDEX = 3
@Suppress("CanBeNonNullable") // TODO will be removed after [REDACTED_JIRA]
internal fun LazyListScope.tokenMarketDetailsBody(
state: MarketsTokenDetailsUM.Body,
@ -207,13 +201,6 @@ private fun LazyListScope.loadingInfoBlocks() {
private fun LazyListScope.relatedNews(relatedNews: RelatedNews) {
item("related-news") {
val listState = rememberLazyListState()
val articlesReadStatus = remember(relatedNews.articles) {
relatedNews.articles.map { it.isViewed }
}
LaunchedEffect(articlesReadStatus) {
listState.requestScrollToItem(0)
}
Column(
modifier = Modifier
.fillMaxWidth()
@ -231,35 +218,18 @@ private fun LazyListScope.relatedNews(relatedNews: RelatedNews) {
color = TangemTheme.colors.text.primary1,
)
LazyRow(
verticalAlignment = Alignment.CenterVertically,
contentPadding = PaddingValues(horizontal = 16.dp),
horizontalArrangement = Arrangement.spacedBy(12.dp),
state = listState,
) {
itemsIndexed(
items = relatedNews.articles,
key = { index, article -> article.id },
) { index, article ->
val articleModifier = if (index == FOURTH_ITEM_INDEX) {
Modifier.onFirstVisible(
minFractionVisible = 0.5f,
callback = relatedNews.onScroll,
)
} else {
Modifier
}
ArticleCard(
articleConfigUM = article,
onArticleClick = { relatedNews.onArticledClicked(article.id) },
modifier = articleModifier
.heightIn(min = 164.dp)
.width(216.dp),
colors = TangemBlockCardColors.copy(containerColor = TangemTheme.colors.background.action),
)
}
}
NewsSlider(
NewsSliderConfig(
callbacks = NewsSliderCallbacks(
onOpenAllNews = {}, // not applicable here
onSliderScroll = relatedNews.onScroll,
onSliderEndReached = {}, // not applicable here
onArticleClick = relatedNews.onArticledClicked,
),
content = relatedNews.articles,
shouldShowSeeAllNewsItem = false,
),
)
}
}
}

View file

@ -2,7 +2,7 @@ package com.tangem.features.feed.ui.market.detailed.state
import androidx.compose.runtime.Immutable
import com.tangem.common.ui.charts.state.MarketChartDataProducer
import com.tangem.common.ui.news.ArticleConfigUM
import com.tangem.features.feed.ui.feed.components.articles.ArticleConfigUM
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.components.marketprice.PriceChangeType
import com.tangem.core.ui.event.StateEvent

View file

@ -37,7 +37,7 @@ import androidx.compose.ui.unit.dp
import coil.compose.SubcomposeAsyncImage
import coil.request.CachePolicy
import coil.request.ImageRequest
import com.tangem.common.ui.news.ArticleHeader
import com.tangem.features.feed.ui.feed.components.articles.ArticleHeader
import com.tangem.core.ui.R
import com.tangem.core.ui.components.*
import com.tangem.core.ui.components.buttons.common.TangemButtonSize

View file

@ -12,7 +12,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.tangem.common.ui.news.ArticleConfigUM
import com.tangem.features.feed.ui.feed.components.articles.ArticleConfigUM
import com.tangem.core.ui.components.SpacerH
import com.tangem.core.ui.components.chip.Chip
import com.tangem.core.ui.components.chip.entity.ChipUM

View file

@ -16,9 +16,9 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.tangem.common.ui.news.ArticleCard
import com.tangem.common.ui.news.ArticleConfigUM
import com.tangem.common.ui.news.DefaultLoadingArticle
import com.tangem.features.feed.ui.feed.components.articles.ArticleCard
import com.tangem.features.feed.ui.feed.components.articles.ArticleConfigUM
import com.tangem.features.feed.ui.feed.components.articles.DefaultLoadingArticle
import com.tangem.core.ui.components.SpacerH
import com.tangem.core.ui.components.UnableToLoadData
import com.tangem.core.ui.components.block.TangemBlockCardColors

View file

@ -1,7 +1,7 @@
package com.tangem.features.feed.ui.news.list.state
import androidx.compose.runtime.Immutable
import com.tangem.common.ui.news.ArticleConfigUM
import com.tangem.features.feed.ui.feed.components.articles.ArticleConfigUM
import com.tangem.core.ui.components.chip.entity.ChipUM
import kotlinx.collections.immutable.ImmutableList