Updated on 2026-08-14

This commit is contained in:
Tangem 2026-03-19 08:41:48 +01:00
parent 09f411a701
commit 16fd81429c
25 changed files with 1445 additions and 320 deletions

View file

@ -3,7 +3,6 @@ package com.tangem.common.ui.markets
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.material3.Button
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
@ -34,10 +33,12 @@ import com.tangem.core.ui.components.RectangleShimmer
import com.tangem.core.ui.components.SpacerW4
import com.tangem.core.ui.components.marketprice.PriceChangeInPercent
import com.tangem.core.ui.components.marketprice.PriceChangeType
import com.tangem.core.ui.ds.badge.*
import com.tangem.core.ui.ds.image.TangemIcon
import com.tangem.core.ui.ds.image.TangemIconUM
import com.tangem.core.ui.ds.row.TangemRowContainer
import com.tangem.core.ui.ds.row.TangemRowLayoutId
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.res.LocalIsInDarkTheme
import com.tangem.core.ui.res.LocalWindowSize
import com.tangem.core.ui.res.TangemTheme
@ -95,7 +96,7 @@ fun MarketListItemContentV2(model: MarketsListItemUM, modifier: Modifier = Modif
.padding(end = TangemTheme.dimens2.x2, start = TangemTheme.dimens2.x3),
ratingPosition = model.ratingPosition,
marketCap = model.marketCap,
// stakingRate = model.stakingRate, TODO in [REDACTED_TASK_KEY]
stakingRate = model.stakingRate,
)
PriceChangeInPercent(
@ -146,7 +147,7 @@ private fun TokenTitle(name: String, currencySymbol: String, modifier: Modifier
private fun TokenSubtitle(
ratingPosition: String?,
marketCap: String?,
// stakingRate: TextReference?, TODO in [REDACTED_TASK_KEY]
stakingRate: TextReference?,
modifier: Modifier = Modifier,
) {
val ratingColor = mapRatingToColor(ratingPosition)
@ -154,7 +155,7 @@ private fun TokenSubtitle(
Row(
modifier = modifier,
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x2),
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x1),
) {
TokenRatingPlace(
ratingPosition = ratingPosition,
@ -167,6 +168,9 @@ private fun TokenSubtitle(
text = marketCap,
)
}
if (stakingRate != null) {
StakingRate(stakingRate = stakingRate)
}
}
}
@ -214,6 +218,18 @@ private fun RowScope.TokenMarketCapText(text: String, ratingColor: Color, modifi
)
}
@Composable
private fun RowScope.StakingRate(stakingRate: TextReference, modifier: Modifier = Modifier) {
TangemBadge(
modifier = modifier.alignByBaseline(),
text = stakingRate,
shape = TangemBadgeShape.Rounded,
size = TangemBadgeSize.X4,
type = TangemBadgeType.Tinted,
color = TangemBadgeColor.Gray,
)
}
@Composable
private fun Chart(chartType: MarketChartLook.Type, chartRawData: MarketChartRawData?, modifier: Modifier = Modifier) {
Box(

View file

@ -4,23 +4,29 @@ import android.content.res.Configuration
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.tangem.core.ui.components.CircleShimmer
import com.tangem.core.ui.components.RectangleShimmer
import com.tangem.core.ui.components.SpacerH
import com.tangem.core.ui.components.SpacerW12
import com.tangem.core.ui.res.LocalWindowSize
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.components.*
import com.tangem.core.ui.res.*
import com.tangem.core.ui.windowsize.WindowSizeType
@Composable
fun MarketsListItemPlaceholder() {
if (LocalRedesignEnabled.current) {
MarketsListItemPlaceholderV2()
} else {
MarketsListItemPlaceholderV1()
}
}
@Suppress("LongMethod")
@Composable
fun MarketsListItemPlaceholder() {
private fun MarketsListItemPlaceholderV1() {
val density = LocalDensity.current
val windowSize = LocalWindowSize.current
val sp12 = with(density) { 12.sp.toDp() }
@ -84,11 +90,71 @@ fun MarketsListItemPlaceholder() {
}
}
@Suppress("LongMethod")
@Composable
private fun MarketsListItemPlaceholderV2() {
val windowSize = LocalWindowSize.current
Row(
modifier = Modifier.padding(
horizontal = 12.dp,
vertical = 14.dp,
),
verticalAlignment = Alignment.CenterVertically,
) {
CircleShimmer(modifier = Modifier.size(40.dp))
SpacerW(4.dp)
Column(
modifier = Modifier.weight(1f),
verticalArrangement = Arrangement.spacedBy(4.dp),
horizontalAlignment = Alignment.Start,
) {
RectangleShimmer(
modifier = Modifier
.width(96.dp)
.height(20.dp),
radius = TangemTheme.dimens2.x25,
)
RectangleShimmer(
modifier = Modifier
.width(46.dp)
.height(16.dp),
radius = TangemTheme.dimens2.x25,
)
}
if (windowSize.widthAtLeast(WindowSizeType.Small)) {
SpacerW(10.dp)
Column(
verticalArrangement = Arrangement.spacedBy(4.dp),
horizontalAlignment = Alignment.End,
) {
RectangleShimmer(
modifier = Modifier
.width(56.dp)
.height(20.dp),
radius = TangemTheme.dimens2.x25,
)
RectangleShimmer(
modifier = Modifier
.width(46.dp)
.height(16.dp),
radius = TangemTheme.dimens2.x25,
)
}
}
}
}
@Preview(showBackground = true, widthDp = 360, name = "normal")
@Preview(showBackground = true, widthDp = 360, name = "normal night", uiMode = Configuration.UI_MODE_NIGHT_YES)
@Preview(showBackground = true, widthDp = 320, name = "small width")
@Composable
private fun Preview() {
private fun PreviewV1() {
TangemThemePreview {
Column(Modifier.background(TangemTheme.colors.background.tertiary)) {
repeat(20) {
@ -96,4 +162,20 @@ private fun Preview() {
}
}
}
}
@Preview(showBackground = true, widthDp = 360, name = "normal")
@Preview(showBackground = true, widthDp = 360, name = "normal night", uiMode = Configuration.UI_MODE_NIGHT_YES)
@Preview(showBackground = true, widthDp = 320, name = "small width")
@Composable
private fun PreviewV2() {
TangemThemePreviewRedesign {
CompositionLocalProvider(LocalRedesignEnabled provides true) {
Column(Modifier.background(TangemTheme.colors2.surface.level3)) {
repeat(20) {
MarketsListItemPlaceholder()
}
}
}
}
}

View file

@ -21,12 +21,8 @@ import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.haze.hazeEffectTangem
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 dev.chrisbanes.haze.HazeStyle
import kotlin.math.abs
import kotlin.math.roundToInt
@ -44,15 +40,6 @@ private val SMALL_DOT_SIZE = DpSize(4.dp, 4.dp)
@Composable
fun PagerIndicator(pagerState: PagerState, modifier: Modifier = Modifier) {
if (LocalRedesignEnabled.current) {
PagerIndicatorV2(pagerState, modifier)
} else {
PagerIndicatorV1(pagerState, modifier)
}
}
@Composable
private fun PagerIndicatorV1(pagerState: PagerState, modifier: Modifier = Modifier) {
val colors = PagerIndicatorColors(
active = TangemTheme.colors.control.key,
inactive = TangemTheme.colors.text.tertiary,
@ -65,21 +52,6 @@ private fun PagerIndicatorV1(pagerState: PagerState, modifier: Modifier = Modifi
)
}
@Composable
private fun PagerIndicatorV2(pagerState: PagerState, modifier: Modifier = Modifier) {
val colors = PagerIndicatorColors(
active = TangemTheme.colors2.graphic.neutral.primary,
inactive = TangemTheme.colors2.graphic.neutral.tertiary,
overlay = TangemTheme.colors2.tabs.backgroundSecondary.copy(alpha = .1f),
)
PagerIndicatorContent(
pagerState = pagerState,
colors = colors,
modifier = modifier,
boxModifier = Modifier.hazeEffectTangem(style = HazeStyle(blurRadius = 22.dp, tint = null)),
)
}
@Composable
private fun rememberPagerIndicatorAnimationState(pagerState: PagerState): PagerIndicatorAnimationState {
val density = LocalDensity.current
@ -291,7 +263,7 @@ private class DotSizeParams private constructor(
@Preview(showBackground = true)
@Composable
private fun PagerIndicatorPreviewV1() {
private fun PagerIndicatorPreviewV() {
TangemThemePreview {
Column(
Modifier
@ -306,23 +278,6 @@ private fun PagerIndicatorPreviewV1() {
}
}
@Preview(showBackground = true)
@Composable
private fun PagerIndicatorPreviewV2() {
TangemThemePreviewRedesign {
Column(
Modifier
.background(TangemTheme.colors.background.primary)
.padding(20.dp),
verticalArrangement = Arrangement.spacedBy(10.dp),
) {
listOf(0, 1, 2, 3, 4).forEach { page ->
PagerIndicator(rememberPagerState(page) { 5 })
}
}
}
}
@Preview(showBackground = true)
@Composable
private fun PagerIndicator6ItemsPreview() {

View file

@ -54,18 +54,14 @@ private val SMALL_DOT_SIZE = DpSize(4.dp, 4.dp)
*
* @param pagerState state of the pager to observe
* @param modifier modifier for styling
* @param hasBackground whether to show a background behind the indicators
* @param activeIndicatorColor color for the active page indicator
* @param inactiveIndicatorColor color for the inactive page indicators
* @param colors colors of indicators(active/inactive) and overlay
*/
@Suppress("LongMethod", "CyclomaticComplexMethod")
@Composable
fun TangemPagerIndicator(
pagerState: PagerState,
modifier: Modifier = Modifier,
hasBackground: Boolean = false,
activeIndicatorColor: Color = TangemTheme.colors2.graphic.neutral.primary,
inactiveIndicatorColor: Color = TangemTheme.colors2.graphic.neutral.tertiary,
colors: PagerIndicatorColors = TangemPagerIndicatorColors,
) {
val totalPages = pagerState.pageCount
val currentIndex = pagerState.currentPage
@ -131,11 +127,13 @@ fun TangemPagerIndicator(
Box(
modifier = modifier
.conditionalCompose(hasBackground) {
background(
color = TangemTheme.colors2.tabs.backgroundSecondary,
shape = CircleShape,
)
.conditionalCompose(colors.overlay != null) {
colors.overlay?.let { overlay ->
background(
color = overlay,
shape = CircleShape,
)
} ?: this
}
.padding(TangemTheme.dimens2.x3),
contentAlignment = Alignment.Center,
@ -162,8 +160,8 @@ fun TangemPagerIndicator(
index = index,
currentIndex = currentIndex,
totalPages = totalPages,
activeColor = activeIndicatorColor,
inactiveColor = inactiveIndicatorColor,
activeColor = colors.active,
inactiveColor = colors.inactive,
modifier = Modifier.graphicsLayer { alpha = dotAlpha },
)
}
@ -297,6 +295,22 @@ private fun Dot(
}
}
val TangemPagerIndicatorColors: PagerIndicatorColors
@Composable
@ReadOnlyComposable
get() = PagerIndicatorColors(
active = TangemTheme.colors2.graphic.neutral.primary,
inactive = TangemTheme.colors2.graphic.neutral.tertiary,
overlay = null,
)
@Immutable
data class PagerIndicatorColors(
val active: Color,
val inactive: Color,
val overlay: Color?,
)
// region Preview
@Composable
@Preview(showBackground = true)
@ -312,7 +326,13 @@ private fun TangemPagerIndicator_Preview(@PreviewParameter(TangemPagerIndicatorP
repeat(params) { index ->
TangemPagerIndicator(
pagerState = rememberPagerState(index) { params },
hasBackground = index % 2 == 0,
colors = TangemPagerIndicatorColors.copy(
overlay = if (index % 2 == 0) {
TangemTheme.colors2.tabs.backgroundSecondary
} else {
null
},
),
)
}
}

View file

@ -208,7 +208,7 @@ private fun TangemButtonIcon(
visible = isVisible,
modifier = Modifier.size(size = size.toContentSize()),
) {
val wrappedIconRes = remember(this) { requireNotNull(iconRes) }
val wrappedIconRes = remember(iconRes) { requireNotNull(iconRes) }
Icon(
painter = painterResource(id = wrappedIconRes),
contentDescription = null,

View file

@ -59,7 +59,6 @@ fun TangemTab(
shape = shape,
)
.clip(shape = shape)
.padding(all = TangemTheme.dimens2.x3)
.toggleable(
value = isChecked,
onValueChange = onCheckedChange,
@ -67,7 +66,8 @@ fun TangemTab(
role = Role.Checkbox,
interactionSource = remember { MutableInteractionSource() },
indication = ripple(),
),
)
.padding(all = TangemTheme.dimens2.x3),
) {
Text(
text = text.resolveReference(),

View file

@ -1,9 +1,16 @@
package com.tangem.features.feed.components.news.details
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.Icon
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.vectorResource
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.model.getOrCreateModel
@ -12,7 +19,13 @@ import com.tangem.core.ui.components.appbar.TangemTopAppBar
import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM
import com.tangem.core.ui.components.bottomsheets.state.BottomSheetState
import com.tangem.core.ui.decompose.ComposableModularBottomSheetContentComponent
import com.tangem.core.ui.ds.topbar.TangemTopBar
import com.tangem.core.ui.ds.topbar.TangemTopBarType
import com.tangem.core.ui.extensions.clickableSingle
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.res.LocalMainBottomSheetColor
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.core.ui.res.TangemTheme
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.markets.TokenMarketParams
import com.tangem.domain.news.model.NewsListConfig
@ -32,21 +45,65 @@ internal class DefaultNewsDetailsComponent(
override fun Title(bottomSheetState: State<BottomSheetState>) {
val background = LocalMainBottomSheetColor.current.value
val state by newsDetailsModel.state.collectAsStateWithLifecycle()
TangemTopAppBar(
containerColor = background,
title = null,
startButton = TopAppBarButtonUM.Icon(
iconRes = R.drawable.ic_back_24,
onClicked = state.onBackClick,
isEnabled = bottomSheetState.value == BottomSheetState.EXPANDED,
),
endButton = TopAppBarButtonUM.Icon(
iconRes = R.drawable.ic_share_24,
onClicked = state.onShareClick,
isEnabled = bottomSheetState.value == BottomSheetState.EXPANDED &&
state.articlesStateUM is ArticlesStateUM.Content,
),
)
if (LocalRedesignEnabled.current) {
TangemTopBar(
title = resourceReference(R.string.common_news),
type = TangemTopBarType.BottomSheet,
startContent = {
Icon(
imageVector = ImageVector.vectorResource(id = R.drawable.ic_arrow_back_28),
contentDescription = null,
tint = TangemTheme.colors2.graphic.neutral.primary,
modifier = Modifier
.size(TangemTheme.dimens2.x11)
.background(
color = TangemTheme.colors2.button.backgroundSecondary,
shape = CircleShape,
)
.clickableSingle(
onClick = state.onBackClick,
enabled = bottomSheetState.value == BottomSheetState.EXPANDED,
)
.padding(TangemTheme.dimens2.x2),
)
},
endContent = {
Icon(
imageVector = ImageVector.vectorResource(id = R.drawable.ic_share_new_24),
contentDescription = null,
tint = TangemTheme.colors2.graphic.neutral.primary,
modifier = Modifier
.size(TangemTheme.dimens2.x11)
.background(
color = TangemTheme.colors2.button.backgroundSecondary,
shape = CircleShape,
)
.clickableSingle(
onClick = state.onShareClick,
enabled = bottomSheetState.value == BottomSheetState.EXPANDED &&
state.articlesStateUM is ArticlesStateUM.Content,
)
.padding(TangemTheme.dimens2.x2_5),
)
},
)
} else {
TangemTopAppBar(
containerColor = background,
title = null,
startButton = TopAppBarButtonUM.Icon(
iconRes = R.drawable.ic_back_24,
onClicked = state.onBackClick,
isEnabled = bottomSheetState.value == BottomSheetState.EXPANDED,
),
endButton = TopAppBarButtonUM.Icon(
iconRes = R.drawable.ic_share_24,
onClicked = state.onShareClick,
isEnabled = bottomSheetState.value == BottomSheetState.EXPANDED &&
state.articlesStateUM is ArticlesStateUM.Content,
),
)
}
}
@Composable

View file

@ -1,9 +1,16 @@
package com.tangem.features.feed.components.news.list
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.Icon
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.vectorResource
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.model.getOrCreateModel
@ -12,8 +19,14 @@ import com.tangem.core.ui.components.appbar.TangemTopAppBar
import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM
import com.tangem.core.ui.components.bottomsheets.state.BottomSheetState
import com.tangem.core.ui.decompose.ComposableModularBottomSheetContentComponent
import com.tangem.core.ui.ds.topbar.TangemTopBar
import com.tangem.core.ui.ds.topbar.TangemTopBarType
import com.tangem.core.ui.extensions.clickableSingle
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.domain.news.model.NewsListConfig
import com.tangem.features.feed.model.news.list.NewsListModel
import com.tangem.features.feed.ui.news.list.NewsListContent
@ -30,15 +43,40 @@ internal class DefaultNewsListComponent(
override fun Title(bottomSheetState: State<BottomSheetState>) {
val background = LocalMainBottomSheetColor.current.value
val state by newsListModel.state.collectAsStateWithLifecycle()
TangemTopAppBar(
containerColor = background,
title = stringResourceSafe(R.string.common_news),
startButton = TopAppBarButtonUM.Icon(
iconRes = R.drawable.ic_back_24,
onClicked = state.onBackClick,
isEnabled = bottomSheetState.value == BottomSheetState.EXPANDED,
),
)
if (LocalRedesignEnabled.current) {
TangemTopBar(
title = resourceReference(R.string.common_news),
type = TangemTopBarType.BottomSheet,
startContent = {
Icon(
imageVector = ImageVector.vectorResource(id = R.drawable.ic_arrow_back_28),
contentDescription = null,
tint = TangemTheme.colors2.graphic.neutral.primary,
modifier = Modifier
.size(TangemTheme.dimens2.x11)
.background(
color = TangemTheme.colors2.button.backgroundSecondary,
shape = CircleShape,
)
.clickableSingle(
onClick = state.onBackClick,
enabled = bottomSheetState.value == BottomSheetState.EXPANDED,
)
.padding(TangemTheme.dimens2.x2),
)
},
)
} else {
TangemTopAppBar(
containerColor = background,
title = stringResourceSafe(R.string.common_news),
startButton = TopAppBarButtonUM.Icon(
iconRes = R.drawable.ic_back_24,
onClicked = state.onBackClick,
isEnabled = bottomSheetState.value == BottomSheetState.EXPANDED,
),
)
}
}
@Composable

View file

@ -48,6 +48,7 @@ import com.tangem.features.feed.model.converter.ShortArticleToArticleConfigUMCon
import com.tangem.features.feed.model.market.details.analytics.MarketDetailsAnalyticsEvent
import com.tangem.features.feed.model.market.details.converter.DescriptionConverter
import com.tangem.features.feed.model.market.details.converter.ExchangeItemStateConverter
import com.tangem.features.feed.model.market.details.converter.ExchangeItemStateConverterV2
import com.tangem.features.feed.model.market.details.converter.TokenMarketInfoConverter
import com.tangem.features.feed.model.market.details.formatter.*
import com.tangem.features.feed.model.market.details.state.QuotesStateUpdater
@ -78,7 +79,7 @@ internal class MarketsTokenDetailsModel @Inject constructor(
getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
getUserCountryUseCase: GetUserCountryUseCase,
paramsContainer: ParamsContainer,
designFeatureToggles: DesignFeatureToggles,
private val designFeatureToggles: DesignFeatureToggles,
private val getTokenPriceChartUseCase: GetTokenPriceChartUseCase,
private val getTokenMarketInfoUseCase: GetTokenMarketInfoUseCase,
private val getTokenFullQuotesUseCase: GetTokenFullQuotesUseCase,
@ -704,9 +705,15 @@ internal class MarketsTokenDetailsModel @Inject constructor(
ExchangesBottomSheetContent.Error(onRetryClick = { onListedOnClick(exchangesCount) })
},
ifRight = { list ->
ExchangesBottomSheetContent.Content(
exchangeItems = ExchangeItemStateConverter.convertList(list).toImmutableList(),
)
if (designFeatureToggles.isRedesignEnabled) {
ExchangesBottomSheetContent.ContentV2(
exchangeItemsV2 = ExchangeItemStateConverterV2.convertList(list).toImmutableList(),
)
} else {
ExchangesBottomSheetContent.ContentV1(
exchangeItems = ExchangeItemStateConverter.convertList(list).toImmutableList(),
)
}
},
)

View file

@ -0,0 +1,60 @@
package com.tangem.features.feed.model.market.details.converter
import com.tangem.core.ui.components.audits.AuditLabelUM
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.core.ui.ds.image.TangemIconUM
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.format.bigdecimal.compact
import com.tangem.core.ui.format.bigdecimal.fiat
import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.domain.markets.TokenMarketExchange
import com.tangem.domain.markets.TokenMarketExchange.TrustScore
import com.tangem.features.feed.impl.R
import com.tangem.features.feed.ui.market.detailed.state.ExchangeItemUM
import com.tangem.utils.converter.Converter
internal object ExchangeItemStateConverterV2 : Converter<TokenMarketExchange, ExchangeItemUM> {
override fun convert(value: TokenMarketExchange): ExchangeItemUM.Content {
return ExchangeItemUM.Content(
id = value.id,
title = stringReference(value.name),
subTitle = stringReference(value = if (value.isCentralized) "CEX" else "DEX"),
icon = TangemIconUM.Currency(
CurrencyIconState.CoinIcon(
url = value.imageUrl,
fallbackResId = R.drawable.ic_alert_24,
isGrayscale = false,
shouldShowCustomBadge = false,
),
),
volumeInUsd = stringReference(
value.volumeInUsd.format {
fiat(
fiatCurrencyCode = "USD",
fiatCurrencySymbol = "$",
).compact()
},
),
auditLabel = value.trustScore.toAuditLabelUM(),
)
}
private fun TrustScore.toAuditLabelUM(): AuditLabelUM {
return when (this) {
TrustScore.Risky -> AuditLabelUM(
text = resourceReference(id = R.string.markets_token_details_exchange_trust_score_risky),
type = AuditLabelUM.Type.Prohibition,
)
TrustScore.Caution -> AuditLabelUM(
text = resourceReference(id = R.string.markets_token_details_exchange_trust_score_caution),
type = AuditLabelUM.Type.Warning,
)
TrustScore.Trusted -> AuditLabelUM(
text = resourceReference(id = R.string.markets_token_details_exchange_trust_score_trusted),
type = AuditLabelUM.Type.Permit,
)
}
}
}

View file

@ -6,7 +6,6 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.*
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.CompositionLocalProvider
@ -14,21 +13,19 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.onFirstVisible
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextAlign
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.*
import com.tangem.core.ui.components.buttons.SecondarySmallButton
import com.tangem.core.ui.components.buttons.SmallButtonConfig
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.core.ui.components.list.InfiniteListHandler
import com.tangem.core.ui.decorations.roundedShapeItemDecoration
import com.tangem.core.ui.extensions.*
import com.tangem.core.ui.extensions.conditional
import com.tangem.core.ui.extensions.conditionalCompose
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.*
import com.tangem.features.feed.ui.earn.components.*
import com.tangem.features.feed.ui.earn.state.*
@ -348,66 +345,6 @@ private fun FilterButtonsShimmer(modifier: Modifier = Modifier) {
}
}
@Composable
private fun BestOpportunitiesEmpty(modifier: Modifier = Modifier) {
Column(
modifier = modifier
.fillMaxWidth()
.padding(horizontal = 16.dp)
.background(
color = TangemTheme.colors.background.action,
shape = TangemTheme.shapes.roundedCornersXMedium,
)
.padding(vertical = 32.dp, horizontal = 12.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Icon(
modifier = Modifier
.fillMaxWidth(),
painter = painterResource(R.drawable.ic_empty_64),
contentDescription = null,
tint = Color.Unspecified,
)
SpacerH(24.dp)
Text(
modifier = Modifier
.padding(horizontal = 32.dp),
text = stringResourceSafe(R.string.earn_empty),
style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.tertiary,
textAlign = TextAlign.Center,
)
}
}
@Composable
private fun BestOpportunitiesEmptyFiltered(onClearFilterClick: () -> Unit, modifier: Modifier = Modifier) {
Column(
modifier = modifier
.fillMaxWidth()
.padding(horizontal = 16.dp)
.background(
color = TangemTheme.colors.background.action,
shape = TangemTheme.shapes.roundedCornersXMedium,
)
.padding(vertical = 32.dp, horizontal = 12.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text(
text = stringResourceSafe(R.string.earn_no_results),
style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.tertiary,
)
SpacerH(12.dp)
SecondarySmallButton(
config = SmallButtonConfig(
text = resourceReference(R.string.earn_clear_filter),
onClick = onClearFilterClick,
),
)
}
}
@Composable
private fun SectionHeader(title: String, modifier: Modifier = Modifier) {
if (LocalRedesignEnabled.current) {

View file

@ -0,0 +1,93 @@
package com.tangem.features.feed.ui.earn.components
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
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.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.R
import com.tangem.core.ui.components.SpacerH
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.core.ui.res.TangemTheme
@Composable
internal fun BestOpportunitiesEmpty(modifier: Modifier = Modifier) {
if (LocalRedesignEnabled.current) {
BestOpportunitiesEmptyV2(modifier)
} else {
BestOpportunitiesEmptyV1(modifier)
}
}
@Composable
private fun BestOpportunitiesEmptyV1(modifier: Modifier = Modifier) {
Column(
modifier = modifier
.fillMaxWidth()
.padding(horizontal = 16.dp)
.background(
color = TangemTheme.colors.background.action,
shape = TangemTheme.shapes.roundedCornersXMedium,
)
.padding(vertical = 32.dp, horizontal = 12.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Icon(
modifier = Modifier
.fillMaxWidth(),
painter = painterResource(R.drawable.ic_empty_64),
contentDescription = null,
tint = Color.Unspecified,
)
SpacerH(TangemTheme.dimens2.x6)
Text(
modifier = Modifier
.padding(horizontal = TangemTheme.dimens2.x8),
text = stringResourceSafe(R.string.earn_empty),
style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.tertiary,
textAlign = TextAlign.Center,
)
}
}
@Composable
private fun BestOpportunitiesEmptyV2(modifier: Modifier = Modifier) {
Column(
modifier = modifier
.fillMaxWidth()
.padding(horizontal = TangemTheme.dimens2.x4)
.background(
color = TangemTheme.colors2.surface.level3,
shape = RoundedCornerShape(TangemTheme.dimens2.x5),
)
.padding(vertical = TangemTheme.dimens2.x8, horizontal = TangemTheme.dimens2.x3),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Icon(
modifier = Modifier
.fillMaxWidth(),
painter = painterResource(R.drawable.ic_empty_64),
contentDescription = null,
tint = TangemTheme.colors2.graphic.neutral.quaternary,
)
SpacerH(TangemTheme.dimens2.x5)
Text(
modifier = Modifier.padding(horizontal = TangemTheme.dimens2.x8),
text = stringResourceSafe(R.string.earn_empty),
style = TangemTheme.typography2.bodyRegular14,
color = TangemTheme.colors2.text.neutral.secondary,
textAlign = TextAlign.Center,
)
}
}

View file

@ -0,0 +1,89 @@
package com.tangem.features.feed.ui.earn.components
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.R
import com.tangem.core.ui.components.SpacerH
import com.tangem.core.ui.components.buttons.SecondarySmallButton
import com.tangem.core.ui.components.buttons.SmallButtonConfig
import com.tangem.core.ui.ds.button.*
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.core.ui.res.TangemTheme
@Composable
internal fun BestOpportunitiesEmptyFiltered(onClearFilterClick: () -> Unit, modifier: Modifier = Modifier) {
if (LocalRedesignEnabled.current) {
BestOpportunitiesEmptyFilteredV2(onClearFilterClick, modifier)
} else {
BestOpportunitiesEmptyFilteredV1(onClearFilterClick, modifier)
}
}
@Composable
private fun BestOpportunitiesEmptyFilteredV2(onClearFilterClick: () -> Unit, modifier: Modifier = Modifier) {
Column(
modifier = modifier
.fillMaxWidth()
.padding(horizontal = TangemTheme.dimens2.x4)
.background(
color = TangemTheme.colors2.surface.level3,
shape = RoundedCornerShape(TangemTheme.dimens2.x5),
)
.padding(vertical = TangemTheme.dimens2.x8, horizontal = TangemTheme.dimens2.x3),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text(
text = stringResourceSafe(R.string.earn_no_results),
style = TangemTheme.typography2.bodyRegular14,
color = TangemTheme.colors2.text.neutral.secondary,
)
SpacerH(TangemTheme.dimens2.x2)
TangemButton(
buttonUM = TangemButtonUM(
text = resourceReference(R.string.earn_clear_filter),
onClick = onClearFilterClick,
type = TangemButtonType.Secondary,
size = TangemButtonSize.X8,
shape = TangemButtonShape.Rounded,
),
)
}
}
@Composable
private fun BestOpportunitiesEmptyFilteredV1(onClearFilterClick: () -> Unit, modifier: Modifier = Modifier) {
Column(
modifier = modifier
.fillMaxWidth()
.padding(horizontal = 16.dp)
.background(
color = TangemTheme.colors.background.action,
shape = TangemTheme.shapes.roundedCornersXMedium,
)
.padding(vertical = 32.dp, horizontal = 12.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text(
text = stringResourceSafe(R.string.earn_no_results),
style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.tertiary,
)
SpacerH(12.dp)
SecondarySmallButton(
config = SmallButtonConfig(
text = resourceReference(R.string.earn_clear_filter),
onClick = onClearFilterClick,
),
)
}
}

View file

@ -1,57 +1,54 @@
package com.tangem.features.feed.ui.earn.components
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Text
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.Icon
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.vectorResource
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
import com.tangem.core.ui.components.bottomsheets.sheet.TangemBottomSheet
import com.tangem.core.ui.ds.button.SecondaryTangemButton
import com.tangem.core.ui.ds.button.TangemButtonShape
import com.tangem.core.ui.ds.button.TangemButtonSize
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetType
import com.tangem.core.ui.ds.topbar.TangemTopBar
import com.tangem.core.ui.ds.topbar.TangemTopBarType
import com.tangem.core.ui.extensions.clickableSingle
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.features.feed.impl.R
@Composable
internal inline fun <reified T : TangemBottomSheetConfigContent> EarnFilterBottomSheet(
config: TangemBottomSheetConfig,
crossinline content: @Composable ColumnScope.(T) -> Unit,
crossinline content: @Composable (T) -> Unit,
) {
TangemBottomSheet<T>(
config = config,
type = TangemBottomSheetType.Modal,
containerColor = TangemTheme.colors2.surface.level3,
title = {
Box(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = TangemTheme.dimens.spacing24)
.padding(bottom = TangemTheme.dimens.spacing12),
contentAlignment = Alignment.Center,
) {
Text(
text = stringResourceSafe(R.string.earn_filter_by),
style = TangemTheme.typography2.headingSemibold17,
color = TangemTheme.colors2.text.neutral.primary,
textAlign = TextAlign.Center,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
SecondaryTangemButton(
modifier = Modifier.align(Alignment.CenterEnd),
onClick = config.onDismissRequest,
iconRes = R.drawable.ic_close_24,
size = TangemButtonSize.X9,
shape = TangemButtonShape.Rounded,
)
}
TangemTopBar(
title = resourceReference(R.string.earn_filter_by),
type = TangemTopBarType.BottomSheet,
endContent = {
Icon(
imageVector = ImageVector.vectorResource(id = R.drawable.ic_close_24),
contentDescription = null,
tint = TangemTheme.colors2.graphic.neutral.primary,
modifier = Modifier
.size(TangemTheme.dimens2.x11)
.background(
color = TangemTheme.colors2.button.backgroundSecondary,
shape = CircleShape,
)
.clickableSingle(onClick = config.onDismissRequest)
.padding(TangemTheme.dimens2.x2_5),
)
},
)
},
content = content,
)

View file

@ -119,9 +119,10 @@ private fun ContentV2(content: EarnFilterByNetworkBottomSheetContentUM) {
Column(
modifier = Modifier
.padding(horizontal = 16.dp)
.padding(horizontal = TangemTheme.dimens2.x4)
.padding(bottom = TangemTheme.dimens2.x4)
.verticalScroll(rememberScrollState()),
verticalArrangement = Arrangement.spacedBy(12.dp),
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x3),
) {
NetworksTypesBlock(
allMyNetworks = allMyNetworks,

View file

@ -93,7 +93,9 @@ private fun ContentV1(content: EarnFilterByTypeBottomSheetContentUM) {
@Composable
private fun ContentV2(content: EarnFilterByTypeBottomSheetContentUM) {
CardFilterBlock(
modifier = Modifier.padding(horizontal = 16.dp),
modifier = Modifier
.padding(horizontal = TangemTheme.dimens2.x4)
.padding(bottom = TangemTheme.dimens2.x4),
) {
EarnFilterTypeUM.entries.forEachIndexed { index, type ->
TangemRowContainer(

View file

@ -9,14 +9,15 @@ 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.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
import com.tangem.core.ui.components.block.TangemBlockCardColors
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.features.feed.ui.feed.components.articles.DefaultLoadingArticle
import com.tangem.features.feed.ui.feed.components.articles.TrendingLoadingArticle
@Composable
internal fun FeedListLoading(modifier: Modifier = Modifier) {
@ -35,70 +36,142 @@ internal fun FeedListLoading(modifier: Modifier = Modifier) {
@Composable
internal fun MarketLoadingBlock() {
RectangleShimmer(
modifier = Modifier
.padding(start = 16.dp)
.size(width = 104.dp, height = 18.dp),
)
SpacerH(15.dp)
ChartsLoading(modifier = Modifier.padding(horizontal = 16.dp))
SpacerH(35.dp)
}
@Composable
internal fun MarketPulseLoadingBlock() {
RectangleShimmer(
modifier = Modifier
.padding(start = 16.dp)
.size(width = 104.dp, height = 18.dp),
)
SpacerH(15.dp)
LazyRow(
modifier = Modifier.padding(vertical = 4.dp),
verticalAlignment = Alignment.CenterVertically,
contentPadding = PaddingValues(16.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp),
state = rememberLazyListState(),
) {
items(DEFAULT_CHART_SIZE_IN_MARKET) {
RectangleShimmer(modifier = Modifier.size(width = 124.dp, height = 36.dp))
}
}
SpacerH(16.dp)
ChartsLoading(modifier = Modifier.padding(horizontal = 16.dp))
SpacerH(32.dp)
}
@Composable
internal fun NewsLoadingBlock() {
Column {
if (LocalRedesignEnabled.current) {
RectangleShimmer(
modifier = Modifier
.padding(start = 16.dp, top = 16.dp)
.size(width = 132.dp, height = 24.dp),
radius = TangemTheme.dimens2.x25,
)
SpacerH(20.dp)
ChartsLoading(modifier = Modifier.padding(horizontal = 16.dp))
SpacerH(40.dp)
} else {
RectangleShimmer(
modifier = Modifier
.padding(start = 16.dp)
.size(width = 104.dp, height = 18.dp),
)
SpacerH(15.dp)
TrendingLoadingArticle(modifier = Modifier.padding(horizontal = 16.dp))
SpacerH(12.dp)
LazyRow(
verticalAlignment = Alignment.CenterVertically,
contentPadding = PaddingValues(16.dp),
horizontalArrangement = Arrangement.spacedBy(12.dp),
state = rememberLazyListState(),
) {
items(DEFAULT_CHART_SIZE_IN_MARKET) {
DefaultLoadingArticle()
}
}
ChartsLoading(modifier = Modifier.padding(horizontal = 16.dp))
SpacerH(35.dp)
}
}
@Composable
internal fun MarketPulseLoadingBlock() {
if (LocalRedesignEnabled.current) {
RectangleShimmer(
modifier = Modifier
.padding(start = 16.dp, top = 16.dp)
.size(width = 132.dp, height = 24.dp),
radius = TangemTheme.dimens2.x25,
)
SpacerH(15.dp)
LazyRow(
modifier = Modifier.padding(vertical = 4.dp),
verticalAlignment = Alignment.CenterVertically,
contentPadding = PaddingValues(16.dp),
horizontalArrangement = Arrangement.spacedBy(6.dp),
state = rememberLazyListState(),
) {
items(DEFAULT_CHART_SIZE_IN_MARKET) {
RectangleShimmer(
modifier = Modifier.size(width = 112.dp, height = 36.dp),
radius = TangemTheme.dimens2.x25,
)
}
}
SpacerH(16.dp)
ChartsLoading(modifier = Modifier.padding(horizontal = 16.dp))
SpacerH(32.dp)
} else {
RectangleShimmer(
modifier = Modifier
.padding(start = 16.dp)
.size(width = 104.dp, height = 18.dp),
)
SpacerH(15.dp)
LazyRow(
modifier = Modifier.padding(vertical = 4.dp),
verticalAlignment = Alignment.CenterVertically,
contentPadding = PaddingValues(16.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp),
state = rememberLazyListState(),
) {
items(DEFAULT_CHART_SIZE_IN_MARKET) {
RectangleShimmer(modifier = Modifier.size(width = 124.dp, height = 36.dp))
}
}
SpacerH(16.dp)
ChartsLoading(modifier = Modifier.padding(horizontal = 16.dp))
SpacerH(32.dp)
}
}
@Composable
internal fun NewsLoadingBlock() {
if (LocalRedesignEnabled.current) {
Column {
RectangleShimmer(
modifier = Modifier
.padding(start = 16.dp, top = 16.dp)
.size(width = 132.dp, height = 24.dp),
radius = TangemTheme.dimens2.x25,
)
SpacerH(20.dp)
TrendingLoadingArticle(modifier = Modifier.padding(horizontal = 16.dp))
SpacerH(12.dp)
LazyRow(
verticalAlignment = Alignment.CenterVertically,
contentPadding = PaddingValues(horizontal = 16.dp),
horizontalArrangement = Arrangement.spacedBy(12.dp),
state = rememberLazyListState(),
) {
items(DEFAULT_CHART_SIZE_IN_MARKET) {
DefaultLoadingArticle()
}
}
SpacerH(40.dp)
}
} else {
Column {
RectangleShimmer(
modifier = Modifier
.padding(start = 16.dp)
.size(width = 104.dp, height = 18.dp),
)
SpacerH(15.dp)
TrendingLoadingArticle(modifier = Modifier.padding(horizontal = 16.dp))
SpacerH(12.dp)
LazyRow(
verticalAlignment = Alignment.CenterVertically,
contentPadding = PaddingValues(16.dp),
horizontalArrangement = Arrangement.spacedBy(12.dp),
state = rememberLazyListState(),
) {
items(DEFAULT_CHART_SIZE_IN_MARKET) {
DefaultLoadingArticle()
}
}
SpacerH(35.dp)
}
}
}
@Composable
private fun ChartsLoading(modifier: Modifier = Modifier) {
val isRedesignEnabled = LocalRedesignEnabled.current
BlockCard(
modifier = modifier,
colors = TangemBlockCardColors.copy(containerColor = TangemTheme.colors.background.action),
colors = TangemBlockCardColors.copy(
containerColor = if (isRedesignEnabled) {
TangemTheme.colors2.surface.level3
} else {
TangemTheme.colors.background.action
},
),
) {
Column(
modifier = Modifier

View file

@ -25,7 +25,9 @@ import com.tangem.core.ui.components.SpacerH
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.ds.tabs.TangemTab
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.market.list.state.SortByTypeUM
import com.tangem.features.feed.ui.feed.state.FeedListCallbacks
@ -34,6 +36,7 @@ import com.tangem.features.feed.ui.feed.state.MarketChartUM
@Composable
internal fun MarketBlock(marketChart: MarketChartUM?, feedListCallbacks: FeedListCallbacks) {
val isRedesignEnabled = LocalRedesignEnabled.current
AnimatedContent(
targetState = marketChart,
label = "MarketBlockChartAnimation",
@ -49,8 +52,16 @@ internal fun MarketBlock(marketChart: MarketChartUM?, feedListCallbacks: FeedLis
title = {
Text(
text = stringResourceSafe(R.string.markets_common_title),
style = TangemTheme.typography.h3,
color = TangemTheme.colors.text.primary1,
style = if (isRedesignEnabled) {
TangemTheme.typography2.headingSemibold20
} else {
TangemTheme.typography.h3
},
color = if (isRedesignEnabled) {
TangemTheme.colors2.text.neutral.primary
} else {
TangemTheme.colors.text.primary1
},
overflow = TextOverflow.Ellipsis,
maxLines = 1,
)
@ -76,8 +87,10 @@ internal fun MarketBlock(marketChart: MarketChartUM?, feedListCallbacks: FeedLis
}
}
@Suppress("LongMethod")
@Composable
internal fun MarketPulseBlock(marketChartConfig: MarketChartConfig, feedListCallbacks: FeedListCallbacks) {
val isRedesignEnabled = LocalRedesignEnabled.current
val onSeeAllClick by rememberUpdatedState {
feedListCallbacks.onMarketOpenClick(marketChartConfig.currentSortByType)
}
@ -86,8 +99,16 @@ internal fun MarketPulseBlock(marketChartConfig: MarketChartConfig, feedListCall
title = {
Text(
text = stringResourceSafe(R.string.markets_pulse_common_title),
style = TangemTheme.typography.h3,
color = TangemTheme.colors.text.primary1,
style = if (isRedesignEnabled) {
TangemTheme.typography2.headingSemibold20
} else {
TangemTheme.typography.h3
},
color = if (isRedesignEnabled) {
TangemTheme.colors2.text.neutral.primary
} else {
TangemTheme.colors.text.primary1
},
overflow = TextOverflow.Ellipsis,
maxLines = 1,
)
@ -98,9 +119,13 @@ internal fun MarketPulseBlock(marketChartConfig: MarketChartConfig, feedListCall
)
LazyRow(
modifier = Modifier.padding(vertical = 4.dp),
modifier = Modifier.padding(vertical = if (isRedesignEnabled) 12.dp else 4.dp),
verticalAlignment = Alignment.CenterVertically,
contentPadding = PaddingValues(16.dp),
contentPadding = if (isRedesignEnabled) {
PaddingValues(horizontal = 16.dp, vertical = 6.dp)
} else {
PaddingValues(16.dp)
},
horizontalArrangement = Arrangement.spacedBy(8.dp),
state = rememberLazyListState(),
) {
@ -108,16 +133,22 @@ internal fun MarketPulseBlock(marketChartConfig: MarketChartConfig, feedListCall
items = marketChartConfig.getFilterPreset(),
key = SortByTypeUM::name,
) { sortByTypeUM ->
ChartsFilterChip(
sortByTypeUM = sortByTypeUM,
isSelected = sortByTypeUM == marketChartConfig.currentSortByType,
onClick = { feedListCallbacks.onSortTypeClick(sortByTypeUM) },
)
if (isRedesignEnabled) {
TangemTab(
text = sortByTypeUM.text,
isChecked = sortByTypeUM == marketChartConfig.currentSortByType,
onCheckedChange = { feedListCallbacks.onSortTypeClick(sortByTypeUM) },
)
} else {
ChartsFilterChip(
sortByTypeUM = sortByTypeUM,
isSelected = sortByTypeUM == marketChartConfig.currentSortByType,
onClick = { feedListCallbacks.onSortTypeClick(sortByTypeUM) },
)
}
}
}
SpacerH(12.dp)
AnimatedContent(
targetState = marketChartConfig.currentSortByType,
label = "MarketPulseChartAnimation",
@ -141,9 +172,16 @@ private fun Charts(
onItemClick: (MarketsListItemUM) -> Unit,
modifier: Modifier = Modifier,
) {
val isRedesignEnabled = LocalRedesignEnabled.current
BlockCard(
modifier = modifier,
colors = TangemBlockCardColors.copy(containerColor = TangemTheme.colors.background.action),
colors = TangemBlockCardColors.copy(
containerColor = if (isRedesignEnabled) {
TangemTheme.colors2.surface.level3
} else {
TangemTheme.colors.background.action
},
),
) {
Column(modifier = Modifier.fillMaxWidth()) {
when (marketChart) {

View file

@ -2,28 +2,31 @@ package com.tangem.features.feed.ui.market.detailed.components
import android.content.res.Configuration
import androidx.annotation.StringRes
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.systemBars
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.shape.CircleShape
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.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.layout.layoutId
import androidx.compose.ui.platform.LocalDensity
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.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.CircleShimmer
import com.tangem.core.ui.components.RectangleShimmer
import com.tangem.core.ui.components.SpacerH
import com.tangem.core.ui.components.SpacerH12
import com.tangem.core.ui.components.appbar.TangemTopAppBar
import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM
@ -35,16 +38,22 @@ import com.tangem.core.ui.components.buttons.SmallButtonConfig
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.core.ui.components.token.TokenItem
import com.tangem.core.ui.components.token.state.TokenItemState
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.decorations.roundedShapeItemDecoration
import com.tangem.core.ui.ds.button.*
import com.tangem.core.ui.ds.image.TangemIcon
import com.tangem.core.ui.ds.row.TangemRowContainer
import com.tangem.core.ui.ds.row.TangemRowLayoutId
import com.tangem.core.ui.ds.topbar.TangemTopBar
import com.tangem.core.ui.ds.topbar.TangemTopBarType
import com.tangem.core.ui.extensions.*
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.features.feed.impl.R
import com.tangem.features.feed.ui.market.detailed.state.ExchangeItemUM
import com.tangem.features.feed.ui.market.detailed.state.ExchangesBottomSheetContent
import kotlinx.collections.immutable.toImmutableList
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet as TangemBottomSheetV2
/**
* Exchanges bottom sheet
@ -55,6 +64,15 @@ import kotlinx.collections.immutable.toImmutableList
*/
@Composable
internal fun ExchangesBottomSheet(config: TangemBottomSheetConfig) {
if (LocalRedesignEnabled.current) {
ExchangesBottomSheetV2(config)
} else {
ExchangesBottomSheetV1(config)
}
}
@Composable
private fun ExchangesBottomSheetV1(config: TangemBottomSheetConfig) {
val bottomBarHeight = with(LocalDensity.current) { WindowInsets.systemBars.getBottom(this).toDp() }
TangemBottomSheet<ExchangesBottomSheetContent>(
@ -98,6 +116,77 @@ internal fun ExchangesBottomSheet(config: TangemBottomSheetConfig) {
)
}
@Composable
private fun ExchangesBottomSheetV2(config: TangemBottomSheetConfig) {
val bottomBarHeight = with(LocalDensity.current) { WindowInsets.systemBars.getBottom(this).toDp() }
TangemBottomSheetV2<ExchangesBottomSheetContent>(
config = config,
title = { content ->
TangemTopBar(
title = resourceReference(content.titleResId),
type = TangemTopBarType.BottomSheet,
startContent = {
Icon(
imageVector = ImageVector.vectorResource(id = R.drawable.ic_arrow_back_28),
contentDescription = null,
tint = TangemTheme.colors2.graphic.neutral.primary,
modifier = Modifier
.size(TangemTheme.dimens2.x11)
.background(
color = TangemTheme.colors2.button.backgroundSecondary,
shape = CircleShape,
)
.clickableSingle(onClick = config.onDismissRequest)
.padding(TangemTheme.dimens2.x2),
)
},
)
},
content = { content ->
Box {
LazyColumn(
modifier = Modifier.fillMaxSize(),
contentPadding = PaddingValues(bottom = bottomBarHeight),
) {
item(key = "subtitle") {
Subtitle(
subtitleRes = content.subtitleResId,
volumeReference = content.volumeReference,
modifier = Modifier.padding(horizontal = TangemTheme.dimens2.x7),
)
}
if (content.exchangeItemsV2.isNotEmpty()) {
val lastIndex = content.exchangeItemsV2.lastIndex
itemsIndexed(
items = content.exchangeItemsV2,
key = { _, item -> item.id },
) { index, item ->
ExchangeItemRow(
exchangeItemUM = item,
modifier = Modifier
.roundedShapeItemDecoration(
currentIndex = index,
lastIndex = lastIndex,
backgroundColor = TangemTheme.colors2.surface.level3,
),
)
}
}
}
if (content is ExchangesBottomSheetContent.Error) {
Error(
content = content,
modifier = Modifier.align(Alignment.Center),
)
}
}
},
)
}
@Composable
private fun Title(@StringRes textResId: Int, onBackClick: () -> Unit) {
TangemTopAppBar(
@ -120,17 +209,36 @@ private fun Subtitle(@StringRes subtitleRes: Int, volumeReference: TextReference
@Composable
private fun SubtitleText(textReference: TextReference) {
Text(
text = textReference.resolveReference(),
color = TangemTheme.colors.text.tertiary,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
style = TangemTheme.typography.body2,
)
if (LocalRedesignEnabled.current) {
Text(
text = textReference.resolveReference(),
color = TangemTheme.colors2.text.neutral.secondary,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
style = TangemTheme.typography2.captionSemibold12,
)
} else {
Text(
text = textReference.resolveReference(),
color = TangemTheme.colors.text.tertiary,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
style = TangemTheme.typography.body2,
)
}
}
@Composable
private fun Error(content: ExchangesBottomSheetContent.Error, modifier: Modifier = Modifier) {
if (LocalRedesignEnabled.current) {
ErrorV2(content, modifier)
} else {
ErrorV1(content, modifier)
}
}
@Composable
private fun ErrorV1(content: ExchangesBottomSheetContent.Error, modifier: Modifier = Modifier) {
Column(
modifier = modifier,
verticalArrangement = Arrangement.Center,
@ -154,6 +262,155 @@ private fun Error(content: ExchangesBottomSheetContent.Error, modifier: Modifier
}
}
@Composable
private fun ErrorV2(content: ExchangesBottomSheetContent.Error, modifier: Modifier = Modifier) {
Column(
modifier = modifier,
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text(
text = stringResourceSafe(id = content.message),
color = TangemTheme.colors2.text.neutral.tertiary,
textAlign = TextAlign.Center,
style = TangemTheme.typography2.bodyRegular14,
)
SpacerH(8.dp)
TangemButton(
buttonUM = TangemButtonUM(
text = resourceReference(com.tangem.core.ui.R.string.try_to_load_data_again_button_title),
onClick = content.onRetryClick,
type = TangemButtonType.Secondary,
size = TangemButtonSize.X8,
shape = TangemButtonShape.Rounded,
),
)
}
}
@Composable
private fun ExchangeItemRow(exchangeItemUM: ExchangeItemUM, modifier: Modifier = Modifier) {
when (exchangeItemUM) {
is ExchangeItemUM.Content -> {
ExchangeItemRowContent(exchangeItemUM, modifier)
}
is ExchangeItemUM.Loading -> {
ExchangeItemRowPlaceholder(modifier)
}
}
}
@Composable
private fun ExchangeItemRowContent(exchangeItemUM: ExchangeItemUM.Content, modifier: Modifier = Modifier) {
TangemRowContainer(
modifier = modifier,
contentPadding = PaddingValues(horizontal = 12.dp, vertical = 14.dp),
) {
TangemIcon(
tangemIconUM = exchangeItemUM.icon,
modifier = Modifier
.layoutId(layoutId = TangemRowLayoutId.HEAD)
.size(TangemTheme.dimens2.x10),
)
Text(
modifier = Modifier
.padding(start = TangemTheme.dimens2.x2)
.layoutId(TangemRowLayoutId.START_TOP),
text = exchangeItemUM.title.resolveReference(),
style = TangemTheme.typography2.bodySemibold16,
color = TangemTheme.colors2.text.neutral.primary,
)
Text(
modifier = Modifier
.padding(start = TangemTheme.dimens2.x2)
.layoutId(TangemRowLayoutId.START_BOTTOM),
text = exchangeItemUM.subTitle.resolveReference(),
style = TangemTheme.typography2.captionSemibold12,
color = TangemTheme.colors2.text.neutral.secondary,
)
Text(
modifier = Modifier
.padding(start = TangemTheme.dimens2.x2)
.layoutId(TangemRowLayoutId.END_TOP),
text = exchangeItemUM.volumeInUsd.resolveReference(),
style = TangemTheme.typography2.bodySemibold16,
color = TangemTheme.colors2.text.neutral.primary,
)
Text(
modifier = Modifier
.background(
color = getColorByTrustValue(exchangeItemUM.auditLabel.type).copy(alpha = .1f),
shape = CircleShape,
)
.padding(vertical = 2.dp, horizontal = 6.dp)
.layoutId(TangemRowLayoutId.END_BOTTOM),
text = exchangeItemUM.auditLabel.text.resolveReference(),
style = TangemTheme.typography2.captionSemibold11,
color = getColorByTrustValue(exchangeItemUM.auditLabel.type),
)
}
}
@Composable
private fun ExchangeItemRowPlaceholder(modifier: Modifier = Modifier) {
TangemRowContainer(
modifier = modifier,
contentPadding = PaddingValues(horizontal = 12.dp, vertical = 14.dp),
) {
CircleShimmer(
modifier = Modifier
.size(40.dp)
.layoutId(TangemRowLayoutId.HEAD),
)
RectangleShimmer(
modifier = Modifier
.width(108.dp)
.height(20.dp)
.padding(start = 4.dp)
.layoutId(TangemRowLayoutId.START_TOP),
)
RectangleShimmer(
modifier = Modifier
.width(52.dp)
.height(16.dp)
.padding(start = 4.dp)
.layoutId(TangemRowLayoutId.START_BOTTOM),
)
RectangleShimmer(
modifier = Modifier
.width(106.dp)
.height(20.dp)
.padding(start = 4.dp)
.layoutId(TangemRowLayoutId.END_TOP),
)
RectangleShimmer(
modifier = Modifier
.width(52.dp)
.height(16.dp)
.padding(start = 4.dp)
.layoutId(TangemRowLayoutId.END_BOTTOM),
)
}
}
@Composable
private fun getColorByTrustValue(type: AuditLabelUM.Type): Color {
return when (type) {
AuditLabelUM.Type.Prohibition -> TangemTheme.colors2.fill.status.attention
AuditLabelUM.Type.Warning -> TangemTheme.colors2.fill.status.warning
AuditLabelUM.Type.Permit,
AuditLabelUM.Type.Info,
AuditLabelUM.Type.General,
-> TangemTheme.colors2.fill.status.accent
}
}
@Preview
@Preview(name = "Dark Theme", uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
@ -175,7 +432,7 @@ private class ExchangesBottomSheetContentProvider : CollectionPreviewParameterPr
listOf(
ExchangesBottomSheetContent.Loading(exchangesCount = 13),
ExchangesBottomSheetContent.Error(onRetryClick = {}),
ExchangesBottomSheetContent.Content(
ExchangesBottomSheetContent.ContentV1(
exchangeItems = List(size = 13) { index ->
TokenItemState.Content(
id = index.toString(),

View file

@ -1,30 +1,52 @@
package com.tangem.features.feed.ui.market.detailed.components
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.systemBars
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.layout.layoutId
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.vectorResource
import com.tangem.core.ui.components.SpacerH
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetType
import com.tangem.core.ui.components.bottomsheets.sheet.TangemBottomSheet
import com.tangem.core.ui.components.bottomsheets.sheet.TangemBottomSheetTitle
import com.tangem.core.ui.components.notifications.Notification
import com.tangem.core.ui.components.notifications.NotificationConfig
import com.tangem.core.ui.ds.row.TangemRowContainer
import com.tangem.core.ui.ds.row.TangemRowLayoutId
import com.tangem.core.ui.ds.topbar.TangemTopBar
import com.tangem.core.ui.ds.topbar.TangemTopBarType
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.clickableSingle
import com.tangem.core.ui.extensions.resolveReference
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.impl.R
import com.tangem.features.feed.ui.market.detailed.state.InfoBottomSheetContent
import dev.jeziellago.compose.markdowntext.MarkdownText
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet as TangemBottomSheetV2
@Composable
internal fun InfoBottomSheet(config: TangemBottomSheetConfig) {
if (LocalRedesignEnabled.current) {
InfoBottomSheetV2(config)
} else {
InfoBottomSheetV1(config)
}
}
@Composable
private fun InfoBottomSheetV1(config: TangemBottomSheetConfig) {
val bottomBarHeight = with(LocalDensity.current) { WindowInsets.systemBars.getBottom(this).toDp() }
TangemBottomSheet<InfoBottomSheetContent>(
@ -62,6 +84,89 @@ internal fun InfoBottomSheet(config: TangemBottomSheetConfig) {
)
}
@Suppress("LongMethod")
@Composable
private fun InfoBottomSheetV2(config: TangemBottomSheetConfig) {
val bottomBarHeight = with(LocalDensity.current) { WindowInsets.systemBars.getBottom(this).toDp() }
TangemBottomSheetV2<InfoBottomSheetContent>(
config = config,
type = TangemBottomSheetType.Modal,
containerColor = TangemTheme.colors2.surface.level3,
title = { content ->
TangemTopBar(
title = content.title,
type = TangemTopBarType.BottomSheet,
endContent = {
Icon(
imageVector = ImageVector.vectorResource(id = R.drawable.ic_close_24),
contentDescription = null,
tint = TangemTheme.colors2.graphic.neutral.primary,
modifier = Modifier
.size(TangemTheme.dimens2.x11)
.background(
color = TangemTheme.colors2.button.backgroundSecondary,
shape = CircleShape,
)
.clickableSingle(onClick = config.onDismissRequest)
.padding(TangemTheme.dimens2.x2_5),
)
},
)
},
content = { content ->
Column(
modifier = Modifier
.verticalScroll(rememberScrollState())
.padding(horizontal = TangemTheme.dimens2.x4),
) {
MarkdownText(
modifier = Modifier.padding(bottom = TangemTheme.dimens2.x3),
markdown = content.body.resolveReference(),
disableLinkMovementMethod = true,
linkifyMask = 0,
syntaxHighlightColor = TangemTheme.colors2.text.neutral.secondary,
style = TangemTheme.typography2.bodyRegular16.copy(
TangemTheme.colors2.text.neutral.secondary,
),
)
if (content.generatedAINotificationUM != null) {
TangemRowContainer(
modifier = Modifier
.background(
color = TangemTheme.colors2.surface.level4,
shape = RoundedCornerShape(TangemTheme.dimens2.x5),
)
.clickableSingle(onClick = content.generatedAINotificationUM.onClick),
contentPadding = PaddingValues(TangemTheme.dimens2.x3),
) {
Icon(
modifier = Modifier
.size(TangemTheme.dimens2.x10)
.padding(TangemTheme.dimens2.x1)
.layoutId(TangemRowLayoutId.HEAD),
tint = TangemTheme.colors2.markers.iconBlue,
contentDescription = null,
imageVector = ImageVector.vectorResource(R.drawable.ic_magic_28),
)
Text(
modifier = Modifier
.padding(start = TangemTheme.dimens2.x1)
.layoutId(TangemRowLayoutId.START_TOP),
text = stringResourceSafe(R.string.information_generated_with_ai),
style = TangemTheme.typography2.captionSemibold12,
color = TangemTheme.colors2.text.neutral.primary,
)
}
}
SpacerH(bottomBarHeight)
}
},
)
}
@Composable
private fun AdditionalInfoNotification(onClick: () -> Unit, modifier: Modifier = Modifier) {
Notification(

View file

@ -1,42 +1,71 @@
package com.tangem.features.feed.ui.market.detailed.components
import android.content.res.Configuration
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.*
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.material3.ripple
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
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.drawWithCache
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.BlendMode
import androidx.compose.ui.graphics.CompositingStrategy
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.layout.layoutId
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastForEach
import androidx.compose.ui.util.fastForEachIndexed
import coil.compose.SubcomposeAsyncImage
import coil.request.ImageRequest
import com.tangem.core.ui.components.*
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetType
import com.tangem.core.ui.components.bottomsheets.sheet.TangemBottomSheet
import com.tangem.core.ui.components.bottomsheets.sheet.TangemBottomSheetTitle
import com.tangem.core.ui.components.inputrow.inner.DividerContainer
import com.tangem.core.ui.decorations.roundedShapeItemDecoration
import com.tangem.core.ui.ds.row.TangemRowContainer
import com.tangem.core.ui.ds.row.TangemRowLayoutId
import com.tangem.core.ui.ds.topbar.TangemTopBar
import com.tangem.core.ui.ds.topbar.TangemTopBarType
import com.tangem.core.ui.extensions.clickableSingle
import com.tangem.core.ui.extensions.resolveReference
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 com.tangem.features.feed.impl.R
import com.tangem.features.feed.ui.market.detailed.preview.SecurityScorePreviewData
import com.tangem.features.feed.ui.market.detailed.state.SecurityScoreBottomSheetContent
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet as TangemBottomSheetV2
@Composable
internal fun SecurityScoreBottomSheet(config: TangemBottomSheetConfig) {
if (LocalRedesignEnabled.current) {
SecurityScoreBottomSheetV2(config)
} else {
SecurityScoreBottomSheetV1(config)
}
}
@Composable
private fun SecurityScoreBottomSheetV1(config: TangemBottomSheetConfig) {
val bottomBarHeight = with(LocalDensity.current) { WindowInsets.systemBars.getBottom(this).toDp() }
TangemBottomSheet<SecurityScoreBottomSheetContent>(
@ -82,6 +111,62 @@ internal fun SecurityScoreBottomSheet(config: TangemBottomSheetConfig) {
)
}
@Composable
private fun SecurityScoreBottomSheetV2(config: TangemBottomSheetConfig) {
val bottomBarHeight = with(LocalDensity.current) { WindowInsets.systemBars.getBottom(this).toDp() }
TangemBottomSheetV2<SecurityScoreBottomSheetContent>(
config = config,
type = TangemBottomSheetType.Modal,
title = { content ->
TangemTopBar(
title = content.title,
type = TangemTopBarType.BottomSheet,
endContent = {
Icon(
imageVector = ImageVector.vectorResource(id = R.drawable.ic_close_24),
contentDescription = null,
tint = TangemTheme.colors2.graphic.neutral.primary,
modifier = Modifier
.size(TangemTheme.dimens2.x11)
.background(
color = TangemTheme.colors2.button.backgroundSecondary,
shape = CircleShape,
)
.clickableSingle(onClick = config.onDismissRequest)
.padding(TangemTheme.dimens2.x2_5),
)
},
)
},
content = { content ->
Column(
modifier = Modifier
.verticalScroll(rememberScrollState())
.padding(horizontal = TangemTheme.dimens2.x4),
) {
Text(
text = content.description.resolveReference(),
style = TangemTheme.typography2.bodyRegular15.copy(
color = TangemTheme.colors2.text.neutral.tertiary,
),
)
SpacerH12()
content.providers.fastForEach { provider ->
SecurityScoreProviderRowV2(
providerUM = provider,
onLinkClick = { content.onProviderLinkClick(provider) },
)
SpacerH(TangemTheme.dimens2.x2)
}
SpacerH16()
SpacerH(bottomBarHeight)
}
},
)
}
@Composable
private fun SecurityScoreProviderRow(
providerUM: SecurityScoreBottomSheetContent.SecurityScoreProviderUM,
@ -145,17 +230,91 @@ private fun SecurityScoreProviderRow(
horizontalSpacing = TangemTheme.dimens.spacing3,
)
UrlBlock(providerUM)
UrlBlockV1(providerUM)
}
}
}
@Composable
private fun UrlBlock(providerUM: SecurityScoreBottomSheetContent.SecurityScoreProviderUM) {
private fun SecurityScoreProviderRowV2(
providerUM: SecurityScoreBottomSheetContent.SecurityScoreProviderUM,
onLinkClick: () -> Unit,
) {
TangemRowContainer(
modifier = Modifier
.background(
color = TangemTheme.colors2.surface.level4,
shape = RoundedCornerShape(TangemTheme.dimens2.x5),
)
.border(
width = 1.dp,
color = TangemTheme.colors2.border.neutral.primary,
shape = RoundedCornerShape(TangemTheme.dimens2.x5),
),
) {
SubcomposeAsyncImage(
modifier = Modifier
.size(size = TangemTheme.dimens.size40)
.clip(TangemTheme.shapes.roundedCorners8)
.layoutId(TangemRowLayoutId.HEAD),
model = ImageRequest.Builder(context = LocalContext.current)
.data(providerUM.iconUrl)
.crossfade(enable = true)
.allowHardware(false)
.build(),
loading = { RectangleShimmer(radius = TangemTheme.dimens2.x3) },
error = { RectangleShimmer(radius = TangemTheme.dimens2.x3) },
contentDescription = null,
)
Text(
modifier = Modifier
.padding(start = TangemTheme.dimens2.x2)
.layoutId(TangemRowLayoutId.START_TOP),
text = providerUM.name,
style = TangemTheme.typography2.bodySemibold16,
color = TangemTheme.colors2.text.neutral.primary,
)
providerUM.lastAuditDate?.let { auditDate ->
Text(
modifier = Modifier
.padding(start = TangemTheme.dimens2.x2)
.layoutId(TangemRowLayoutId.START_BOTTOM),
text = auditDate,
style = TangemTheme.typography2.captionSemibold12,
color = TangemTheme.colors2.text.neutral.secondary,
)
}
StarBlock(
score = providerUM.score,
modifier = Modifier
.padding(start = TangemTheme.dimens2.x2)
.clickableSingle(onClick = onLinkClick)
.layoutId(TangemRowLayoutId.END_TOP),
)
UrlBlockV2(
modifier = Modifier
.padding(start = TangemTheme.dimens2.x2)
.clickableSingle(onClick = onLinkClick)
.layoutId(TangemRowLayoutId.END_BOTTOM),
providerUM = providerUM,
)
}
}
@Composable
private fun UrlBlockV1(
providerUM: SecurityScoreBottomSheetContent.SecurityScoreProviderUM,
modifier: Modifier = Modifier,
) {
val urlData = providerUM.urlData
val rootHost = urlData?.rootHost
if (urlData != null && rootHost != null) {
Row(
modifier = modifier,
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing4),
) {
Text(
@ -174,10 +333,75 @@ private fun UrlBlock(providerUM: SecurityScoreBottomSheetContent.SecurityScorePr
}
}
@Composable
private fun UrlBlockV2(
providerUM: SecurityScoreBottomSheetContent.SecurityScoreProviderUM,
modifier: Modifier = Modifier,
) {
val urlData = providerUM.urlData
val rootHost = urlData?.rootHost
if (urlData != null && rootHost != null) {
Row(
modifier = modifier,
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x0_5),
) {
Text(
text = urlData.rootHost,
style = TangemTheme.typography2.captionSemibold12,
color = TangemTheme.colors2.text.neutral.secondary,
)
Icon(
modifier = Modifier
.size(TangemTheme.dimens2.x4),
imageVector = ImageVector.vectorResource(id = R.drawable.ic_arrow_top_right_24),
contentDescription = null,
tint = TangemTheme.colors2.markers.iconGray,
)
}
}
}
@Suppress("MagicNumber")
@Composable
fun StarBlock(score: Float, modifier: Modifier = Modifier) {
val grayColor = TangemTheme.colors2.markers.iconGray
val percentage = score / 5
Row(
modifier = modifier,
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x1),
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = score.toString(),
style = TangemTheme.typography2.bodySemibold16,
color = TangemTheme.colors2.text.neutral.primary,
)
Icon(
modifier = Modifier
.requiredSize(TangemTheme.dimens2.x5)
.graphicsLayer(compositingStrategy = CompositingStrategy.Offscreen)
.drawWithCache {
onDrawWithContent {
drawContent()
drawRect(
color = grayColor,
topLeft = Offset(x = size.width * percentage, y = 0f),
size = Size(size.width * (1 - percentage), size.height),
blendMode = BlendMode.SrcIn,
)
}
},
imageVector = ImageVector.vectorResource(R.drawable.ic_star_filled_20),
contentDescription = null,
tint = TangemTheme.colors2.markers.iconBlue,
)
}
}
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun SecurityScoreBottomSheetPreview() {
private fun SecurityScoreBottomSheetPreviewV1() {
TangemThemePreview {
SecurityScoreBottomSheet(
config = TangemBottomSheetConfig(
@ -187,4 +411,21 @@ private fun SecurityScoreBottomSheetPreview() {
),
)
}
}
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun SecurityScoreBottomSheetPreviewV2() {
TangemThemePreviewRedesign {
CompositionLocalProvider(LocalRedesignEnabled provides true) {
SecurityScoreBottomSheet(
config = TangemBottomSheetConfig(
isShown = true,
onDismissRequest = {},
content = SecurityScorePreviewData.bottomSheetContent,
),
)
}
}
}

View file

@ -0,0 +1,23 @@
package com.tangem.features.feed.ui.market.detailed.state
import androidx.compose.runtime.Immutable
import com.tangem.core.ui.components.audits.AuditLabelUM
import com.tangem.core.ui.ds.image.TangemIconUM
import com.tangem.core.ui.extensions.TextReference
@Immutable
internal sealed interface ExchangeItemUM {
val id: String
data class Content(
override val id: String,
val title: TextReference,
val subTitle: TextReference,
val icon: TangemIconUM,
val volumeInUsd: TextReference,
val auditLabel: AuditLabelUM,
) : ExchangeItemUM
data class Loading(override val id: String) : ExchangeItemUM
}

View file

@ -1,6 +1,7 @@
package com.tangem.features.feed.ui.market.detailed.state
import androidx.annotation.StringRes
import androidx.compose.runtime.Immutable
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
import com.tangem.core.ui.components.token.state.TokenItemState
import com.tangem.core.ui.extensions.TextReference
@ -18,6 +19,7 @@ import kotlinx.collections.immutable.toImmutableList
*
[REDACTED_AUTHOR]
*/
@Immutable
internal sealed interface ExchangesBottomSheetContent : TangemBottomSheetConfigContent {
/** Title of bottom sheet. Like, app bar. */
@ -39,6 +41,8 @@ internal sealed interface ExchangesBottomSheetContent : TangemBottomSheetConfigC
/** Exchange items */
val exchangeItems: ImmutableList<TokenItemState>
val exchangeItemsV2: ImmutableList<ExchangeItemUM>
/**
* Loading state
*
@ -49,6 +53,10 @@ internal sealed interface ExchangesBottomSheetContent : TangemBottomSheetConfigC
override val exchangeItems: ImmutableList<TokenItemState>
get() = List(size = exchangesCount) { index -> TokenItemState.Loading(id = "loading#$index") }
.toImmutableList()
override val exchangeItemsV2: ImmutableList<ExchangeItemUM>
get() = List(size = exchangesCount) { index -> ExchangeItemUM.Loading(id = "loading#$index") }
.toImmutableList()
}
/**
@ -56,8 +64,14 @@ internal sealed interface ExchangesBottomSheetContent : TangemBottomSheetConfigC
*
* @property exchangeItems exchanges
*/
data class Content(
data class ContentV1(
override val exchangeItems: ImmutableList<TokenItemState>,
override val exchangeItemsV2: ImmutableList<ExchangeItemUM> = persistentListOf(),
) : ExchangesBottomSheetContent
data class ContentV2(
override val exchangeItemsV2: ImmutableList<ExchangeItemUM>,
override val exchangeItems: ImmutableList<TokenItemState> = persistentListOf(),
) : ExchangesBottomSheetContent
/** Error state */
@ -65,6 +79,7 @@ internal sealed interface ExchangesBottomSheetContent : TangemBottomSheetConfigC
val onRetryClick: () -> Unit,
) : ExchangesBottomSheetContent {
override val exchangeItems: ImmutableList<TokenItemState> = persistentListOf()
override val exchangeItemsV2: ImmutableList<ExchangeItemUM> = persistentListOf()
@StringRes
val message: Int = R.string.markets_loading_error_title

View file

@ -15,6 +15,8 @@ import androidx.compose.ui.unit.dp
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.ds.TangemPagerIndicator
import com.tangem.core.ui.ds.TangemPagerIndicatorColors
import com.tangem.core.ui.extensions.conditionalCompose
import com.tangem.core.ui.res.*
import com.tangem.features.feed.ui.news.details.components.ArticleDetail
@ -105,12 +107,24 @@ private fun Content(state: NewsDetailsUM, background: Color) {
)
}
if (state.articles.size > 1) {
PagerIndicator(
pagerState = pagerState,
modifier = Modifier
.align(Alignment.BottomCenter)
.windowInsetsPadding(WindowInsets.navigationBars),
)
if (isRedesignEnabled) {
TangemPagerIndicator(
pagerState = pagerState,
modifier = Modifier
.align(Alignment.BottomCenter)
.windowInsetsPadding(WindowInsets.navigationBars),
colors = TangemPagerIndicatorColors.copy(
overlay = TangemTheme.colors2.tabs.backgroundSecondary.copy(alpha = .1f),
),
)
} else {
PagerIndicator(
pagerState = pagerState,
modifier = Modifier
.align(Alignment.BottomCenter)
.windowInsetsPadding(WindowInsets.navigationBars),
)
}
}
}
}

View file

@ -28,6 +28,7 @@ internal fun RelatedTokensBlock(
onItemClick: ((MarketsListItemUM) -> Unit)?,
modifier: Modifier = Modifier,
) {
val isRedesignEnabled = LocalRedesignEnabled.current
val isVisible = remember(relatedTokensUM) {
when (relatedTokensUM) {
is RelatedTokensUM.Content -> relatedTokensUM.items.isNotEmpty()
@ -40,7 +41,7 @@ internal fun RelatedTokensBlock(
Column(modifier = modifier) {
SpacerH(40.dp)
if (LocalRedesignEnabled.current) {
if (isRedesignEnabled) {
Text(
modifier = Modifier.padding(horizontal = 8.dp),
text = stringResourceSafe(R.string.news_related_tokens),
@ -58,7 +59,11 @@ internal fun RelatedTokensBlock(
BlockCard(
colors = TangemBlockCardColors.copy(
containerColor = TangemTheme.colors.background.action,
containerColor = if (isRedesignEnabled) {
TangemTheme.colors2.surface.level3
} else {
TangemTheme.colors.background.action
},
),
) {
Column(modifier = Modifier.fillMaxWidth()) {