diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml
index 2306a9be2e..da1a8d484c 100644
--- a/core/res/src/main/res/values/strings.xml
+++ b/core/res/src/main/res/values/strings.xml
@@ -768,6 +768,7 @@
This asset is not available for this wallet
Add
APY %s
+ Market Price
My portfolio
Market
Staking & Yield mode
diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/token/block/impl/ui/TokenMarketBlock.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/token/block/impl/ui/TokenMarketBlock.kt
index 493d0af09c..66ce42ea67 100644
--- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/token/block/impl/ui/TokenMarketBlock.kt
+++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/token/block/impl/ui/TokenMarketBlock.kt
@@ -1,25 +1,152 @@
package com.tangem.features.markets.token.block.impl.ui
+import android.content.res.Configuration
+import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.requiredSize
+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.draw.clip
+import androidx.compose.ui.layout.layoutId
+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.PreviewParameterProvider
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import com.tangem.common.ui.charts.MarketChartMini
+import com.tangem.common.ui.charts.state.MarketChartRawData
+import com.tangem.core.ui.components.RectangleShimmer
+import com.tangem.core.ui.components.marketprice.PriceChangeState
+import com.tangem.core.ui.components.marketprice.PriceChangeType
+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.ds.image.TangemIconUM
+import com.tangem.core.ui.ds.row.TangemRowContainer
+import com.tangem.core.ui.ds.row.TangemRowLayoutId
+import com.tangem.core.ui.ds.row.token.internal.TokenRowPriceChangeContent
+import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
+import com.tangem.core.ui.res.TangemThemePreviewRedesign
+import com.tangem.core.ui.R as CoreR
+import com.tangem.features.markets.impl.R
+import com.tangem.features.markets.token.block.impl.model.formatter.toChartType
import com.tangem.features.markets.token.block.impl.ui.state.TokenMarketBlockUM
+import kotlinx.collections.immutable.toImmutableList
+import kotlin.random.Random
+
+private val ChartWidth: Dp = 52.dp
+private val ChartHeight: Dp = 32.dp
-@Suppress("UnusedParameter")
@Composable
internal fun TokenMarketBlock(tokenMarketBlockUM: TokenMarketBlockUM, modifier: Modifier = Modifier) {
- Box(
- modifier = modifier.fillMaxWidth(),
- contentAlignment = Alignment.Center,
+ TangemRowContainer(
+ modifier = modifier
+ .fillMaxWidth()
+ .clip(RoundedCornerShape(TangemTheme.dimens2.x5))
+ .background(TangemTheme.colors2.surface.level3),
) {
Text(
- text = "Market Block Redesign",
- style = TangemTheme.typography.body2,
- color = TangemTheme.colors.text.primary1,
+ text = stringResourceSafe(id = R.string.markets_common_market_price),
+ style = TangemTheme.typography2.bodySemibold16,
+ color = TangemTheme.colors2.text.neutral.primary,
+ modifier = Modifier.layoutId(TangemRowLayoutId.START_TOP),
+ )
+
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = Modifier.layoutId(TangemRowLayoutId.START_BOTTOM),
+ ) {
+ Text(
+ text = tokenMarketBlockUM.currentPrice.orEmpty(),
+ maxLines = 1,
+ overflow = TextOverflow.Ellipsis,
+ style = TangemTheme.typography2.captionMedium12,
+ color = TangemTheme.colors2.text.neutral.primary,
+ )
+ TokenRowPriceChangeContent(
+ priceChangeState = PriceChangeState.Content(
+ type = tokenMarketBlockUM.priceChangeType,
+ valueInPercent = tokenMarketBlockUM.h24Percent.orEmpty(),
+ ),
+ isFlickering = false,
+ )
+ }
+
+ Box(
+ modifier = Modifier.layoutId(TangemRowLayoutId.END_BOTTOM),
+ ) {
+ val chartModifier = Modifier.requiredSize(width = ChartWidth, height = ChartHeight)
+
+ if (tokenMarketBlockUM.chartData != null) {
+ MarketChartMini(
+ rawData = tokenMarketBlockUM.chartData,
+ type = tokenMarketBlockUM.priceChangeType.toChartType(),
+ modifier = chartModifier,
+ )
+ } else {
+ RectangleShimmer(modifier = chartModifier)
+ }
+ }
+
+ SecondaryTangemButton(
+ onClick = tokenMarketBlockUM.onClick,
+ tangemIconUM = TangemIconUM.Icon(iconRes = CoreR.drawable.ic_arrow_expand_24),
+ shape = TangemButtonShape.Rounded,
+ size = TangemButtonSize.X10,
+ modifier = Modifier
+ .layoutId(TangemRowLayoutId.TAIL)
+ .padding(start = TangemTheme.dimens2.x10),
)
}
-}
\ No newline at end of file
+}
+
+// region Preview
+@Composable
+@Preview(showBackground = true, widthDp = 360)
+@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
+private fun TokenMarketBlock_Preview(
+ @PreviewParameter(TokenMarketBlockPreviewProvider::class) params: TokenMarketBlockUM,
+) {
+ TangemThemePreviewRedesign {
+ TokenMarketBlock(
+ tokenMarketBlockUM = params,
+ modifier = Modifier.background(TangemTheme.colors2.surface.level3),
+ )
+ }
+}
+
+private class TokenMarketBlockPreviewProvider : PreviewParameterProvider {
+
+ private val data = MarketChartRawData(
+ x = List(size = 20) { Random.nextFloat().toDouble() }.toImmutableList(),
+ y = List(size = 20) { Random.nextFloat().toDouble() }.toImmutableList(),
+ )
+
+ private val state = TokenMarketBlockUM(
+ currencySymbol = "XRP",
+ currentPrice = "0,5$",
+ currentPriceValue = null,
+ priceAnnotated = null,
+ h24Percent = "0,5%",
+ priceChangeType = PriceChangeType.UP,
+ chartData = data,
+ onClick = {},
+ )
+
+ override val values: Sequence
+ get() = sequenceOf(
+ state,
+ state.copy(currentPrice = "0,0000000000012356786789$"),
+ state.copy(currentPrice = null, chartData = null),
+ state.copy(chartData = null),
+ )
+}
+// endregion
\ No newline at end of file
diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/DefaultTokenDetailsComponent.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/DefaultTokenDetailsComponent.kt
index 639595b1f4..035e4511bf 100644
--- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/DefaultTokenDetailsComponent.kt
+++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/DefaultTokenDetailsComponent.kt
@@ -97,6 +97,7 @@ internal class DefaultTokenDetailsComponent @AssistedInject constructor(
TokenDetailsScreen(
tokenDetailsUM = tokenDetailsUM,
+ tokenMarketBlockComponent = tokenMarketBlockComponent,
modifier = modifier,
)
} else {
diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/TokenDetailsScreen.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/TokenDetailsScreen.kt
index 14147b34e3..0783260183 100644
--- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/TokenDetailsScreen.kt
+++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/TokenDetailsScreen.kt
@@ -3,9 +3,12 @@ package com.tangem.feature.tokendetails.presentation.tokendetails.ui
import android.content.res.Configuration
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxScope
+import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.foundation.layout.systemBars
@@ -15,14 +18,22 @@ import com.tangem.common.ui.notifications.notifications
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.nestedscroll.nestedScroll
+import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.tangem.common.ui.account.AccountIconUM
+import com.tangem.core.ui.components.BottomFade
import com.tangem.core.ui.components.containers.pullToRefresh.PullToRefreshConfig
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.core.ui.components.dropdownmenu.TangemDropdownMenuItem
@@ -44,17 +55,22 @@ import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDeta
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsUM
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.TokenDetailsBalanceBlock
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.TokenDetailsBalanceBlockHeight
+import com.tangem.features.markets.token.block.TokenMarketBlockComponent
import dev.chrisbanes.haze.HazeProgressive
import dev.chrisbanes.haze.HazeTint
import kotlinx.collections.immutable.persistentListOf
-@Composable
-internal fun TokenDetailsScreen(tokenDetailsUM: TokenDetailsUM, modifier: Modifier = Modifier) {
- val topAppBarUM = tokenDetailsUM.topAppBarUM
+private val TopBarHeight: Dp = 64.dp
+private val MarketBlockHorizontalPadding: Dp = 14.dp
+@Composable
+internal fun TokenDetailsScreen(
+ tokenDetailsUM: TokenDetailsUM,
+ tokenMarketBlockComponent: TokenMarketBlockComponent?,
+ modifier: Modifier = Modifier,
+) {
val statusBarHeight = with(LocalDensity.current) { WindowInsets.systemBars.getTop(this).toDp() }
- val topBarHeight = 64.dp
- val partialCollapsedHeight = topBarHeight + statusBarHeight
+ val partialCollapsedHeight = TopBarHeight + statusBarHeight
val expandedHeight = TokenDetailsBalanceBlockHeight + partialCollapsedHeight
val behavior = rememberTangemExitUntilCollapsedScrollBehavior(
@@ -63,6 +79,7 @@ internal fun TokenDetailsScreen(tokenDetailsUM: TokenDetailsUM, modifier: Modifi
)
val rootBackground by LocalRootBackgroundColor.current
+ var marketBlockHeight by remember { mutableStateOf(0.dp) }
val notificationModifier = Modifier
.fillMaxWidth()
.padding(horizontal = TangemTheme.dimens2.x4)
@@ -80,16 +97,18 @@ internal fun TokenDetailsScreen(tokenDetailsUM: TokenDetailsUM, modifier: Modifi
collapsingPart = {
TokenDetailsBalanceBlock(
balanceBlockUM = tokenDetailsUM.balanceBlockUM,
+ behavior = behavior,
modifier = Modifier
.fillMaxWidth()
.statusBarsPadding()
- .padding(top = topBarHeight),
+ .padding(top = TopBarHeight),
)
},
body = {
TokenDetailsBody(
tokenDetailsUM = tokenDetailsUM,
rootBackground = rootBackground,
+ bottomContentPadding = marketBlockHeight,
modifier = Modifier
.fillMaxSize()
.nestedScroll(behavior.nestedScrollConnection),
@@ -99,33 +118,90 @@ internal fun TokenDetailsScreen(tokenDetailsUM: TokenDetailsUM, modifier: Modifi
)
}
- val hazeIntensity by animateFloatAsState(
- targetValue = (behavior.state.collapsedFraction * 2f).coerceIn(0f, 1f),
- label = "TopBarHazeIntensity",
+ TokenDetailsTopBarOverlay(
+ topAppBarUM = tokenDetailsUM.topAppBarUM,
+ collapsedFraction = behavior.state.collapsedFraction,
+ rootBackground = rootBackground,
)
- Box(
- modifier = Modifier.hazeEffectTangem {
- fallbackTint = HazeTint(rootBackground.copy(alpha = hazeIntensity / 2f))
- progressive = HazeProgressive.verticalGradient(
- startIntensity = hazeIntensity,
- endIntensity = 0f,
- preferPerformance = true,
- )
- },
- ) {
- TokenDetailsTopBar(topAppBarUM = topAppBarUM)
+
+ if (tokenMarketBlockComponent != null) {
+ TokenDetailsMarketBlockOverlay(
+ component = tokenMarketBlockComponent,
+ rootBackground = rootBackground,
+ onHeightChange = { marketBlockHeight = it },
+ )
}
}
}
+@Composable
+private fun TokenDetailsTopBarOverlay(
+ topAppBarUM: TokenDetailsTopAppBarUM,
+ collapsedFraction: Float,
+ rootBackground: Color,
+) {
+ val hazeIntensity by animateFloatAsState(
+ targetValue = (collapsedFraction * 2f).coerceIn(0f, 1f),
+ label = "TopBarHazeIntensity",
+ )
+ Box(
+ modifier = Modifier.hazeEffectTangem {
+ fallbackTint = HazeTint(rootBackground.copy(alpha = hazeIntensity / 2f))
+ progressive = HazeProgressive.verticalGradient(
+ startIntensity = hazeIntensity,
+ endIntensity = 0f,
+ preferPerformance = true,
+ )
+ },
+ ) {
+ TokenDetailsTopBar(topAppBarUM = topAppBarUM)
+ }
+}
+
+@Composable
+private fun BoxScope.TokenDetailsMarketBlockOverlay(
+ component: TokenMarketBlockComponent,
+ rootBackground: Color,
+ onHeightChange: (Dp) -> Unit,
+) {
+ val density = LocalDensity.current
+
+ BottomFade(
+ gradientBrush = Brush.verticalGradient(
+ colors = listOf(
+ rootBackground.copy(alpha = 0f),
+ rootBackground,
+ ),
+ ),
+ modifier = Modifier.align(Alignment.BottomCenter),
+ )
+
+ component.Content(
+ modifier = Modifier
+ .align(Alignment.BottomCenter)
+ .onSizeChanged { size ->
+ onHeightChange(with(density) { size.height.toDp() })
+ }
+ .navigationBarsPadding()
+ .padding(
+ horizontal = MarketBlockHorizontalPadding,
+ vertical = TangemTheme.dimens2.x1_5,
+ ),
+ )
+}
+
@Composable
private fun TokenDetailsBody(
tokenDetailsUM: TokenDetailsUM,
rootBackground: Color,
+ bottomContentPadding: Dp,
modifier: Modifier = Modifier,
itemModifier: Modifier = Modifier,
) {
- LazyColumn(modifier = modifier) {
+ LazyColumn(
+ modifier = modifier,
+ contentPadding = PaddingValues(bottom = bottomContentPadding),
+ ) {
notifications(
notifications = tokenDetailsUM.notifications,
contentColor = rootBackground,
@@ -150,6 +226,7 @@ private fun TokenDetailsBody(
private fun TokenDetailsScreen_Preview() {
TangemThemePreviewRedesign {
TokenDetailsScreen(
+ tokenMarketBlockComponent = null,
tokenDetailsUM = TokenDetailsUM(
topAppBarUM = TokenDetailsTopAppBarUM(
titleState = TitleState.WithAccount(
diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/TokenDetailsBalanceBlock.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/TokenDetailsBalanceBlock.kt
index ee3f598efc..5de00c3eb0 100644
--- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/TokenDetailsBalanceBlock.kt
+++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/TokenDetailsBalanceBlock.kt
@@ -16,6 +16,8 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.alpha
+import androidx.compose.ui.draw.scale
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.tooling.preview.Preview
@@ -31,6 +33,9 @@ import com.tangem.core.ui.ds.button.TangemButtonType
import com.tangem.core.ui.ds.button.TangemButtonUM
import com.tangem.core.ui.ds.button.action.ActionButtons
import com.tangem.core.ui.ds.image.TangemIconUM
+import com.tangem.core.ui.ds.topbar.collapsing.TangemCollapsingAppBarBehavior
+import com.tangem.core.ui.ds.topbar.collapsing.rememberTangemExitUntilCollapsedScrollBehavior
+import com.tangem.core.ui.ds.topbar.collapsing.snapToExitUntilCollapsed
import com.tangem.core.ui.extensions.resolveAnnotatedReference
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.stringReference
@@ -45,13 +50,26 @@ import kotlinx.collections.immutable.persistentListOf
private val CurrencyIconSize: Dp = 70.dp
private val NetworkBadgeSize: Dp = 24.dp
internal val TokenDetailsBalanceBlockHeight: Dp = 404.dp
+private const val MIN_SCALE = 0.75f
+private const val MAX_SCALE = 1f
@Composable
-internal fun TokenDetailsBalanceBlock(balanceBlockUM: TokenDetailsBalanceBlockUM, modifier: Modifier = Modifier) {
+internal fun TokenDetailsBalanceBlock(
+ balanceBlockUM: TokenDetailsBalanceBlockUM,
+ behavior: TangemCollapsingAppBarBehavior,
+ modifier: Modifier = Modifier,
+) {
val rootBackground by LocalRootBackgroundColor.current
+ val collapsedFraction = behavior.state.collapsedFraction
+ val alpha = 1f - collapsedFraction
+ val scale = alpha.coerceIn(MIN_SCALE, MAX_SCALE)
+
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = modifier
+ .alpha(alpha)
+ .scale(scale)
+ .snapToExitUntilCollapsed(behavior)
.fillMaxWidth()
.padding(vertical = TangemTheme.dimens2.x10),
) {
@@ -165,6 +183,7 @@ private fun TokenDetailsBalanceBlock_Preview(
TangemThemePreviewRedesign {
TokenDetailsBalanceBlock(
balanceBlockUM = params,
+ behavior = rememberTangemExitUntilCollapsedScrollBehavior(),
modifier = Modifier.background(TangemTheme.colors2.surface.level2),
)
}