Updated on 2026-08-14
This commit is contained in:
parent
ba1b0e1f84
commit
1470d4b08d
18 changed files with 1201 additions and 64 deletions
|
|
@ -7,7 +7,8 @@ import androidx.compose.animation.core.*
|
|||
import androidx.compose.foundation.Canvas
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
|
@ -21,6 +22,7 @@ import androidx.compose.ui.tooling.preview.Preview
|
|||
import androidx.compose.ui.unit.LayoutDirection
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.offset
|
||||
import com.tangem.core.ui.ds.progress.TangemLinearProgressIndicatorWithDot
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import kotlin.math.abs
|
||||
|
|
@ -278,6 +280,14 @@ private fun LinearProgressIndicator_Preview() {
|
|||
color = TangemTheme.colors.icon.primary1,
|
||||
backgroundColor = TangemTheme.colors.background.tertiary,
|
||||
)
|
||||
TangemLinearProgressIndicatorWithDot(
|
||||
progress = { 1f },
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(6.dp),
|
||||
dotColor = TangemTheme.colors.icon.primary1,
|
||||
backgroundColor = TangemTheme.colors.background.tertiary,
|
||||
)
|
||||
TangemLinearProgressIndicator(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
color = TangemTheme.colors.icon.primary1,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,159 @@
|
|||
package com.tangem.core.ui.ds.progress
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.Canvas
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.StrokeCap
|
||||
import androidx.compose.ui.graphics.drawscope.DrawScope
|
||||
import androidx.compose.ui.semantics.ProgressBarRangeInfo
|
||||
import androidx.compose.ui.semantics.progressBarRangeInfo
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.LayoutDirection
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.components.progressbar.increaseSemanticsBounds
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreviewRedesign
|
||||
import kotlin.math.abs
|
||||
|
||||
/**
|
||||
* Progress indicator with a dot that reflects the current progress position.
|
||||
* The dot is drawn on top of the background line. Dot diameter equals the track height.
|
||||
* At progress 0 the dot's left edge aligns with the track's left edge;
|
||||
* at progress 1 the dot's right edge aligns with the track's right edge.
|
||||
*
|
||||
* @param progress The progress of this indicator, where 0.0 represents no progress and 1.0
|
||||
* represents full progress. Values outside of this range are coerced into the range.
|
||||
* @param modifier the [Modifier] to be applied to this progress indicator
|
||||
* @param dotColor The color of the progress dot.
|
||||
* @param backgroundColor The color of the background track.
|
||||
* @param strokeCap stroke cap to use for the ends of the background track
|
||||
*/
|
||||
@Composable
|
||||
fun TangemLinearProgressIndicatorWithDot(
|
||||
progress: () -> Float,
|
||||
dotColor: Color,
|
||||
backgroundColor: Color,
|
||||
modifier: Modifier = Modifier,
|
||||
strokeCap: StrokeCap = StrokeCap.Round,
|
||||
) {
|
||||
val coercedProgress = { progress().coerceIn(0f, 1f) }
|
||||
Canvas(
|
||||
modifier
|
||||
.increaseSemanticsBounds()
|
||||
.semantics(mergeDescendants = true) {
|
||||
progressBarRangeInfo = ProgressBarRangeInfo(coercedProgress(), 0f..1f)
|
||||
},
|
||||
) {
|
||||
val strokeWidth = size.height
|
||||
drawLinearIndicatorBackground(backgroundColor, strokeWidth, strokeCap)
|
||||
drawLinearProgressDot(
|
||||
progress = coercedProgress(),
|
||||
color = dotColor,
|
||||
dotDiameter = strokeWidth,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun DrawScope.drawLinearIndicatorBackground(color: Color, strokeWidth: Float, strokeCap: StrokeCap) =
|
||||
drawLinearIndicator(
|
||||
startFraction = 0f,
|
||||
endFraction = 1f,
|
||||
color = color,
|
||||
strokeWidth = strokeWidth,
|
||||
strokeCap = strokeCap,
|
||||
)
|
||||
|
||||
private fun DrawScope.drawLinearProgressDot(progress: Float, color: Color, dotDiameter: Float) {
|
||||
val width = size.width
|
||||
val radius = dotDiameter / 2
|
||||
val yOffset = size.height / 2
|
||||
|
||||
val isLtr = layoutDirection == LayoutDirection.Ltr
|
||||
val centerX = if (isLtr) {
|
||||
radius + progress * (width - dotDiameter)
|
||||
} else {
|
||||
width - radius - progress * (width - dotDiameter)
|
||||
}
|
||||
|
||||
drawCircle(
|
||||
color = color,
|
||||
radius = radius,
|
||||
center = Offset(centerX, yOffset),
|
||||
)
|
||||
}
|
||||
|
||||
private fun DrawScope.drawLinearIndicator(
|
||||
startFraction: Float,
|
||||
endFraction: Float,
|
||||
color: Color,
|
||||
strokeWidth: Float,
|
||||
strokeCap: StrokeCap,
|
||||
) {
|
||||
val width = size.width
|
||||
val height = size.height
|
||||
// Start drawing from the vertical center of the stroke
|
||||
val yOffset = height / 2
|
||||
|
||||
val isLtr = layoutDirection == LayoutDirection.Ltr
|
||||
val barStart = (if (isLtr) startFraction else 1f - endFraction) * width
|
||||
val barEnd = (if (isLtr) endFraction else 1f - startFraction) * width
|
||||
|
||||
// if there isn't enough space to draw the stroke caps, fall back to StrokeCap.Butt
|
||||
if (strokeCap == StrokeCap.Butt || height > width) {
|
||||
// Progress line
|
||||
drawLine(
|
||||
color = color,
|
||||
start = Offset(barStart, yOffset),
|
||||
end = Offset(barEnd, yOffset),
|
||||
strokeWidth = strokeWidth,
|
||||
)
|
||||
} else {
|
||||
// need to adjust barStart and barEnd for the stroke caps
|
||||
val strokeCapOffset = strokeWidth / 2
|
||||
val coerceRange = strokeCapOffset..width - strokeCapOffset
|
||||
val adjustedBarStart = barStart.coerceIn(coerceRange)
|
||||
val adjustedBarEnd = barEnd.coerceIn(coerceRange)
|
||||
|
||||
if (abs(endFraction - startFraction) > 0) {
|
||||
// Progress line
|
||||
drawLine(
|
||||
color = color,
|
||||
start = Offset(adjustedBarStart, yOffset),
|
||||
end = Offset(adjustedBarEnd, yOffset),
|
||||
strokeWidth = strokeWidth,
|
||||
cap = strokeCap,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// region Preview
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun LinearProgressIndicator_Preview() {
|
||||
TangemThemePreviewRedesign {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.background(TangemTheme.colors.background.secondary)
|
||||
.padding(12.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp),
|
||||
) {
|
||||
TangemLinearProgressIndicatorWithDot(
|
||||
progress = { 1f },
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(6.dp),
|
||||
dotColor = TangemTheme.colors2.fill.status.accent,
|
||||
backgroundColor = TangemTheme.colors2.graphic.neutral.primaryInvertedConstant,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
// endregion
|
||||
9
core/ui/src/main/res/drawable/ic_big_laurel_left_20.xml
Normal file
9
core/ui/src/main/res/drawable/ic_big_laurel_left_20.xml
Normal file
File diff suppressed because one or more lines are too long
9
core/ui/src/main/res/drawable/ic_big_laurel_right_20.xml
Normal file
9
core/ui/src/main/res/drawable/ic_big_laurel_right_20.xml
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -16,6 +16,7 @@ import com.tangem.core.decompose.model.Model
|
|||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.core.navigation.share.ShareManager
|
||||
import com.tangem.core.navigation.url.UrlOpener
|
||||
import com.tangem.core.ui.DesignFeatureToggles
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
|
||||
import com.tangem.core.ui.components.marketprice.PriceChangeType
|
||||
|
|
@ -77,6 +78,7 @@ internal class MarketsTokenDetailsModel @Inject constructor(
|
|||
getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
|
||||
getUserCountryUseCase: GetUserCountryUseCase,
|
||||
paramsContainer: ParamsContainer,
|
||||
designFeatureToggles: DesignFeatureToggles,
|
||||
private val getTokenPriceChartUseCase: GetTokenPriceChartUseCase,
|
||||
private val getTokenMarketInfoUseCase: GetTokenMarketInfoUseCase,
|
||||
private val getTokenFullQuotesUseCase: GetTokenFullQuotesUseCase,
|
||||
|
|
@ -147,6 +149,7 @@ internal class MarketsTokenDetailsModel @Inject constructor(
|
|||
needApplyFCARestrictions = Provider {
|
||||
userCountry.needApplyFCARestrictions()
|
||||
},
|
||||
isRedesignEnabled = designFeatureToggles.isRedesignEnabled,
|
||||
// ==================
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ package com.tangem.features.feed.model.market.details.converter
|
|||
|
||||
import androidx.compose.runtime.Stable
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.core.ui.format.bigdecimal.compact
|
||||
import com.tangem.core.ui.format.bigdecimal.crypto
|
||||
import com.tangem.core.ui.format.bigdecimal.fiat
|
||||
|
|
@ -9,20 +11,23 @@ import com.tangem.core.ui.format.bigdecimal.format
|
|||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.markets.TokenMarketInfo
|
||||
import com.tangem.features.feed.impl.R
|
||||
import com.tangem.features.feed.ui.market.detailed.state.InfoBottomSheetContent
|
||||
import com.tangem.features.feed.ui.market.detailed.state.InfoPointUM
|
||||
import com.tangem.features.feed.ui.market.detailed.state.MetricsUM
|
||||
import com.tangem.features.feed.ui.market.detailed.state.*
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.StringsSigns
|
||||
import com.tangem.utils.converter.Converter
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import java.math.BigDecimal
|
||||
import java.math.RoundingMode
|
||||
|
||||
@Suppress("LargeClass")
|
||||
@Stable
|
||||
internal class MetricsConverter(
|
||||
private val appCurrency: Provider<AppCurrency>,
|
||||
private val tokenSymbol: String,
|
||||
private val onInfoClick: (InfoBottomSheetContent) -> Unit,
|
||||
private val isRedesignEnabled: Boolean,
|
||||
) : Converter<TokenMarketInfo.Metrics, MetricsUM> {
|
||||
|
||||
@Suppress("LongMethod")
|
||||
|
|
@ -115,10 +120,120 @@ internal class MetricsConverter(
|
|||
},
|
||||
),
|
||||
),
|
||||
metricsV2 = if (isRedesignEnabled) {
|
||||
convertToMetricsV2UM(value)
|
||||
} else {
|
||||
null
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("LongMethod", "NestedScopeFunctions")
|
||||
private fun convertToMetricsV2UM(value: TokenMarketInfo.Metrics): MetricsV2UM {
|
||||
val infoPoints = with(value) {
|
||||
val liquidity = getLiquidity(value.volume24h, value.marketCap)
|
||||
persistentListOf(
|
||||
InfoPointUMV2.MarketCap(
|
||||
capitalizationValue = stringReference(marketCap.formatAmount()),
|
||||
onInfoClick = {
|
||||
onInfoClick(
|
||||
InfoBottomSheetContent(
|
||||
title = resourceReference(
|
||||
R.string.markets_token_details_market_capitalization_full,
|
||||
),
|
||||
body = resourceReference(
|
||||
R.string.markets_token_details_market_capitalization_description,
|
||||
),
|
||||
),
|
||||
)
|
||||
},
|
||||
),
|
||||
InfoPointUMV2.TradingVolume(
|
||||
tradingValue = stringReference(volume24h.formatAmount()),
|
||||
liquidity = liquidity,
|
||||
trendingVolumeLiquidityType = getTrendingVolumeLiquidityType(liquidity),
|
||||
onInfoClick = {
|
||||
onInfoClick(
|
||||
InfoBottomSheetContent(
|
||||
title = resourceReference(R.string.markets_token_details_trading_volume_full),
|
||||
body = resourceReference(
|
||||
R.string.markets_token_details_trading_volume_24h_description,
|
||||
),
|
||||
),
|
||||
)
|
||||
},
|
||||
),
|
||||
InfoPointUMV2.MarketPosition(
|
||||
position = marketRating?.toString() ?: StringsSigns.DASH_SIGN,
|
||||
rangeValue = getMarketRatingRangeValue(marketRating),
|
||||
marketRatingType = getMarketRatingType(marketRating),
|
||||
onInfoClick = {
|
||||
onInfoClick(
|
||||
InfoBottomSheetContent(
|
||||
title = resourceReference(R.string.markets_token_details_market_rating_full),
|
||||
body = resourceReference(R.string.markets_token_details_market_rating_description),
|
||||
),
|
||||
)
|
||||
},
|
||||
),
|
||||
InfoPointUMV2.FullyDilutedValuation(
|
||||
value = resourceReference(
|
||||
R.string.markets_token_details_valuation_value_in_total,
|
||||
wrappedList(fullyDilutedValuation.formatAmount()),
|
||||
),
|
||||
onInfoClick = {
|
||||
onInfoClick(
|
||||
InfoBottomSheetContent(
|
||||
title = resourceReference(
|
||||
R.string.markets_token_details_fully_diluted_valuation_full,
|
||||
),
|
||||
body = resourceReference(
|
||||
R.string.markets_token_details_fully_diluted_valuation_description,
|
||||
),
|
||||
),
|
||||
)
|
||||
},
|
||||
),
|
||||
InfoPointUMV2.CirculatingSupply(
|
||||
currentValue = stringReference(circulatingSupply.formatAmount(crypto = true)),
|
||||
maxValue = maxSupply?.let { supply ->
|
||||
if (supply > BigDecimal.ZERO) {
|
||||
stringReference(supply.formatMaxSupply())
|
||||
} else {
|
||||
null
|
||||
}
|
||||
},
|
||||
fillValue = getCirculatingSupplyFillValue(circulatingSupply, maxSupply),
|
||||
onInfoClick = {
|
||||
onInfoClick(
|
||||
InfoBottomSheetContent(
|
||||
title = resourceReference(R.string.markets_token_details_max_supply_full),
|
||||
body = resourceReference(R.string.markets_token_details_total_supply_description),
|
||||
),
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
return buildMetricsV2UM(infoPoints)
|
||||
}
|
||||
|
||||
private fun buildMetricsV2UM(infoPoints: ImmutableList<InfoPointUMV2>): MetricsV2UM {
|
||||
val rows = infoPoints.chunked(size = 2)
|
||||
.map { chunk ->
|
||||
MetricsV2UM.Row(
|
||||
first = chunk.first(),
|
||||
second = chunk.getOrNull(1),
|
||||
)
|
||||
}
|
||||
|
||||
return MetricsV2UM(
|
||||
rows = rows.toImmutableList(),
|
||||
)
|
||||
}
|
||||
|
||||
private fun BigDecimal?.formatMaxSupply(): String {
|
||||
when (this) {
|
||||
null -> return StringsSigns.DASH_SIGN
|
||||
|
|
@ -149,4 +264,92 @@ internal class MetricsConverter(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
private fun getLiquidity(volume24h: BigDecimal?, marketCap: BigDecimal?): Float {
|
||||
if (volume24h == null || marketCap == null || marketCap == BigDecimal.ZERO) return 0f
|
||||
|
||||
val ratio = volume24h
|
||||
.divide(marketCap, 6, RoundingMode.HALF_UP)
|
||||
.toFloat()
|
||||
|
||||
return ratio.coerceIn(0f, 1f)
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
private fun getTrendingVolumeLiquidityType(liquidity: Float): TrendingVolumeLiquidityType {
|
||||
return when {
|
||||
liquidity >= 0.5f -> TrendingVolumeLiquidityType.HIGH
|
||||
liquidity in 0.2f..<0.5f -> TrendingVolumeLiquidityType.MEDIUM
|
||||
else -> TrendingVolumeLiquidityType.LOW
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
private fun getMarketRatingType(marketRating: Int?): MarketRatingType {
|
||||
return when (marketRating) {
|
||||
1 -> MarketRatingType.GOLD
|
||||
2 -> MarketRatingType.SILVER
|
||||
3 -> MarketRatingType.BRONZE
|
||||
else -> MarketRatingType.OTHER
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
private fun getMarketRatingRangeValue(marketRating: Int?): Float {
|
||||
if (marketRating == null) return 0f
|
||||
|
||||
return when {
|
||||
marketRating <= 20 -> {
|
||||
val segmentStart = 1f
|
||||
val segmentEnd = 0.75f
|
||||
val rangeMin = 1
|
||||
val rangeMax = 20
|
||||
val progress = segmentStart +
|
||||
(marketRating - rangeMin).toFloat() / (rangeMax - rangeMin) * (segmentEnd - segmentStart)
|
||||
progress.coerceIn(0f, 1f)
|
||||
}
|
||||
marketRating <= 100 -> {
|
||||
val segmentStart = 0.76f
|
||||
val segmentEnd = 0.5f
|
||||
val rangeMin = 21
|
||||
val rangeMax = 100
|
||||
val progress = segmentStart +
|
||||
(marketRating - rangeMin).toFloat() / (rangeMax - rangeMin) * (segmentEnd - segmentStart)
|
||||
progress.coerceIn(0f, 1f)
|
||||
}
|
||||
marketRating <= 1000 -> {
|
||||
val segmentStart = 0.51f
|
||||
val segmentEnd = 0.25f
|
||||
val rangeMin = 101
|
||||
val rangeMax = 1000
|
||||
val progress = segmentStart +
|
||||
(marketRating - rangeMin).toFloat() / (rangeMax - rangeMin) * (segmentEnd - segmentStart)
|
||||
progress.coerceIn(0f, 1f)
|
||||
}
|
||||
marketRating <= 10000 -> {
|
||||
val segmentStart = 0.26f
|
||||
val segmentEnd = 0.01f
|
||||
val rangeMin = 1001
|
||||
val rangeMax = 10000
|
||||
val progress = segmentStart +
|
||||
(marketRating - rangeMin).toFloat() / (rangeMax - rangeMin) * (segmentEnd - segmentStart)
|
||||
progress.coerceIn(0f, 1f)
|
||||
}
|
||||
else -> {
|
||||
0f
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
private fun getCirculatingSupplyFillValue(circulatingSupply: BigDecimal?, maxSupply: BigDecimal?): Float? {
|
||||
if (circulatingSupply == null || maxSupply == null || maxSupply == BigDecimal.ZERO) return null
|
||||
|
||||
val ratio = circulatingSupply
|
||||
.divide(maxSupply, 6, RoundingMode.HALF_UP)
|
||||
.toFloat()
|
||||
|
||||
return ratio.coerceIn(0f, 1f)
|
||||
}
|
||||
}
|
||||
|
|
@ -15,6 +15,7 @@ import com.tangem.utils.converter.Converter
|
|||
@Stable
|
||||
@Suppress("LongParameterList")
|
||||
internal class TokenMarketInfoConverter(
|
||||
private val isRedesignEnabled: Boolean,
|
||||
private val appCurrency: Provider<AppCurrency>,
|
||||
private val needApplyFCARestrictions: Provider<Boolean>,
|
||||
private val onInfoClick: (TangemBottomSheetConfigContent) -> Unit,
|
||||
|
|
@ -47,6 +48,7 @@ internal class TokenMarketInfoConverter(
|
|||
tokenSymbol = value.symbol,
|
||||
appCurrency = appCurrency,
|
||||
onInfoClick = onInfoClick,
|
||||
isRedesignEnabled = isRedesignEnabled,
|
||||
)
|
||||
|
||||
val exchangesAmount = value.exchangesAmount
|
||||
|
|
|
|||
|
|
@ -0,0 +1,77 @@
|
|||
package com.tangem.features.feed.ui.components
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.extensions.conditional
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreviewRedesign
|
||||
|
||||
@Composable
|
||||
internal fun MetricsCard(
|
||||
modifier: Modifier = Modifier,
|
||||
onClick: (() -> Unit)? = null,
|
||||
cardColor: Color = TangemTheme.colors2.surface.level3,
|
||||
title: @Composable () -> Unit,
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier
|
||||
.background(
|
||||
color = cardColor,
|
||||
shape = RoundedCornerShape(TangemTheme.dimens2.x5),
|
||||
)
|
||||
.conditional(
|
||||
condition = onClick != null,
|
||||
modifier = {
|
||||
if (onClick != null) {
|
||||
clickable(onClick = onClick)
|
||||
} else {
|
||||
this
|
||||
}
|
||||
},
|
||||
)
|
||||
.padding(TangemTheme.dimens2.x4),
|
||||
verticalArrangement = Arrangement.SpaceBetween,
|
||||
) {
|
||||
title()
|
||||
content()
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun MetricsCardPreview() {
|
||||
TangemThemePreviewRedesign {
|
||||
MetricsCard(
|
||||
modifier = Modifier.heightIn(120.dp),
|
||||
title = {
|
||||
Text(
|
||||
text = "$ 22.4 M",
|
||||
style = TangemTheme.typography2.headingBold22,
|
||||
color = TangemTheme.colors2.text.neutral.primary,
|
||||
)
|
||||
},
|
||||
content = {
|
||||
Text(
|
||||
text = "Market cap",
|
||||
style = TangemTheme.typography2.captionSemibold12,
|
||||
color = TangemTheme.colors2.text.neutral.tertiary,
|
||||
)
|
||||
},
|
||||
onClick = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -9,6 +9,7 @@ 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 com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
|
||||
import com.tangem.core.ui.components.bottomsheets.sheet.TangemBottomSheet
|
||||
|
|
@ -40,6 +41,8 @@ internal inline fun <reified T : TangemBottomSheetConfigContent> EarnFilterBotto
|
|||
style = TangemTheme.typography2.headingSemibold17,
|
||||
color = TangemTheme.colors2.text.neutral.primary,
|
||||
textAlign = TextAlign.Center,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
SecondaryTangemButton(
|
||||
modifier = Modifier.align(Alignment.CenterEnd),
|
||||
|
|
|
|||
|
|
@ -161,6 +161,8 @@ private fun NetworksTypesBlock(
|
|||
}.resolveReference(),
|
||||
style = TangemTheme.typography2.bodySemibold16,
|
||||
color = TangemTheme.colors2.text.neutral.primary,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
|
||||
TangemCheckbox(
|
||||
|
|
@ -189,6 +191,8 @@ private fun SpecificNetworksBlock(
|
|||
text = stringResourceSafe(id = R.string.earn_filter_networks),
|
||||
style = TangemTheme.typography2.bodyRegular14,
|
||||
color = TangemTheme.colors2.text.neutral.tertiary,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
specificNetworks.fastForEachIndexed { index, item ->
|
||||
TangemRowContainer(
|
||||
|
|
@ -215,6 +219,8 @@ private fun SpecificNetworksBlock(
|
|||
text = item.text,
|
||||
style = TangemTheme.typography2.bodySemibold16,
|
||||
color = TangemTheme.colors2.text.neutral.primary,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
|
||||
TangemCheckbox(
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import androidx.compose.material3.Text
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.layout.layoutId
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
|
|
@ -110,6 +111,8 @@ private fun ContentV2(content: EarnFilterByTypeBottomSheetContentUM) {
|
|||
text = type.text.resolveReference(),
|
||||
style = TangemTheme.typography2.bodySemibold16,
|
||||
color = TangemTheme.colors2.text.neutral.primary,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
|
||||
TangemCheckbox(
|
||||
|
|
|
|||
|
|
@ -4,10 +4,8 @@ import androidx.compose.foundation.clickable
|
|||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.requiredSize
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
|
|
@ -16,43 +14,37 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.conditional
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
||||
@Composable
|
||||
internal fun InformationTextBlock(
|
||||
text: TextReference,
|
||||
onInfoClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
textStyle: TextStyle = TangemTheme.typography2.captionSemibold12,
|
||||
textColor: Color = TangemTheme.colors2.text.neutral.secondary,
|
||||
onInfoClick: (() -> Unit)? = null,
|
||||
textColor: Color = TangemTheme.colors2.text.neutral.tertiary,
|
||||
infoIconColor: Color = TangemTheme.colors2.markers.iconGray,
|
||||
informationTextBlockIconPosition: InformationTextBlockIconPosition = InformationTextBlockIconPosition.START,
|
||||
) {
|
||||
val interactionSource = remember { MutableInteractionSource() }
|
||||
|
||||
val infoIcon: @Composable () -> Unit = {
|
||||
IconButton(
|
||||
modifier = Modifier.requiredSize(TangemTheme.dimens2.x4),
|
||||
interactionSource = interactionSource,
|
||||
onClick = onInfoClick,
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier.size(TangemTheme.dimens2.x4),
|
||||
imageVector = ImageVector.vectorResource(id = R.drawable.ic_information_24),
|
||||
tint = TangemTheme.colors2.markers.iconGray,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
Icon(
|
||||
modifier = Modifier.size(TangemTheme.dimens2.x4),
|
||||
imageVector = ImageVector.vectorResource(id = R.drawable.ic_information_24),
|
||||
tint = infoIconColor,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
|
||||
val contentText: @Composable () -> Unit = {
|
||||
Text(
|
||||
text = text.resolveReference(),
|
||||
style = textStyle,
|
||||
style = TangemTheme.typography2.captionSemibold12,
|
||||
color = textColor,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
|
|
@ -61,10 +53,17 @@ internal fun InformationTextBlock(
|
|||
|
||||
Row(
|
||||
modifier = modifier
|
||||
.clickable(
|
||||
interactionSource = interactionSource,
|
||||
indication = null,
|
||||
onClick = onInfoClick,
|
||||
.conditional(
|
||||
condition = onInfoClick != null,
|
||||
modifier = {
|
||||
onInfoClick?.let { infoClick ->
|
||||
clickable(
|
||||
interactionSource = interactionSource,
|
||||
indication = null,
|
||||
onClick = infoClick,
|
||||
)
|
||||
} ?: this
|
||||
},
|
||||
),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x1),
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
package com.tangem.features.feed.ui.market.detailed.components
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.components.RectangleShimmer
|
||||
import com.tangem.core.ui.components.TextButton
|
||||
import com.tangem.core.ui.components.TextShimmer
|
||||
import com.tangem.core.ui.components.block.information.GridItems
|
||||
|
|
@ -16,12 +16,16 @@ import com.tangem.core.ui.components.block.information.InformationBlock
|
|||
import com.tangem.core.ui.components.buttons.common.TangemButtonsDefaults
|
||||
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
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.utils.PreviewShimmerContainer
|
||||
import com.tangem.features.feed.impl.R
|
||||
import com.tangem.features.feed.ui.components.MetricsCard
|
||||
import com.tangem.features.feed.ui.market.detailed.state.InfoPointUM
|
||||
import com.tangem.features.feed.ui.market.detailed.state.InfoPointUMV2
|
||||
import com.tangem.features.feed.ui.market.detailed.state.MetricsUM
|
||||
import com.tangem.features.feed.ui.market.detailed.state.MetricsV2UM
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
|
|
@ -29,6 +33,17 @@ const val MAX_METRICS_COUNT = 6
|
|||
|
||||
@Composable
|
||||
internal fun MetricsBlock(state: MetricsUM, modifier: Modifier = Modifier) {
|
||||
if (LocalRedesignEnabled.current) {
|
||||
state.metricsV2?.let {
|
||||
MetricsBlockV2(it, modifier)
|
||||
}
|
||||
} else {
|
||||
MetricsBlockV1(state, modifier)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun MetricsBlockV1(state: MetricsUM, modifier: Modifier = Modifier) {
|
||||
var isExpanded by remember { mutableStateOf(false) }
|
||||
|
||||
InformationBlock(
|
||||
|
|
@ -64,6 +79,50 @@ internal fun MetricsBlock(state: MetricsUM, modifier: Modifier = Modifier) {
|
|||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun MetricsBlockV2(state: MetricsV2UM, modifier: Modifier = Modifier) {
|
||||
Column(
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x2),
|
||||
) {
|
||||
state.rows.forEach { row ->
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x2),
|
||||
) {
|
||||
Box(Modifier.weight(1f)) {
|
||||
MetricRowItem(row.first)
|
||||
}
|
||||
|
||||
row.second?.let { second ->
|
||||
Box(Modifier.weight(1f)) {
|
||||
MetricRowItem(second)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun MetricRowItem(item: InfoPointUMV2) {
|
||||
when (item) {
|
||||
is InfoPointUMV2.CirculatingSupply -> CirculatingSupplyCard(item)
|
||||
else -> MetricCard(item)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun MetricCard(item: InfoPointUMV2) {
|
||||
when (item) {
|
||||
is InfoPointUMV2.MarketCap -> MarketCapCard(item)
|
||||
is InfoPointUMV2.TradingVolume -> TradingVolumeCard(item)
|
||||
is InfoPointUMV2.MarketPosition -> MarketPositionCard(item)
|
||||
is InfoPointUMV2.FullyDilutedValuation -> FDVCard(item)
|
||||
is InfoPointUMV2.CirculatingSupply -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
// TODO make TextButton clickable area smaller and remove paddings for an action in InformationBlock
|
||||
@Composable
|
||||
private fun ShowLessMoreButton(expanded: Boolean, onClick: () -> Unit) {
|
||||
|
|
@ -84,6 +143,15 @@ private fun ShowLessMoreButton(expanded: Boolean, onClick: () -> Unit) {
|
|||
|
||||
@Composable
|
||||
internal fun MetricsBlockPlaceholder(modifier: Modifier = Modifier) {
|
||||
if (LocalRedesignEnabled.current) {
|
||||
MetricsBlockPlaceholderV2(modifier)
|
||||
} else {
|
||||
MetricsBlockPlaceholderV1(modifier)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun MetricsBlockPlaceholderV1(modifier: Modifier = Modifier) {
|
||||
InformationBlock(
|
||||
modifier = modifier,
|
||||
title = {
|
||||
|
|
@ -111,6 +179,108 @@ internal fun MetricsBlockPlaceholder(modifier: Modifier = Modifier) {
|
|||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun MetricsBlockPlaceholderV2(modifier: Modifier = Modifier) {
|
||||
Column(
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x2),
|
||||
) {
|
||||
repeat(2) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x2),
|
||||
) {
|
||||
repeat(2) {
|
||||
Box(Modifier.weight(1f)) {
|
||||
MetricsCard(
|
||||
modifier = Modifier
|
||||
.heightIn(120.dp)
|
||||
.fillMaxWidth(),
|
||||
title = {
|
||||
RectangleShimmer(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(28.dp)
|
||||
.padding(end = 10.dp),
|
||||
radius = TangemTheme.dimens2.x25,
|
||||
)
|
||||
},
|
||||
content = {
|
||||
RectangleShimmer(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(16.dp)
|
||||
.padding(end = 74.dp),
|
||||
radius = TangemTheme.dimens2.x25,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
CirculatingSupplyCardPlaceholder()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun CirculatingSupplyCardPlaceholder() {
|
||||
MetricsCard(
|
||||
modifier = Modifier
|
||||
.heightIn(120.dp)
|
||||
.fillMaxWidth(),
|
||||
title = {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp),
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
) {
|
||||
RectangleShimmer(
|
||||
modifier = Modifier
|
||||
.width(104.dp)
|
||||
.height(16.dp),
|
||||
radius = TangemTheme.dimens2.x25,
|
||||
)
|
||||
RectangleShimmer(
|
||||
modifier = Modifier
|
||||
.width(64.dp)
|
||||
.height(16.dp),
|
||||
radius = TangemTheme.dimens2.x25,
|
||||
)
|
||||
}
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
) {
|
||||
RectangleShimmer(
|
||||
modifier = Modifier
|
||||
.width(160.dp)
|
||||
.height(28.dp),
|
||||
radius = TangemTheme.dimens2.x25,
|
||||
)
|
||||
RectangleShimmer(
|
||||
modifier = Modifier
|
||||
.width(48.dp)
|
||||
.height(28.dp),
|
||||
radius = TangemTheme.dimens2.x25,
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
content = {
|
||||
RectangleShimmer(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(6.dp),
|
||||
radius = TangemTheme.dimens2.x25,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Preview("Dark Theme", uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
|
|
@ -150,6 +320,7 @@ private fun BlockPreview() {
|
|||
onInfoClick = {},
|
||||
),
|
||||
),
|
||||
metricsV2 = null,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,417 @@
|
|||
package com.tangem.features.feed.ui.market.detailed.components
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.LinearProgressIndicator
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.StrokeCap
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.layout.layoutId
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.tangem.core.ui.components.SpacerH
|
||||
import com.tangem.core.ui.components.progressbar.TangemLinearProgressIndicator
|
||||
import com.tangem.core.ui.ds.progress.TangemLinearProgressIndicatorWithDot
|
||||
import com.tangem.core.ui.ds.row.TangemRowContainer
|
||||
import com.tangem.core.ui.ds.row.TangemRowLayoutId
|
||||
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.res.LocalIsInDarkTheme
|
||||
import com.tangem.core.ui.res.LocalRedesignEnabled
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreviewRedesign
|
||||
import com.tangem.features.feed.impl.R
|
||||
import com.tangem.features.feed.ui.components.MetricsCard
|
||||
import com.tangem.features.feed.ui.market.detailed.state.InfoPointUMV2
|
||||
import com.tangem.features.feed.ui.market.detailed.state.MarketRatingType
|
||||
import com.tangem.features.feed.ui.market.detailed.state.TrendingVolumeLiquidityType
|
||||
|
||||
@Composable
|
||||
internal fun MarketCapCard(item: InfoPointUMV2.MarketCap) {
|
||||
MetricsCard(
|
||||
modifier = Modifier
|
||||
.heightIn(120.dp)
|
||||
.fillMaxWidth(),
|
||||
title = {
|
||||
Text(
|
||||
text = item.capitalizationValue.resolveReference(),
|
||||
style = TangemTheme.typography2.headingSemibold20,
|
||||
color = TangemTheme.colors2.text.neutral.primary,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
},
|
||||
content = {
|
||||
InformationTextBlock(
|
||||
text = resourceReference(R.string.markets_token_details_market_capitalization),
|
||||
onInfoClick = item.onInfoClick,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun TradingVolumeCard(item: InfoPointUMV2.TradingVolume) {
|
||||
val tradingColor = when (item.trendingVolumeLiquidityType) {
|
||||
TrendingVolumeLiquidityType.HIGH -> TangemTheme.colors2.markers.backgroundSolidGreen
|
||||
TrendingVolumeLiquidityType.MEDIUM -> TangemTheme.colors2.graphic.status.attention
|
||||
TrendingVolumeLiquidityType.LOW -> TangemTheme.colors2.graphic.status.warning
|
||||
}
|
||||
MetricsCard(
|
||||
modifier = Modifier
|
||||
.heightIn(120.dp)
|
||||
.fillMaxWidth(),
|
||||
title = {
|
||||
Row {
|
||||
Text(
|
||||
text = item.tradingValue.resolveReference(),
|
||||
style = TangemTheme.typography2.headingSemibold20,
|
||||
color = TangemTheme.colors2.text.neutral.primary,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier.padding(TangemTheme.dimens2.x1),
|
||||
text = stringResourceSafe(R.string.markets_token_details_trading_interval),
|
||||
style = TangemTheme.typography2.captionSemibold11,
|
||||
color = TangemTheme.colors2.text.neutral.primary,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
},
|
||||
content = {
|
||||
Column(modifier = Modifier.fillMaxWidth()) {
|
||||
TangemLinearProgressIndicator(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(6.dp),
|
||||
progress = { item.liquidity },
|
||||
color = tradingColor,
|
||||
backgroundColor = TangemTheme.colors2.graphic.neutral.primaryInvertedConstant.copy(alpha = .1f),
|
||||
)
|
||||
SpacerH(12.dp)
|
||||
InformationTextBlock(
|
||||
text = resourceReference(R.string.markets_token_details_trading_volume),
|
||||
textColor = tradingColor,
|
||||
infoIconColor = tradingColor,
|
||||
onInfoClick = item.onInfoClick,
|
||||
)
|
||||
}
|
||||
},
|
||||
cardColor = tradingColor.copy(alpha = .2f),
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun MarketPositionCard(item: InfoPointUMV2.MarketPosition) {
|
||||
val ratingCardColor = mapRatingToCardColor(marketRatingType = item.marketRatingType)
|
||||
val ratingColor = mapRatingToColor(marketRatingType = item.marketRatingType)
|
||||
|
||||
MetricsCard(
|
||||
modifier = Modifier
|
||||
.heightIn(120.dp)
|
||||
.fillMaxWidth(),
|
||||
title = {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Icon(
|
||||
imageVector = ImageVector.vectorResource(R.drawable.ic_big_laurel_left_20),
|
||||
tint = ratingColor,
|
||||
contentDescription = null,
|
||||
)
|
||||
|
||||
Text(
|
||||
textAlign = TextAlign.Center,
|
||||
text = item.position,
|
||||
color = ratingColor,
|
||||
style = TangemTheme.typography2.headingSemibold20.copy(letterSpacing = 0.sp),
|
||||
maxLines = 1,
|
||||
)
|
||||
|
||||
Icon(
|
||||
imageVector = ImageVector.vectorResource(R.drawable.ic_big_laurel_right_20),
|
||||
tint = ratingColor,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
},
|
||||
content = {
|
||||
Column(modifier = Modifier.fillMaxWidth()) {
|
||||
TangemLinearProgressIndicatorWithDot(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(6.dp),
|
||||
progress = { item.rangeValue },
|
||||
dotColor = TangemTheme.colors2.fill.neutral.primaryInvertedConstant,
|
||||
backgroundColor = TangemTheme.colors2.graphic.neutral.primaryInvertedConstant.copy(alpha = .1f),
|
||||
)
|
||||
SpacerH(12.dp)
|
||||
InformationTextBlock(
|
||||
text = resourceReference(R.string.markets_token_details_market_rating),
|
||||
textColor = ratingColor,
|
||||
infoIconColor = ratingColor,
|
||||
onInfoClick = item.onInfoClick,
|
||||
)
|
||||
}
|
||||
},
|
||||
cardColor = ratingCardColor,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun FDVCard(item: InfoPointUMV2.FullyDilutedValuation) {
|
||||
MetricsCard(
|
||||
modifier = Modifier
|
||||
.heightIn(120.dp)
|
||||
.fillMaxWidth(),
|
||||
title = {
|
||||
Text(
|
||||
text = item.value.resolveReference(),
|
||||
style = TangemTheme.typography2.headingSemibold20,
|
||||
color = TangemTheme.colors2.text.neutral.primary,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
},
|
||||
content = {
|
||||
InformationTextBlock(
|
||||
text = resourceReference(R.string.markets_token_details_fully_diluted_valuation),
|
||||
onInfoClick = item.onInfoClick,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun CirculatingSupplyCard(item: InfoPointUMV2.CirculatingSupply) {
|
||||
MetricsCard(
|
||||
modifier = Modifier
|
||||
.heightIn(min = if (item.fillValue == null) 88.dp else 114.dp)
|
||||
.fillMaxWidth(),
|
||||
title = {
|
||||
TangemRowContainer(contentPadding = PaddingValues(0.dp)) {
|
||||
Text(
|
||||
modifier = Modifier.layoutId(TangemRowLayoutId.START_TOP),
|
||||
text = stringResourceSafe(R.string.markets_token_details_circulating_supply),
|
||||
style = TangemTheme.typography2.captionSemibold13,
|
||||
color = TangemTheme.colors2.text.neutral.tertiary,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.padding(top = 12.dp)
|
||||
.layoutId(TangemRowLayoutId.START_BOTTOM),
|
||||
text = item.currentValue.resolveReference(),
|
||||
style = TangemTheme.typography2.headingSemibold20,
|
||||
color = TangemTheme.colors2.text.neutral.primary,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
|
||||
Text(
|
||||
modifier = Modifier.layoutId(TangemRowLayoutId.END_TOP),
|
||||
text = stringResourceSafe(R.string.markets_token_details_max_supply),
|
||||
style = TangemTheme.typography2.captionSemibold13,
|
||||
color = TangemTheme.colors2.text.neutral.tertiary,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
|
||||
if (item.maxValue != null) {
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.padding(top = 12.dp)
|
||||
.layoutId(TangemRowLayoutId.END_BOTTOM),
|
||||
text = item.maxValue.resolveReference(),
|
||||
style = TangemTheme.typography2.headingSemibold20,
|
||||
color = TangemTheme.colors2.text.neutral.primary,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
content = {
|
||||
if (item.fillValue != null) {
|
||||
LinearProgressIndicator(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(6.dp),
|
||||
color = TangemTheme.colors2.graphic.status.accent,
|
||||
trackColor = TangemTheme.colors2.graphic.neutral.primaryInvertedConstant.copy(alpha = .1f),
|
||||
progress = { item.fillValue },
|
||||
strokeCap = StrokeCap.Round,
|
||||
drawStopIndicator = {},
|
||||
gapSize = 4.dp,
|
||||
)
|
||||
}
|
||||
},
|
||||
onClick = item.onInfoClick,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun MarketRatingType.baseColor(): Color {
|
||||
val isDarkTheme = LocalIsInDarkTheme.current
|
||||
|
||||
return when (this) {
|
||||
MarketRatingType.GOLD ->
|
||||
if (isDarkTheme) Color(GOLD_PLACE_COLOR_NIGHT) else Color(GOLD_PLACE_COLOR_LIGHT)
|
||||
|
||||
MarketRatingType.SILVER ->
|
||||
if (isDarkTheme) Color(SILVER_PLACE_COLOR_NIGHT) else Color(SILVER_PLACE_COLOR_LIGHT)
|
||||
|
||||
MarketRatingType.BRONZE ->
|
||||
if (isDarkTheme) Color(BRONZE_PLACE_COLOR_NIGHT) else Color(BRONZE_PLACE_COLOR_LIGHT)
|
||||
|
||||
MarketRatingType.OTHER ->
|
||||
TangemTheme.colors2.graphic.neutral.primary
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun mapRatingToColor(marketRatingType: MarketRatingType): Color = marketRatingType.baseColor()
|
||||
|
||||
@Composable
|
||||
private fun mapRatingToCardColor(marketRatingType: MarketRatingType): Color {
|
||||
return when (marketRatingType) {
|
||||
MarketRatingType.OTHER -> TangemTheme.colors2.surface.level3
|
||||
else -> marketRatingType.baseColor().copy(alpha = 0.3f)
|
||||
}
|
||||
}
|
||||
|
||||
private const val GOLD_PLACE_COLOR_NIGHT = 0xFFFBEE76
|
||||
private const val GOLD_PLACE_COLOR_LIGHT = 0xFFD9B900
|
||||
private const val SILVER_PLACE_COLOR_NIGHT = 0xFFAABEF7
|
||||
private const val SILVER_PLACE_COLOR_LIGHT = 0xFF6680CC
|
||||
private const val BRONZE_PLACE_COLOR_NIGHT = 0xFFFF9976
|
||||
private const val BRONZE_PLACE_COLOR_LIGHT = 0xFFCC7F66
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@Preview(widthDp = 360, heightDp = 1500, showBackground = true)
|
||||
@Preview(widthDp = 360, heightDp = 1500, showBackground = true, locale = "ru")
|
||||
@Preview(widthDp = 360, heightDp = 1500, showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun MetricsCardsPreview() {
|
||||
CompositionLocalProvider(LocalRedesignEnabled provides true) {
|
||||
TangemThemePreviewRedesign {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.background(TangemTheme.colors2.surface.level2)
|
||||
.padding(16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp),
|
||||
) {
|
||||
MarketCapCard(
|
||||
item = InfoPointUMV2.MarketCap(
|
||||
capitalizationValue = stringReference("$ 1.2 T"),
|
||||
onInfoClick = {},
|
||||
),
|
||||
)
|
||||
|
||||
TradingVolumeCard(
|
||||
item = InfoPointUMV2.TradingVolume(
|
||||
tradingValue = stringReference("$ 45.2 M"),
|
||||
liquidity = 0.75f,
|
||||
trendingVolumeLiquidityType = TrendingVolumeLiquidityType.HIGH,
|
||||
onInfoClick = {},
|
||||
),
|
||||
)
|
||||
|
||||
TradingVolumeCard(
|
||||
item = InfoPointUMV2.TradingVolume(
|
||||
tradingValue = stringReference("$ 12.1 M"),
|
||||
liquidity = 0.45f,
|
||||
trendingVolumeLiquidityType = TrendingVolumeLiquidityType.MEDIUM,
|
||||
onInfoClick = {},
|
||||
),
|
||||
)
|
||||
|
||||
TradingVolumeCard(
|
||||
item = InfoPointUMV2.TradingVolume(
|
||||
tradingValue = stringReference("$ 2.3 M"),
|
||||
liquidity = 0.15f,
|
||||
trendingVolumeLiquidityType = TrendingVolumeLiquidityType.LOW,
|
||||
onInfoClick = {},
|
||||
),
|
||||
)
|
||||
|
||||
MarketPositionCard(
|
||||
item = InfoPointUMV2.MarketPosition(
|
||||
position = "1",
|
||||
rangeValue = 0.02f,
|
||||
marketRatingType = MarketRatingType.GOLD,
|
||||
onInfoClick = {},
|
||||
),
|
||||
)
|
||||
|
||||
MarketPositionCard(
|
||||
item = InfoPointUMV2.MarketPosition(
|
||||
position = "2",
|
||||
rangeValue = 0.05f,
|
||||
marketRatingType = MarketRatingType.SILVER,
|
||||
onInfoClick = {},
|
||||
),
|
||||
)
|
||||
|
||||
MarketPositionCard(
|
||||
item = InfoPointUMV2.MarketPosition(
|
||||
position = "3",
|
||||
rangeValue = 0.08f,
|
||||
marketRatingType = MarketRatingType.BRONZE,
|
||||
onInfoClick = {},
|
||||
),
|
||||
)
|
||||
|
||||
MarketPositionCard(
|
||||
item = InfoPointUMV2.MarketPosition(
|
||||
position = "42",
|
||||
rangeValue = 0.42f,
|
||||
marketRatingType = MarketRatingType.OTHER,
|
||||
onInfoClick = {},
|
||||
),
|
||||
)
|
||||
|
||||
FDVCard(
|
||||
item = InfoPointUMV2.FullyDilutedValuation(
|
||||
value = stringReference("$ 1.5 T"),
|
||||
onInfoClick = {},
|
||||
),
|
||||
)
|
||||
|
||||
CirculatingSupplyCard(
|
||||
item = InfoPointUMV2.CirculatingSupply(
|
||||
currentValue = stringReference("12.5 B POL"),
|
||||
maxValue = stringReference("21 B POL"),
|
||||
fillValue = 0.6f,
|
||||
onInfoClick = {},
|
||||
),
|
||||
)
|
||||
|
||||
CirculatingSupplyCard(
|
||||
item = InfoPointUMV2.CirculatingSupply(
|
||||
currentValue = stringReference("18.9 M ETH"),
|
||||
maxValue = null,
|
||||
fillValue = null,
|
||||
onInfoClick = {},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -27,6 +27,7 @@ import com.tangem.core.ui.res.TangemThemePreview
|
|||
import com.tangem.core.ui.res.TangemThemePreviewRedesign
|
||||
import com.tangem.core.ui.utils.PreviewShimmerContainer
|
||||
import com.tangem.features.feed.impl.R
|
||||
import com.tangem.features.feed.ui.components.ContainerWithDivider
|
||||
import com.tangem.features.feed.ui.market.detailed.state.SecurityScoreUM
|
||||
|
||||
@Composable
|
||||
|
|
@ -79,39 +80,44 @@ private fun SecurityScoreBlockV1(state: SecurityScoreUM, modifier: Modifier = Mo
|
|||
|
||||
@Composable
|
||||
private fun SecurityScoreBlockV2(state: SecurityScoreUM, modifier: Modifier = Modifier) {
|
||||
TangemRowContainer(modifier = modifier) {
|
||||
Text(
|
||||
modifier = Modifier.layoutId(layoutId = TangemRowLayoutId.START_TOP),
|
||||
text = "${state.score}",
|
||||
color = TangemTheme.colors2.text.neutral.primary,
|
||||
style = TangemTheme.typography2.headingBold28,
|
||||
)
|
||||
ContainerWithDivider(
|
||||
modifier = modifier,
|
||||
showDivider = true,
|
||||
) {
|
||||
TangemRowContainer(modifier = Modifier.padding(top = 20.dp, bottom = 24.dp)) {
|
||||
Text(
|
||||
modifier = Modifier.layoutId(layoutId = TangemRowLayoutId.START_TOP),
|
||||
text = "${state.score}",
|
||||
color = TangemTheme.colors2.text.neutral.primary,
|
||||
style = TangemTheme.typography2.headingBold28,
|
||||
)
|
||||
|
||||
InformationTextBlock(
|
||||
modifier = Modifier.layoutId(layoutId = TangemRowLayoutId.START_BOTTOM),
|
||||
text = resourceReference(R.string.markets_token_details_security_score),
|
||||
onInfoClick = state.onInfoClick,
|
||||
textColor = TangemTheme.colors2.text.neutral.primary,
|
||||
informationTextBlockIconPosition = InformationTextBlockIconPosition.END,
|
||||
)
|
||||
InformationTextBlock(
|
||||
modifier = Modifier.layoutId(layoutId = TangemRowLayoutId.START_BOTTOM),
|
||||
text = resourceReference(R.string.markets_token_details_security_score),
|
||||
onInfoClick = state.onInfoClick,
|
||||
textColor = TangemTheme.colors2.text.neutral.primary,
|
||||
informationTextBlockIconPosition = InformationTextBlockIconPosition.END,
|
||||
)
|
||||
|
||||
Text(
|
||||
modifier = Modifier.layoutId(layoutId = TangemRowLayoutId.END_BOTTOM),
|
||||
text = state.description.resolveReference(),
|
||||
style = TangemTheme.typography2.captionSemibold12,
|
||||
color = TangemTheme.colors2.text.neutral.tertiary,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier.layoutId(layoutId = TangemRowLayoutId.END_BOTTOM),
|
||||
text = state.description.resolveReference(),
|
||||
style = TangemTheme.typography2.captionSemibold12,
|
||||
color = TangemTheme.colors2.text.neutral.tertiary,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
|
||||
ScoreStarsBlock(
|
||||
modifier = Modifier
|
||||
.padding(bottom = 16.dp)
|
||||
.layoutId(layoutId = TangemRowLayoutId.END_TOP),
|
||||
score = state.score,
|
||||
scoreTextStyle = TangemTheme.typography.body1,
|
||||
horizontalSpacing = TangemTheme.dimens.spacing8,
|
||||
)
|
||||
ScoreStarsBlock(
|
||||
modifier = Modifier
|
||||
.padding(bottom = 16.dp)
|
||||
.layoutId(layoutId = TangemRowLayoutId.END_TOP),
|
||||
score = state.score,
|
||||
scoreTextStyle = TangemTheme.typography.body1,
|
||||
horizontalSpacing = TangemTheme.dimens.spacing8,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -307,9 +307,7 @@ private fun LazyListScope.loadingInfoBlocksV2() {
|
|||
}
|
||||
|
||||
item("insights-loading") {
|
||||
InsightsBlockPlaceholder(
|
||||
modifier = Modifier.blockPaddings(),
|
||||
)
|
||||
InsightsBlockPlaceholder(modifier = Modifier.blockPaddings())
|
||||
}
|
||||
|
||||
item(key = "listedOn-loading") {
|
||||
|
|
|
|||
|
|
@ -104,6 +104,7 @@ internal object MarketsTokenDetailsPreview {
|
|||
infoPoint,
|
||||
infoPoint,
|
||||
),
|
||||
metricsV2 = null,
|
||||
),
|
||||
pricePerformance = PricePerformanceUM(
|
||||
h24 = PricePerformanceUM.Value(
|
||||
|
|
|
|||
|
|
@ -1,7 +1,68 @@
|
|||
package com.tangem.features.feed.ui.market.detailed.state
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
internal data class MetricsUM(
|
||||
val metrics: ImmutableList<InfoPointUM>,
|
||||
)
|
||||
val metricsV2: MetricsV2UM?,
|
||||
)
|
||||
|
||||
@Immutable
|
||||
internal sealed interface InfoPointUMV2 {
|
||||
|
||||
@Immutable
|
||||
data class MarketCap(
|
||||
val capitalizationValue: TextReference,
|
||||
val onInfoClick: () -> Unit,
|
||||
) : InfoPointUMV2
|
||||
|
||||
@Immutable
|
||||
data class TradingVolume(
|
||||
val tradingValue: TextReference,
|
||||
val liquidity: Float,
|
||||
val trendingVolumeLiquidityType: TrendingVolumeLiquidityType,
|
||||
val onInfoClick: () -> Unit,
|
||||
) : InfoPointUMV2
|
||||
|
||||
@Immutable
|
||||
data class MarketPosition(
|
||||
val position: String,
|
||||
val rangeValue: Float,
|
||||
val marketRatingType: MarketRatingType,
|
||||
val onInfoClick: () -> Unit,
|
||||
) : InfoPointUMV2
|
||||
|
||||
@Immutable
|
||||
data class FullyDilutedValuation(
|
||||
val value: TextReference,
|
||||
val onInfoClick: () -> Unit,
|
||||
) : InfoPointUMV2
|
||||
|
||||
@Immutable
|
||||
data class CirculatingSupply(
|
||||
val currentValue: TextReference,
|
||||
val maxValue: TextReference?,
|
||||
val fillValue: Float?,
|
||||
val onInfoClick: () -> Unit,
|
||||
) : InfoPointUMV2
|
||||
}
|
||||
|
||||
internal data class MetricsV2UM(
|
||||
val rows: ImmutableList<Row>,
|
||||
) {
|
||||
|
||||
internal data class Row(
|
||||
val first: InfoPointUMV2,
|
||||
val second: InfoPointUMV2?,
|
||||
)
|
||||
}
|
||||
|
||||
internal enum class TrendingVolumeLiquidityType {
|
||||
HIGH, MEDIUM, LOW,
|
||||
}
|
||||
|
||||
internal enum class MarketRatingType {
|
||||
GOLD, SILVER, BRONZE, OTHER
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue