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

@ -217,8 +217,8 @@ private const val SCALE_FACTOR = 1.5f
private const val INNER_SHADOW_COLOR_START = 0x00000000
private const val INNER_SHADOW_COLOR_END = 0xFFFFFFFF
private const val BORDER_COLOR = 0xF0F0F0
private const val OVERLAY_DARK = 0x141414
private const val BORDER_COLOR = 0xFFF0F0F0
private const val OVERLAY_DARK = 0xFF141414
// region Previews

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="16dp"
android:height="16dp"
android:viewportWidth="16"
android:viewportHeight="16">
<path
android:pathData="M7.993,1.333C11.64,1.333 14.667,4.353 14.667,8C14.667,11.641 11.647,14.667 8,14.667C4.359,14.667 1.333,11.641 1.333,8C1.333,4.353 4.353,1.333 7.993,1.333ZM8.333,4.912C8.19,4.62 7.753,4.62 7.61,4.912L6.824,6.522C6.766,6.641 6.648,6.723 6.511,6.739L4.656,6.953C4.32,6.992 4.184,7.387 4.433,7.606L5.801,8.815C5.902,8.905 5.947,9.037 5.921,9.165L5.562,10.907C5.496,11.223 5.85,11.468 6.146,11.311L7.778,10.448C7.899,10.385 8.046,10.385 8.166,10.448L9.798,11.311C10.094,11.468 10.448,11.223 10.383,10.907L10.023,9.165C9.997,9.037 10.042,8.905 10.143,8.815L11.511,7.606C11.759,7.387 11.624,6.992 11.288,6.953L9.434,6.739C9.297,6.723 9.178,6.641 9.12,6.522L8.333,4.912Z"
android:fillColor="#656565"/>
</vector>

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

@ -1,4 +1,4 @@
package com.tangem.common.ui.news
package com.tangem.features.feed.ui.feed.components.articles
import android.content.res.Configuration
import androidx.compose.foundation.*
@ -37,7 +37,7 @@ import kotlinx.collections.immutable.toImmutableList
import kotlinx.collections.immutable.toImmutableSet
@Composable
fun ArticleCard(
internal fun ArticleCardV1(
articleConfigUM: ArticleConfigUM,
onArticleClick: () -> Unit,
modifier: Modifier = Modifier,
@ -126,7 +126,7 @@ private fun TrendingArticle(
}
@Composable
fun ShowMoreArticlesCard(modifier: Modifier = Modifier, onClick: () -> Unit) {
internal fun ShowMoreArticlesCardV1(modifier: Modifier = Modifier, onClick: () -> Unit) {
BlockCard(
modifier = modifier,
onClick = onClick,

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

@ -1,9 +1,11 @@
package com.tangem.common.ui.news
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,

View file

@ -1,4 +1,4 @@
package com.tangem.common.ui.news
package com.tangem.features.feed.ui.feed.components.articles
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column

View file

@ -1,4 +1,4 @@
package com.tangem.common.ui.news
package com.tangem.features.feed.ui.feed.components.articles
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement

View file

@ -1,4 +1,4 @@
package com.tangem.common.ui.news
package com.tangem.features.feed.ui.feed.components.articles
import androidx.compose.foundation.layout.*
import androidx.compose.runtime.Composable

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

@ -1,4 +1,4 @@
package com.tangem.common.ui.news
package com.tangem.features.feed.ui.feed.components.articles
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier

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

View file

@ -177,7 +177,7 @@ private inline fun BaseScaffoldWithMarkets(
val coroutineScope = rememberCoroutineScope()
val background = if (state.isNewMarketEnabled) {
TangemTheme.colors.background.tertiary
TangemTheme.colors2.surface.level2
} else {
TangemTheme.colors.background.primary
}