Updated on 2026-08-14
This commit is contained in:
parent
0d4fd5874e
commit
8cd088188f
20 changed files with 1119 additions and 489 deletions
|
|
@ -1,12 +1,12 @@
|
|||
package com.tangem.features.feed.model.converter
|
||||
|
||||
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
|
||||
import com.tangem.data.common.currency.getTokenIconUrlFromDefaultHost
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.news.ShortArticle
|
||||
import com.tangem.features.feed.ui.feed.components.articles.ArticleConfigUM
|
||||
import com.tangem.features.feed.ui.utils.mapFormattedDate
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
|
@ -16,7 +16,7 @@ import kotlinx.collections.immutable.toPersistentList
|
|||
import kotlinx.collections.immutable.toPersistentSet
|
||||
|
||||
internal class ShortArticleToArticleConfigUMConverter(
|
||||
private val isTrending: Provider<Boolean>,
|
||||
private val isTrending: Provider<Boolean>?,
|
||||
) : Converter<List<ShortArticle>, ImmutableList<ArticleConfigUM>> {
|
||||
|
||||
override fun convert(value: List<ShortArticle>): ImmutableList<ArticleConfigUM> {
|
||||
|
|
@ -25,7 +25,7 @@ internal class ShortArticleToArticleConfigUMConverter(
|
|||
id = shortArticle.id,
|
||||
title = shortArticle.title,
|
||||
score = shortArticle.score,
|
||||
isTrending = isTrending(),
|
||||
isTrending = isTrending?.invoke() ?: shortArticle.isTrending,
|
||||
tags = buildArticleTags(shortArticle),
|
||||
createdAt = mapFormattedDate(shortArticle.createdAt),
|
||||
isViewed = shortArticle.viewed,
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ internal class NewsDetailsConverter(
|
|||
newsUrl = value.newsUrl,
|
||||
relatedTokens = value.relatedTokens.toImmutableList(),
|
||||
isLiked = value.isLiked,
|
||||
isTrending = value.isTrending,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
package com.tangem.features.feed.model.news.list.statemanager
|
||||
|
||||
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
|
||||
import com.tangem.domain.news.usecase.GetNewsListBatchFlowUseCase
|
||||
import com.tangem.features.feed.model.converter.ShortArticleToArticleConfigUMConverter
|
||||
import com.tangem.features.feed.model.converter.distinctBatchesContent
|
||||
import com.tangem.features.feed.ui.feed.components.articles.ArticleConfigUM
|
||||
import com.tangem.pagination.Batch
|
||||
import com.tangem.pagination.BatchAction
|
||||
import com.tangem.pagination.PaginationStatus
|
||||
|
|
@ -30,7 +30,7 @@ internal open class NewsListBatchFlowManager(
|
|||
) {
|
||||
private val actionsFlow = MutableSharedFlow<BatchAction<Int, NewsListConfig, Nothing>>()
|
||||
private val converter by lazy {
|
||||
ShortArticleToArticleConfigUMConverter(isTrending = Provider { false })
|
||||
ShortArticleToArticleConfigUMConverter(null)
|
||||
}
|
||||
|
||||
private val batchFlow = getNewsListBatchFlowUseCase(
|
||||
|
|
|
|||
|
|
@ -10,12 +10,8 @@ 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.core.ui.components.HorizontalFadeWithBlur
|
||||
import com.tangem.core.ui.components.block.TangemBlockCardColors
|
||||
import com.tangem.core.ui.components.haze.hazeSourceTangem
|
||||
import com.tangem.core.ui.extensions.conditionalCompose
|
||||
import com.tangem.core.ui.res.LocalMainBottomSheetColor
|
||||
import com.tangem.core.ui.res.LocalRedesignEnabled
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.features.feed.ui.feed.components.articles.ArticleCard
|
||||
import com.tangem.features.feed.ui.feed.components.articles.ShowMoreArticlesCard
|
||||
|
|
@ -25,72 +21,50 @@ import com.tangem.features.feed.ui.feed.state.NewsSliderConfig
|
|||
@Composable
|
||||
internal fun NewsSlider(newsSliderConfig: NewsSliderConfig) {
|
||||
val background = LocalMainBottomSheetColor.current.value
|
||||
val isRedesignEnabled = LocalRedesignEnabled.current
|
||||
Box(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
LazyRow(
|
||||
modifier = Modifier.background(color = background),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
contentPadding = PaddingValues(horizontal = 16.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||
state = rememberLazyListState(),
|
||||
) {
|
||||
LazyRow(
|
||||
modifier = Modifier
|
||||
.conditionalCompose(
|
||||
condition = isRedesignEnabled,
|
||||
modifier = {
|
||||
hazeSourceTangem(zIndex = -1f)
|
||||
},
|
||||
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,
|
||||
)
|
||||
.background(color = background),
|
||||
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
|
||||
} else {
|
||||
Modifier
|
||||
}
|
||||
ArticleCard(
|
||||
articleConfigUM = article,
|
||||
onArticleClick = { newsSliderConfig.callbacks.onArticleClick(article.id) },
|
||||
modifier = articleModifier
|
||||
.width(228.dp)
|
||||
.heightIn(min = 172.dp)
|
||||
.fillMaxHeight(),
|
||||
colors = TangemBlockCardColors.copy(containerColor = TangemTheme.colors.background.action),
|
||||
)
|
||||
}
|
||||
|
||||
if (newsSliderConfig.shouldShowSeeAllNewsItem) {
|
||||
item(contentType = "show_more") {
|
||||
ShowMoreArticlesCard(
|
||||
modifier = Modifier
|
||||
.width(228.dp)
|
||||
.heightIn(min = 172.dp)
|
||||
.fillMaxHeight(),
|
||||
colors = TangemBlockCardColors.copy(containerColor = TangemTheme.colors.background.action),
|
||||
.onFirstVisible(
|
||||
minFractionVisible = 0.5f,
|
||||
callback = newsSliderConfig.callbacks.onSliderEndReached,
|
||||
),
|
||||
onClick = newsSliderConfig.callbacks.onOpenAllNews,
|
||||
)
|
||||
}
|
||||
|
||||
if (newsSliderConfig.shouldShowSeeAllNewsItem) {
|
||||
item(contentType = "show_more") {
|
||||
ShowMoreArticlesCard(
|
||||
modifier = Modifier
|
||||
.width(228.dp)
|
||||
.heightIn(min = 172.dp)
|
||||
.onFirstVisible(
|
||||
minFractionVisible = 0.5f,
|
||||
callback = newsSliderConfig.callbacks.onSliderEndReached,
|
||||
),
|
||||
onClick = newsSliderConfig.callbacks.onOpenAllNews,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isRedesignEnabled) {
|
||||
HorizontalFadeWithBlur(
|
||||
modifier = Modifier
|
||||
.align(Alignment.TopEnd)
|
||||
.heightIn(min = 172.dp)
|
||||
.fillMaxHeight()
|
||||
.width(100.dp),
|
||||
backgroundColor = background,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,23 +1,69 @@
|
|||
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.foundation.layout.*
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.VerticalDivider
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
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.SpacerW
|
||||
import com.tangem.core.ui.components.label.Label
|
||||
import com.tangem.core.ui.components.label.entity.LabelLeadingContentUM
|
||||
import com.tangem.core.ui.components.label.entity.LabelUM
|
||||
import com.tangem.core.ui.ds.badge.TangemBadge
|
||||
import com.tangem.core.ui.ds.badge.TangemBadgeColor
|
||||
import com.tangem.core.ui.ds.badge.TangemBadgeIconPosition
|
||||
import com.tangem.core.ui.ds.badge.TangemBadgeShape
|
||||
import com.tangem.core.ui.ds.badge.TangemBadgeSize
|
||||
import com.tangem.core.ui.ds.badge.TangemBadgeType
|
||||
import com.tangem.core.ui.ds.image.TangemIconUM
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.LocalRedesignEnabled
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.res.TangemThemePreviewRedesign
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
@Composable
|
||||
fun ArticleHeader(
|
||||
isTrending: Boolean,
|
||||
title: String,
|
||||
createdAt: String,
|
||||
score: Float,
|
||||
tags: ImmutableList<LabelUM>,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
if (LocalRedesignEnabled.current) {
|
||||
ArticleHeaderV2(
|
||||
isTrending = isTrending,
|
||||
title = title,
|
||||
createdAt = createdAt,
|
||||
score = score,
|
||||
tags = tags,
|
||||
modifier = modifier,
|
||||
)
|
||||
} else {
|
||||
ArticleHeaderV1(
|
||||
title = title,
|
||||
createdAt = createdAt,
|
||||
score = score,
|
||||
tags = tags,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
@Composable
|
||||
private fun ArticleHeaderV1(
|
||||
title: String,
|
||||
createdAt: String,
|
||||
score: Float,
|
||||
|
|
@ -52,4 +98,161 @@ fun ArticleHeader(
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
@Composable
|
||||
private fun ArticleHeaderV2(
|
||||
isTrending: Boolean,
|
||||
title: String,
|
||||
createdAt: String,
|
||||
score: Float,
|
||||
tags: ImmutableList<LabelUM>,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(modifier = modifier) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.heightIn(min = 66.dp)
|
||||
.padding(top = 16.dp),
|
||||
verticalAlignment = Alignment.Bottom,
|
||||
) {
|
||||
DateBlock(
|
||||
modifier = Modifier.weight(1f),
|
||||
createdAt = createdAt,
|
||||
)
|
||||
SpacerW(30.dp)
|
||||
VerticalDivider(
|
||||
modifier = Modifier
|
||||
.height(46.dp)
|
||||
.padding(bottom = 4.dp),
|
||||
color = TangemTheme.colors2.border.neutral.primary,
|
||||
)
|
||||
SpacerW(30.dp)
|
||||
ScoreBlock(
|
||||
modifier = Modifier.weight(1f),
|
||||
score = score,
|
||||
isTrending = isTrending,
|
||||
)
|
||||
}
|
||||
|
||||
Text(
|
||||
modifier = Modifier.padding(vertical = 36.dp),
|
||||
text = title,
|
||||
style = TangemTheme.typography2.headingBold34,
|
||||
color = TangemTheme.colors2.text.neutral.primary,
|
||||
)
|
||||
|
||||
if (tags.isNotEmpty()) {
|
||||
Spacer(modifier = Modifier.height(20.dp))
|
||||
FlowRow(
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||
) {
|
||||
tags.forEach { tag ->
|
||||
TangemBadge(
|
||||
text = tag.text,
|
||||
tangemIconUM = when (val content = tag.leadingContent) {
|
||||
LabelLeadingContentUM.None -> null
|
||||
is LabelLeadingContentUM.Token -> TangemIconUM.Url(content.iconUrl)
|
||||
},
|
||||
shape = TangemBadgeShape.Rounded,
|
||||
size = TangemBadgeSize.X9,
|
||||
type = TangemBadgeType.Tinted,
|
||||
color = TangemBadgeColor.Gray,
|
||||
iconPosition = when (tag.leadingContent) {
|
||||
LabelLeadingContentUM.None -> TangemBadgeIconPosition.None
|
||||
is LabelLeadingContentUM.Token -> TangemBadgeIconPosition.Start
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ScoreBlock(score: Float, isTrending: Boolean, modifier: Modifier = Modifier) {
|
||||
Column(
|
||||
modifier = modifier,
|
||||
verticalArrangement = Arrangement.spacedBy(10.dp),
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(10.dp),
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier.size(20.dp),
|
||||
imageVector = ImageVector.vectorResource(R.drawable.ic_wrapped_circle_star_16),
|
||||
tint = if (isTrending) {
|
||||
TangemTheme.colors2.fill.status.attention
|
||||
} else {
|
||||
TangemTheme.colors2.graphic.neutral.primary
|
||||
},
|
||||
contentDescription = null,
|
||||
)
|
||||
Text(
|
||||
text = score.toString(),
|
||||
style = TangemTheme.typography2.bodyRegular16,
|
||||
color = if (isTrending) {
|
||||
TangemTheme.colors2.text.status.attention
|
||||
} else {
|
||||
TangemTheme.colors2.text.neutral.primary
|
||||
},
|
||||
)
|
||||
}
|
||||
Text(
|
||||
text = stringResourceSafe(R.string.news_trending_score),
|
||||
style = TangemTheme.typography2.captionSemibold13,
|
||||
color = TangemTheme.colors2.text.neutral.tertiary,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun DateBlock(createdAt: String, modifier: Modifier = Modifier) {
|
||||
Column(
|
||||
modifier = modifier,
|
||||
verticalArrangement = Arrangement.spacedBy(10.dp),
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier.size(20.dp),
|
||||
imageVector = ImageVector.vectorResource(R.drawable.ic_calendar_20),
|
||||
tint = TangemTheme.colors2.fill.neutral.primary,
|
||||
contentDescription = null,
|
||||
)
|
||||
Text(
|
||||
text = createdAt,
|
||||
style = TangemTheme.typography2.captionSemibold13,
|
||||
color = TangemTheme.colors2.text.neutral.tertiary,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Composable
|
||||
private fun ArticleHeaderPreviewV1() {
|
||||
TangemThemePreview {
|
||||
ArticleHeader(
|
||||
title = "Something going good!",
|
||||
createdAt = "1 hour ago",
|
||||
score = 5.5f,
|
||||
tags = persistentListOf(),
|
||||
isTrending = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Composable
|
||||
private fun ArticleHeaderPreviewV2() {
|
||||
TangemThemePreviewRedesign {
|
||||
ArticleHeader(
|
||||
title = "Something going good!",
|
||||
createdAt = "1 hour ago",
|
||||
score = 5.5f,
|
||||
tags = persistentListOf(),
|
||||
isTrending = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -3,53 +3,25 @@ package com.tangem.features.feed.ui.news.details
|
|||
import android.content.res.Configuration
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.LazyRow
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.foundation.pager.HorizontalPager
|
||||
import androidx.compose.foundation.pager.rememberPagerState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.VerticalDivider
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.snapshotFlow
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import coil.compose.SubcomposeAsyncImage
|
||||
import coil.request.CachePolicy
|
||||
import coil.request.ImageRequest
|
||||
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
|
||||
import com.tangem.core.ui.components.UnableToLoadData
|
||||
import com.tangem.core.ui.components.haze.hazeSourceTangem
|
||||
import com.tangem.core.ui.components.pager.PagerIndicator
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.LocalMainBottomSheetColor
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.extensions.conditionalCompose
|
||||
import com.tangem.core.ui.res.*
|
||||
import com.tangem.features.feed.ui.news.details.components.ArticleDetail
|
||||
import com.tangem.features.feed.ui.news.details.components.NewsDetailsPlaceholder
|
||||
import com.tangem.features.feed.ui.news.details.components.RelatedTokensBlock
|
||||
import com.tangem.features.feed.ui.news.details.state.*
|
||||
import com.tangem.features.feed.ui.news.details.state.ArticlesStateUM
|
||||
import com.tangem.features.feed.ui.news.details.state.MockArticlesFactory
|
||||
import com.tangem.features.feed.ui.news.details.state.NewsDetailsUM
|
||||
|
||||
@Composable
|
||||
internal fun NewsDetailsContent(state: NewsDetailsUM, modifier: Modifier = Modifier) {
|
||||
|
|
@ -86,6 +58,7 @@ internal fun NewsDetailsContent(state: NewsDetailsUM, modifier: Modifier = Modif
|
|||
|
||||
@Composable
|
||||
private fun Content(state: NewsDetailsUM, background: Color) {
|
||||
val isRedesignEnabled = LocalRedesignEnabled.current
|
||||
val pagerState = rememberPagerState(
|
||||
initialPage = state.selectedArticleIndex,
|
||||
pageCount = { state.articles.size },
|
||||
|
|
@ -109,6 +82,12 @@ private fun Content(state: NewsDetailsUM, background: Color) {
|
|||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.conditionalCompose(
|
||||
condition = isRedesignEnabled,
|
||||
modifier = {
|
||||
hazeSourceTangem(zIndex = 1f)
|
||||
},
|
||||
)
|
||||
.background(background),
|
||||
) {
|
||||
Box(modifier = Modifier.fillMaxSize()) {
|
||||
|
|
@ -138,242 +117,6 @@ private fun Content(state: NewsDetailsUM, background: Color) {
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
private fun ArticleDetail(
|
||||
article: ArticleUM,
|
||||
onLikeClick: () -> Unit,
|
||||
relatedTokensUM: RelatedTokensUM,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val hapticFeedback = LocalHapticFeedback.current
|
||||
val density = LocalDensity.current
|
||||
val background = LocalMainBottomSheetColor.current.value
|
||||
val pagerHeight = 32.dp
|
||||
val contentPadding = pagerHeight + 56.dp + with(density) {
|
||||
WindowInsets.navigationBars.getBottom(this).div(this.density)
|
||||
}.dp
|
||||
|
||||
Box(modifier = modifier) {
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentPadding = PaddingValues(bottom = contentPadding),
|
||||
) {
|
||||
item("content") {
|
||||
ArticleHeader(
|
||||
title = article.title,
|
||||
createdAt = article.createdAt.resolveReference(),
|
||||
score = article.score,
|
||||
tags = article.tags,
|
||||
modifier = Modifier
|
||||
.padding(top = 16.dp)
|
||||
.padding(horizontal = 16.dp),
|
||||
)
|
||||
|
||||
if (article.shortContent.isNotEmpty()) {
|
||||
QuickRecap(
|
||||
content = article.shortContent,
|
||||
modifier = Modifier
|
||||
.padding(top = 32.dp)
|
||||
.padding(horizontal = 16.dp),
|
||||
)
|
||||
}
|
||||
|
||||
Text(
|
||||
text = article.content,
|
||||
style = TangemTheme.typography.body1,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
modifier = Modifier
|
||||
.padding(top = 16.dp)
|
||||
.padding(horizontal = 16.dp),
|
||||
)
|
||||
|
||||
SpacerH(24.dp)
|
||||
|
||||
SecondaryButtonIconStart(
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
iconResId = if (article.isLiked) {
|
||||
R.drawable.ic_heart_filled_20
|
||||
} else {
|
||||
R.drawable.ic_heart_20
|
||||
},
|
||||
iconTint = Color.Unspecified,
|
||||
text = stringResourceSafe(R.string.news_like),
|
||||
size = TangemButtonSize.RoundedAction,
|
||||
onClick = {
|
||||
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||
onLikeClick()
|
||||
},
|
||||
)
|
||||
|
||||
RelatedTokensBlock(
|
||||
relatedTokensUM = relatedTokensUM,
|
||||
onItemClick = when (relatedTokensUM) {
|
||||
is RelatedTokensUM.Content -> relatedTokensUM.onTokenClick
|
||||
else -> null
|
||||
},
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
)
|
||||
|
||||
if (article.relatedArticles.isNotEmpty()) {
|
||||
SpacerH(24.dp)
|
||||
Row(
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
Text(
|
||||
text = stringResourceSafe(R.string.news_sources),
|
||||
style = TangemTheme.typography.h3,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
Text(
|
||||
text = "${article.relatedArticles.size}",
|
||||
style = TangemTheme.typography.h3,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (article.relatedArticles.isNotEmpty()) {
|
||||
item("relatedArticles") {
|
||||
LazyRow(
|
||||
modifier = Modifier.padding(vertical = 12.dp),
|
||||
state = rememberLazyListState(),
|
||||
contentPadding = PaddingValues(horizontal = 16.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||
) {
|
||||
items(
|
||||
items = article.relatedArticles,
|
||||
key = RelatedArticleUM::id,
|
||||
) { article ->
|
||||
RelatedNewsItem(
|
||||
relatedArticle = article,
|
||||
modifier = Modifier.fillParentMaxHeight(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
BottomFade(
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomCenter),
|
||||
backgroundColor = background,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun QuickRecap(content: String, modifier: Modifier = Modifier) {
|
||||
Box(
|
||||
modifier = modifier.height(IntrinsicSize.Min),
|
||||
) {
|
||||
VerticalDivider(
|
||||
modifier = Modifier.fillMaxHeight(),
|
||||
thickness = 2.dp,
|
||||
color = TangemTheme.colors.stroke.primary,
|
||||
)
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(start = 16.dp),
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_quick_recap_16),
|
||||
contentDescription = null,
|
||||
tint = TangemTheme.colors.icon.accent,
|
||||
modifier = Modifier.size(20.dp),
|
||||
)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Text(
|
||||
text = stringResourceSafe(R.string.news_quick_recap),
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
color = TangemTheme.colors.text.accent,
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
Text(
|
||||
text = content,
|
||||
style = TangemTheme.typography.body1,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun RelatedNewsItem(relatedArticle: RelatedArticleUM, modifier: Modifier = Modifier) {
|
||||
Column(
|
||||
modifier = modifier
|
||||
.sizeIn(maxWidth = 256.dp, minHeight = 132.dp)
|
||||
.background(color = TangemTheme.colors.background.action, shape = RoundedCornerShape(12.dp))
|
||||
.clickable(onClick = relatedArticle.onClick)
|
||||
.padding(12.dp),
|
||||
) {
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(bottom = 4.dp)) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_explore_16),
|
||||
contentDescription = null,
|
||||
tint = TangemTheme.colors.icon.informative,
|
||||
modifier = Modifier.size(16.dp),
|
||||
)
|
||||
SpacerW(4.dp)
|
||||
Text(
|
||||
text = relatedArticle.media.name,
|
||||
style = TangemTheme.typography.caption1,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
}
|
||||
if (relatedArticle.title.isNotEmpty()) {
|
||||
SpacerH(4.dp)
|
||||
Text(
|
||||
text = relatedArticle.title,
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
maxLines = 3,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
}
|
||||
if (relatedArticle.imageUrl != null) {
|
||||
SubcomposeAsyncImage(
|
||||
modifier = Modifier
|
||||
.size(40.dp)
|
||||
.clip(RoundedCornerShape(4.dp)),
|
||||
contentScale = ContentScale.Crop,
|
||||
model = ImageRequest.Builder(context = LocalContext.current)
|
||||
.data(relatedArticle.imageUrl)
|
||||
.crossfade(enable = false)
|
||||
.allowHardware(true)
|
||||
.memoryCachePolicy(CachePolicy.DISABLED)
|
||||
.build(),
|
||||
loading = {
|
||||
RectangleShimmer(
|
||||
modifier = Modifier.size(40.dp),
|
||||
radius = 4.dp,
|
||||
)
|
||||
},
|
||||
error = {},
|
||||
contentDescription = relatedArticle.media.name,
|
||||
)
|
||||
}
|
||||
}
|
||||
SpacerHMax()
|
||||
Text(
|
||||
text = relatedArticle.publishedAt.resolveReference(),
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
|
|
@ -396,4 +139,28 @@ private fun PreviewNewsDetailsContent() {
|
|||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun PreviewNewsDetailsContentV2() {
|
||||
TangemThemePreviewRedesign {
|
||||
val background = TangemTheme.colors.background.tertiary
|
||||
CompositionLocalProvider(
|
||||
LocalMainBottomSheetColor provides remember { mutableStateOf(background) },
|
||||
) {
|
||||
NewsDetailsContent(
|
||||
state = NewsDetailsUM(
|
||||
articlesStateUM = ArticlesStateUM.Content,
|
||||
articles = MockArticlesFactory.createMockArticles(),
|
||||
selectedArticleIndex = 0,
|
||||
onShareClick = {},
|
||||
onLikeClick = {},
|
||||
onBackClick = {},
|
||||
onArticleIndexChanged = {},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,324 @@
|
|||
package com.tangem.features.feed.ui.news.details.components
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.LazyRow
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.BottomFade
|
||||
import com.tangem.core.ui.components.BottomFadeWithBlur
|
||||
import com.tangem.core.ui.components.SecondaryButtonIconStart
|
||||
import com.tangem.core.ui.components.SpacerH
|
||||
import com.tangem.core.ui.components.buttons.common.TangemButtonSize
|
||||
import com.tangem.core.ui.components.haze.hazeSourceTangem
|
||||
import com.tangem.core.ui.ds.button.SecondaryTangemButton
|
||||
import com.tangem.core.ui.ds.button.TangemButtonShape
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.LocalMainBottomSheetColor
|
||||
import com.tangem.core.ui.res.LocalRedesignEnabled
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.features.feed.ui.feed.components.articles.ArticleHeader
|
||||
import com.tangem.features.feed.ui.news.details.state.ArticleUM
|
||||
import com.tangem.features.feed.ui.news.details.state.RelatedArticleUM
|
||||
import com.tangem.features.feed.ui.news.details.state.RelatedTokensUM
|
||||
|
||||
@Composable
|
||||
internal fun ArticleDetail(
|
||||
article: ArticleUM,
|
||||
onLikeClick: () -> Unit,
|
||||
relatedTokensUM: RelatedTokensUM,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
if (LocalRedesignEnabled.current) {
|
||||
ArticleDetailV2(
|
||||
article = article,
|
||||
onLikeClick = onLikeClick,
|
||||
relatedTokensUM = relatedTokensUM,
|
||||
modifier = modifier,
|
||||
)
|
||||
} else {
|
||||
ArticleDetailV1(
|
||||
article = article,
|
||||
onLikeClick = onLikeClick,
|
||||
relatedTokensUM = relatedTokensUM,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
private fun ArticleDetailV1(
|
||||
article: ArticleUM,
|
||||
onLikeClick: () -> Unit,
|
||||
relatedTokensUM: RelatedTokensUM,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val hapticFeedback = LocalHapticFeedback.current
|
||||
val density = LocalDensity.current
|
||||
val background = LocalMainBottomSheetColor.current.value
|
||||
val pagerHeight = 32.dp
|
||||
val contentPadding = pagerHeight + 56.dp + with(density) {
|
||||
WindowInsets.navigationBars.getBottom(this).div(this.density)
|
||||
}.dp
|
||||
|
||||
Box(modifier = modifier) {
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentPadding = PaddingValues(bottom = contentPadding),
|
||||
) {
|
||||
item("content") {
|
||||
ArticleHeader(
|
||||
title = article.title,
|
||||
createdAt = article.createdAt.resolveReference(),
|
||||
score = article.score,
|
||||
tags = article.tags,
|
||||
isTrending = article.isTrending,
|
||||
modifier = Modifier
|
||||
.padding(top = 16.dp)
|
||||
.padding(horizontal = 16.dp),
|
||||
)
|
||||
|
||||
if (article.shortContent.isNotEmpty()) {
|
||||
QuickRecap(
|
||||
content = article.shortContent,
|
||||
modifier = Modifier
|
||||
.padding(top = 32.dp)
|
||||
.padding(horizontal = 16.dp),
|
||||
)
|
||||
}
|
||||
|
||||
Text(
|
||||
text = article.content,
|
||||
style = TangemTheme.typography.body1,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
modifier = Modifier
|
||||
.padding(top = 16.dp)
|
||||
.padding(horizontal = 16.dp),
|
||||
)
|
||||
|
||||
SpacerH(24.dp)
|
||||
|
||||
SecondaryButtonIconStart(
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
iconResId = if (article.isLiked) {
|
||||
R.drawable.ic_heart_filled_20
|
||||
} else {
|
||||
R.drawable.ic_heart_20
|
||||
},
|
||||
iconTint = Color.Unspecified,
|
||||
text = stringResourceSafe(R.string.news_like),
|
||||
size = TangemButtonSize.RoundedAction,
|
||||
onClick = {
|
||||
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||
onLikeClick()
|
||||
},
|
||||
)
|
||||
|
||||
RelatedTokensBlock(
|
||||
relatedTokensUM = relatedTokensUM,
|
||||
onItemClick = when (relatedTokensUM) {
|
||||
is RelatedTokensUM.Content -> relatedTokensUM.onTokenClick
|
||||
else -> null
|
||||
},
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
)
|
||||
|
||||
if (article.relatedArticles.isNotEmpty()) {
|
||||
SpacerH(24.dp)
|
||||
Row(
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
Text(
|
||||
text = stringResourceSafe(R.string.news_sources),
|
||||
style = TangemTheme.typography.h3,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
Text(
|
||||
text = "${article.relatedArticles.size}",
|
||||
style = TangemTheme.typography.h3,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (article.relatedArticles.isNotEmpty()) {
|
||||
item("relatedArticles") {
|
||||
LazyRow(
|
||||
modifier = Modifier.padding(vertical = 12.dp),
|
||||
state = rememberLazyListState(),
|
||||
contentPadding = PaddingValues(horizontal = 16.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||
) {
|
||||
items(
|
||||
items = article.relatedArticles,
|
||||
key = RelatedArticleUM::id,
|
||||
) { article ->
|
||||
RelatedNewsItem(
|
||||
relatedArticle = article,
|
||||
modifier = Modifier.fillParentMaxHeight(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
BottomFade(
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomCenter),
|
||||
backgroundColor = background,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
internal fun ArticleDetailV2(
|
||||
article: ArticleUM,
|
||||
onLikeClick: () -> Unit,
|
||||
relatedTokensUM: RelatedTokensUM,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val hapticFeedback = LocalHapticFeedback.current
|
||||
val density = LocalDensity.current
|
||||
val background = LocalMainBottomSheetColor.current.value
|
||||
val pagerHeight = 32.dp
|
||||
val contentPadding = pagerHeight + 56.dp + with(density) {
|
||||
WindowInsets.navigationBars.getBottom(this).div(this.density)
|
||||
}.dp
|
||||
|
||||
Box(modifier = modifier) {
|
||||
LazyColumn(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.hazeSourceTangem(zIndex = 0f)
|
||||
.background(background),
|
||||
contentPadding = PaddingValues(bottom = contentPadding),
|
||||
) {
|
||||
item("content") {
|
||||
ArticleHeader(
|
||||
title = article.title,
|
||||
createdAt = article.createdAt.resolveReference(),
|
||||
score = article.score,
|
||||
tags = article.tags,
|
||||
isTrending = article.isTrending,
|
||||
modifier = Modifier
|
||||
.padding(top = 16.dp)
|
||||
.padding(horizontal = 16.dp),
|
||||
)
|
||||
|
||||
if (article.shortContent.isNotEmpty()) {
|
||||
QuickRecap(
|
||||
content = article.shortContent,
|
||||
modifier = Modifier
|
||||
.padding(top = 32.dp)
|
||||
.padding(horizontal = 16.dp),
|
||||
)
|
||||
}
|
||||
|
||||
Text(
|
||||
text = article.content,
|
||||
style = TangemTheme.typography2.bodyRegular16,
|
||||
color = TangemTheme.colors2.text.neutral.primary,
|
||||
modifier = Modifier
|
||||
.padding(top = 12.dp)
|
||||
.padding(horizontal = 16.dp),
|
||||
)
|
||||
|
||||
SpacerH(24.dp)
|
||||
|
||||
HorizontalDivider(
|
||||
modifier = Modifier.padding(horizontal = 24.dp),
|
||||
color = TangemTheme.colors2.border.neutral.primary,
|
||||
)
|
||||
|
||||
SpacerH(20.dp)
|
||||
|
||||
SecondaryTangemButton(
|
||||
modifier = Modifier.padding(horizontal = 24.dp),
|
||||
text = resourceReference(R.string.news_like),
|
||||
size = com.tangem.core.ui.ds.button.TangemButtonSize.X9,
|
||||
iconRes = if (article.isLiked) {
|
||||
R.drawable.ic_like_20
|
||||
} else {
|
||||
R.drawable.ic_heart_20
|
||||
},
|
||||
onClick = {
|
||||
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||
onLikeClick()
|
||||
},
|
||||
shape = TangemButtonShape.Rounded,
|
||||
)
|
||||
|
||||
RelatedTokensBlock(
|
||||
relatedTokensUM = relatedTokensUM,
|
||||
onItemClick = when (relatedTokensUM) {
|
||||
is RelatedTokensUM.Content -> relatedTokensUM.onTokenClick
|
||||
else -> null
|
||||
},
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
)
|
||||
|
||||
if (article.relatedArticles.isNotEmpty()) {
|
||||
SpacerH(24.dp)
|
||||
Row(
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
Text(
|
||||
text = stringResourceSafe(R.string.news_sources),
|
||||
style = TangemTheme.typography2.headingSemibold20,
|
||||
color = TangemTheme.colors2.text.neutral.primary,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (article.relatedArticles.isNotEmpty()) {
|
||||
item("relatedArticles") {
|
||||
LazyRow(
|
||||
modifier = Modifier
|
||||
.padding(vertical = 12.dp),
|
||||
state = rememberLazyListState(),
|
||||
contentPadding = PaddingValues(horizontal = 16.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||
) {
|
||||
items(
|
||||
items = article.relatedArticles,
|
||||
key = RelatedArticleUM::id,
|
||||
) { article ->
|
||||
RelatedNewsItem(
|
||||
relatedArticle = article,
|
||||
modifier = Modifier.fillParentMaxHeight(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BottomFadeWithBlur(
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomCenter)
|
||||
.height(80.dp)
|
||||
.fillMaxWidth(),
|
||||
backgroundColor = background,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -17,6 +17,7 @@ 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.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.SpacerH
|
||||
|
|
@ -24,6 +25,7 @@ import com.tangem.core.ui.components.SpacerW
|
|||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.LocalRedesignEnabled
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreviewRedesign
|
||||
|
||||
@Composable
|
||||
internal fun QuickRecap(content: String, modifier: Modifier = Modifier) {
|
||||
|
|
@ -105,20 +107,36 @@ private fun QuickRecapV2(content: String, modifier: Modifier = Modifier) {
|
|||
|
||||
Box {
|
||||
VerticalDivider(
|
||||
modifier = Modifier.fillMaxHeight(),
|
||||
modifier = Modifier
|
||||
.fillMaxHeight()
|
||||
.padding(start = 10.dp),
|
||||
thickness = 2.dp,
|
||||
color = Color(QUICK_RECAP_DIVIDER_COLOR),
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier.padding(start = 16.dp),
|
||||
modifier = Modifier.padding(start = 20.dp),
|
||||
text = content,
|
||||
style = TangemTheme.typography.body1,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
style = TangemTheme.typography2.bodyRegular16,
|
||||
color = TangemTheme.colors2.text.neutral.primary,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Composable
|
||||
private fun QuickRecapPreview() {
|
||||
TangemThemePreviewRedesign {
|
||||
QuickRecapV2(
|
||||
content = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut" +
|
||||
" labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris " +
|
||||
"nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit " +
|
||||
"esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt " +
|
||||
"in culpa qui officia deserunt mollit anim id est laborum.",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private const val QUICK_RECAP_DIVIDER_COLOR = 0xFFA99FFF
|
||||
private const val LINEAR_GRADIENT_FIRST_PART = 0xFFA3A0FF
|
||||
private const val LINEAR_GRADIENT_SECOND_PART = 0xFFF79DFF
|
||||
|
|
|
|||
|
|
@ -0,0 +1,188 @@
|
|||
package com.tangem.features.feed.ui.news.details.components
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.clickable
|
||||
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.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import coil.compose.SubcomposeAsyncImage
|
||||
import coil.request.CachePolicy
|
||||
import coil.request.ImageRequest
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.RectangleShimmer
|
||||
import com.tangem.core.ui.components.SpacerH
|
||||
import com.tangem.core.ui.components.SpacerHMax
|
||||
import com.tangem.core.ui.components.SpacerW
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.res.LocalRedesignEnabled
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.features.feed.ui.news.details.state.RelatedArticleUM
|
||||
|
||||
@Composable
|
||||
internal fun RelatedNewsItem(relatedArticle: RelatedArticleUM, modifier: Modifier = Modifier) {
|
||||
if (LocalRedesignEnabled.current) {
|
||||
RelatedNewsItemV2(relatedArticle, modifier)
|
||||
} else {
|
||||
RelatedNewsItemV1(relatedArticle, modifier)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun RelatedNewsItemV1(relatedArticle: RelatedArticleUM, modifier: Modifier = Modifier) {
|
||||
Column(
|
||||
modifier = modifier
|
||||
.sizeIn(maxWidth = 256.dp, minHeight = 132.dp)
|
||||
.background(color = TangemTheme.colors.background.action, shape = RoundedCornerShape(12.dp))
|
||||
.clickable(onClick = relatedArticle.onClick)
|
||||
.padding(12.dp),
|
||||
) {
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(bottom = 4.dp)) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_explore_16),
|
||||
contentDescription = null,
|
||||
tint = TangemTheme.colors.icon.informative,
|
||||
modifier = Modifier.size(16.dp),
|
||||
)
|
||||
SpacerW(4.dp)
|
||||
Text(
|
||||
text = relatedArticle.media.name,
|
||||
style = TangemTheme.typography.caption1,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
}
|
||||
if (relatedArticle.title.isNotEmpty()) {
|
||||
SpacerH(4.dp)
|
||||
Text(
|
||||
text = relatedArticle.title,
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
maxLines = 3,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
}
|
||||
if (relatedArticle.imageUrl != null) {
|
||||
SubcomposeAsyncImage(
|
||||
modifier = Modifier
|
||||
.size(40.dp)
|
||||
.clip(RoundedCornerShape(4.dp)),
|
||||
contentScale = ContentScale.Crop,
|
||||
model = ImageRequest.Builder(context = LocalContext.current)
|
||||
.data(relatedArticle.imageUrl)
|
||||
.crossfade(enable = false)
|
||||
.allowHardware(true)
|
||||
.memoryCachePolicy(CachePolicy.DISABLED)
|
||||
.build(),
|
||||
loading = {
|
||||
RectangleShimmer(
|
||||
modifier = Modifier.size(40.dp),
|
||||
radius = 4.dp,
|
||||
)
|
||||
},
|
||||
error = {},
|
||||
contentDescription = relatedArticle.media.name,
|
||||
)
|
||||
}
|
||||
}
|
||||
SpacerHMax()
|
||||
Text(
|
||||
text = relatedArticle.publishedAt.resolveReference(),
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
private fun RelatedNewsItemV2(relatedArticle: RelatedArticleUM, modifier: Modifier = Modifier) {
|
||||
Column(
|
||||
modifier = modifier
|
||||
.sizeIn(maxWidth = 228.dp, minHeight = 160.dp)
|
||||
.background(
|
||||
color = TangemTheme.colors2.surface.level3,
|
||||
shape = RoundedCornerShape(TangemTheme.dimens2.x5),
|
||||
)
|
||||
.clickable(onClick = relatedArticle.onClick)
|
||||
.border(
|
||||
width = 1.dp,
|
||||
color = TangemTheme.colors2.border.neutral.primary,
|
||||
shape = RoundedCornerShape(TangemTheme.dimens2.x5),
|
||||
)
|
||||
.padding(16.dp),
|
||||
) {
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(bottom = 4.dp)) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_explore_16),
|
||||
contentDescription = null,
|
||||
tint = TangemTheme.colors2.markers.iconGray,
|
||||
modifier = Modifier.size(16.dp),
|
||||
)
|
||||
SpacerW(2.dp)
|
||||
Text(
|
||||
text = relatedArticle.media.name,
|
||||
style = TangemTheme.typography2.captionSemibold12,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
color = TangemTheme.colors2.text.neutral.secondary,
|
||||
)
|
||||
}
|
||||
if (relatedArticle.title.isNotEmpty()) {
|
||||
SpacerH(8.dp)
|
||||
Text(
|
||||
text = relatedArticle.title,
|
||||
style = TangemTheme.typography2.bodyRegular16,
|
||||
color = TangemTheme.colors2.text.neutral.primary,
|
||||
maxLines = 3,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
}
|
||||
if (relatedArticle.imageUrl != null) {
|
||||
SubcomposeAsyncImage(
|
||||
modifier = Modifier
|
||||
.size(44.dp)
|
||||
.clip(RoundedCornerShape(12.dp)),
|
||||
contentScale = ContentScale.Crop,
|
||||
model = ImageRequest.Builder(context = LocalContext.current)
|
||||
.data(relatedArticle.imageUrl)
|
||||
.crossfade(enable = false)
|
||||
.allowHardware(true)
|
||||
.memoryCachePolicy(CachePolicy.DISABLED)
|
||||
.build(),
|
||||
loading = {
|
||||
RectangleShimmer(
|
||||
modifier = Modifier.size(44.dp),
|
||||
radius = 12.dp,
|
||||
)
|
||||
},
|
||||
error = {},
|
||||
contentDescription = relatedArticle.media.name,
|
||||
)
|
||||
}
|
||||
}
|
||||
SpacerHMax()
|
||||
Text(
|
||||
text = relatedArticle.publishedAt.resolveReference(),
|
||||
style = TangemTheme.typography2.captionSemibold12,
|
||||
color = TangemTheme.colors2.text.neutral.secondary,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ package com.tangem.features.feed.ui.news.details.components
|
|||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
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
|
||||
|
|
@ -16,6 +17,7 @@ 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.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.LocalRedesignEnabled
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.features.feed.model.news.details.NewsDetailsModel.Companion.RELATED_TOKEN_MAX_COUNT
|
||||
import com.tangem.features.feed.ui.news.details.state.RelatedTokensUM
|
||||
|
|
@ -38,11 +40,20 @@ internal fun RelatedTokensBlock(
|
|||
|
||||
Column(modifier = modifier) {
|
||||
SpacerH(40.dp)
|
||||
Text(
|
||||
text = stringResourceSafe(R.string.news_related_tokens),
|
||||
style = TangemTheme.typography.h3,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
if (LocalRedesignEnabled.current) {
|
||||
Text(
|
||||
modifier = Modifier.padding(horizontal = 8.dp),
|
||||
text = stringResourceSafe(R.string.news_related_tokens),
|
||||
style = TangemTheme.typography2.headingSemibold20,
|
||||
color = TangemTheme.colors2.text.neutral.primary,
|
||||
)
|
||||
} else {
|
||||
Text(
|
||||
text = stringResourceSafe(R.string.news_related_tokens),
|
||||
style = TangemTheme.typography.h3,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
}
|
||||
SpacerH(12.dp)
|
||||
|
||||
BlockCard(
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import kotlinx.collections.immutable.toPersistentList
|
|||
internal object MockArticlesFactory {
|
||||
fun createMockArticles(): ImmutableList<ArticleUM> = listOf(
|
||||
ArticleUM(
|
||||
isTrending = false,
|
||||
id = 1,
|
||||
title = "SEC delays decisions on ETH-staking ETFs and spot XRP/SOL funds",
|
||||
createdAt = TextReference.Str("20 Jun, 21:45"),
|
||||
|
|
@ -78,6 +79,7 @@ internal object MockArticlesFactory {
|
|||
newsUrl = "",
|
||||
relatedTokens = persistentListOf(),
|
||||
isLiked = false,
|
||||
isTrending = false,
|
||||
),
|
||||
ArticleUM(
|
||||
id = 3,
|
||||
|
|
@ -107,6 +109,7 @@ internal object MockArticlesFactory {
|
|||
newsUrl = "",
|
||||
relatedTokens = persistentListOf(),
|
||||
isLiked = false,
|
||||
isTrending = false,
|
||||
),
|
||||
ArticleUM(
|
||||
id = 4,
|
||||
|
|
@ -136,6 +139,7 @@ internal object MockArticlesFactory {
|
|||
newsUrl = "",
|
||||
relatedTokens = persistentListOf(),
|
||||
isLiked = false,
|
||||
isTrending = false,
|
||||
),
|
||||
ArticleUM(
|
||||
id = 5,
|
||||
|
|
@ -151,6 +155,7 @@ internal object MockArticlesFactory {
|
|||
newsUrl = "",
|
||||
relatedTokens = persistentListOf(),
|
||||
isLiked = false,
|
||||
isTrending = false,
|
||||
),
|
||||
ArticleUM(
|
||||
id = 6,
|
||||
|
|
@ -167,6 +172,7 @@ internal object MockArticlesFactory {
|
|||
newsUrl = "",
|
||||
relatedTokens = persistentListOf(),
|
||||
isLiked = false,
|
||||
isTrending = false,
|
||||
),
|
||||
ArticleUM(
|
||||
id = 7,
|
||||
|
|
@ -182,6 +188,7 @@ internal object MockArticlesFactory {
|
|||
newsUrl = "",
|
||||
relatedTokens = persistentListOf(),
|
||||
isLiked = false,
|
||||
isTrending = false,
|
||||
),
|
||||
ArticleUM(
|
||||
id = 8,
|
||||
|
|
@ -197,6 +204,7 @@ internal object MockArticlesFactory {
|
|||
newsUrl = "",
|
||||
relatedTokens = persistentListOf(),
|
||||
isLiked = false,
|
||||
isTrending = false,
|
||||
),
|
||||
ArticleUM(
|
||||
id = 9,
|
||||
|
|
@ -213,6 +221,7 @@ internal object MockArticlesFactory {
|
|||
newsUrl = "",
|
||||
relatedTokens = persistentListOf(),
|
||||
isLiked = false,
|
||||
isTrending = false,
|
||||
),
|
||||
ArticleUM(
|
||||
id = 10,
|
||||
|
|
@ -229,6 +238,7 @@ internal object MockArticlesFactory {
|
|||
newsUrl = "",
|
||||
relatedTokens = persistentListOf(),
|
||||
isLiked = false,
|
||||
isTrending = false,
|
||||
),
|
||||
).toPersistentList()
|
||||
}
|
||||
|
|
@ -25,6 +25,7 @@ internal sealed interface ArticlesStateUM {
|
|||
data class LoadingError(val onRetryClicked: () -> Unit) : ArticlesStateUM
|
||||
}
|
||||
|
||||
@Immutable
|
||||
internal data class ArticleUM(
|
||||
val id: Int,
|
||||
val title: String,
|
||||
|
|
@ -37,6 +38,7 @@ internal data class ArticleUM(
|
|||
val newsUrl: String,
|
||||
val relatedTokens: ImmutableList<RelatedToken>,
|
||||
val isLiked: Boolean,
|
||||
val isTrending: Boolean,
|
||||
)
|
||||
|
||||
internal data class RelatedArticleUM(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue