Updated on 2026-08-14

This commit is contained in:
Tangem 2026-07-17 12:49:55 +04:00
parent 7f4454c8d2
commit 98d0d9c75e
39 changed files with 266 additions and 3536 deletions

View file

@ -10,9 +10,8 @@ class DefaultDesignFeatureToggles @Inject constructor(
) : DesignFeatureToggles { ) : DesignFeatureToggles {
override val isRedesignEnabled: Boolean override val isRedesignEnabled: Boolean
get() = featureTogglesManager.isFeatureEnabled(FeatureToggles.APP_REDESIGN_ENABLED) get() = true
override val isWarningsRefactoringEnabled: Boolean override val isWarningsRefactoringEnabled: Boolean
get() = isRedesignEnabled && get() = featureTogglesManager.isFeatureEnabled(FeatureToggles.AND_14829_WARNINGS_REFACTORING_ENABLED)
featureTogglesManager.isFeatureEnabled(FeatureToggles.AND_14829_WARNINGS_REFACTORING_ENABLED)
} }

View file

@ -98,15 +98,13 @@ internal fun RootContent(
hostState = snackbarHostState, hostState = snackbarHostState,
) )
if (LocalRedesignEnabled.current) { TangemTopSnackbarHost(
TangemTopSnackbarHost( modifier = Modifier
modifier = Modifier .align(Alignment.TopCenter)
.align(Alignment.TopCenter) .statusBarsPadding()
.statusBarsPadding() .padding(all = 16.dp),
.padding(all = 16.dp), hostState = LocalTopSnackbarHostState.current,
hostState = LocalTopSnackbarHostState.current, )
)
}
} }
} }
EventMessageEffect() EventMessageEffect()

View file

@ -48,7 +48,6 @@ import com.tangem.common.ui.charts.state.*
import com.tangem.core.ui.components.SpacerH16 import com.tangem.core.ui.components.SpacerH16
import com.tangem.core.ui.haptic.TangemHapticEffect import com.tangem.core.ui.haptic.TangemHapticEffect
import com.tangem.core.ui.res.LocalHapticManager import com.tangem.core.ui.res.LocalHapticManager
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.utils.DateTimeFormatters import com.tangem.core.ui.utils.DateTimeFormatters
@ -317,19 +316,9 @@ private fun rememberMarketChartBottomAxis(
@Composable @Composable
private fun rememberChartAxisGuidelineComponent(color: Color, isWithPadding: Boolean = true): LineComponent { private fun rememberChartAxisGuidelineComponent(color: Color, isWithPadding: Boolean = true): LineComponent {
val isRedesign = LocalRedesignEnabled.current val startPadding = TangemTheme.dimens2.x2_5
val startPadding = if (isRedesign) { val endPadding = TangemTheme.dimens2.x2
TangemTheme.dimens2.x2_5
} else {
TangemTheme.dimens.spacing4
}
val endPadding = if (isRedesign) {
TangemTheme.dimens2.x2
} else {
TangemTheme.dimens.spacing4
}
return rememberAxisGuidelineComponent( return rememberAxisGuidelineComponent(
color = color, color = color,

View file

@ -91,7 +91,6 @@ fun MarketListItemContentV2(model: MarketsListItemUM, modifier: Modifier = Modif
.layoutId(layoutId = TangemRowLayoutId.END_TOP) .layoutId(layoutId = TangemRowLayoutId.END_TOP)
.padding(start = TangemTheme.dimens2.x3) .padding(start = TangemTheme.dimens2.x3)
.testTag(tag = TokenElementsTestTags.TOKEN_FIAT_AMOUNT), .testTag(tag = TokenElementsTestTags.TOKEN_FIAT_AMOUNT),
price = model.price.text,
priceChangeType = model.price.changeType, priceChangeType = model.price.changeType,
priceAnnotated = model.price.annotated, priceAnnotated = model.price.annotated,
priceValue = model.price.fiatPrice, priceValue = model.price.fiatPrice,

View file

@ -3,21 +3,12 @@ package com.tangem.common.ui.markets
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import com.tangem.common.ui.markets.models.MarketsListItemUM import com.tangem.common.ui.markets.models.MarketsListItemUM
import com.tangem.core.ui.res.LocalRedesignEnabled
@Composable @Composable
fun MarketsListItem(model: MarketsListItemUM, modifier: Modifier = Modifier, onClick: () -> Unit = {}) { fun MarketsListItem(model: MarketsListItemUM, modifier: Modifier = Modifier, onClick: () -> Unit = {}) {
if (LocalRedesignEnabled.current) { MarketsListItemV2(
MarketsListItemV2( model = model,
model = model, modifier = modifier,
modifier = modifier, onClick = onClick,
onClick = onClick, )
)
} else {
MarketsListItemV1(
model = model,
modifier = modifier,
onClick = onClick,
)
}
} }

View file

@ -7,92 +7,15 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.tangem.core.ui.components.* import com.tangem.core.ui.components.*
import com.tangem.core.ui.res.* import com.tangem.core.ui.res.*
import com.tangem.core.ui.windowsize.WindowSizeType import com.tangem.core.ui.windowsize.WindowSizeType
@Suppress("LongMethod")
@Composable @Composable
fun MarketsListItemPlaceholder() { fun MarketsListItemPlaceholder() {
if (LocalRedesignEnabled.current) {
MarketsListItemPlaceholderV2()
} else {
MarketsListItemPlaceholderV1()
}
}
@Suppress("LongMethod")
@Composable
private fun MarketsListItemPlaceholderV1() {
val density = LocalDensity.current
val windowSize = LocalWindowSize.current
val sp12 = with(density) { 12.sp.toDp() }
Row(
modifier = Modifier.padding(
horizontal = TangemTheme.dimens.spacing16,
vertical = TangemTheme.dimens.spacing15,
),
verticalAlignment = Alignment.CenterVertically,
) {
CircleShimmer(Modifier.size(TangemTheme.dimens.size36))
SpacerW12()
Column(modifier = Modifier.weight(1f)) {
Box(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = TangemTheme.dimens.spacing4),
contentAlignment = Alignment.CenterStart,
) {
RectangleShimmer(
modifier = Modifier
.width(TangemTheme.dimens.size70)
.height(sp12),
radius = TangemTheme.dimens.radius3,
)
}
SpacerH(height = TangemTheme.dimens.spacing2)
Box(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = TangemTheme.dimens.spacing2),
contentAlignment = Alignment.CenterStart,
) {
RectangleShimmer(
modifier = Modifier
.width(TangemTheme.dimens.size52)
.height(sp12),
radius = TangemTheme.dimens.radius3,
)
}
}
if (windowSize.widthAtLeast(WindowSizeType.Small)) {
Spacer(Modifier.width(TangemTheme.dimens.spacing10))
Box {
RectangleShimmer(
modifier = Modifier
.align(Alignment.Center)
.width(TangemTheme.dimens.size56)
.height(TangemTheme.dimens.size12),
radius = TangemTheme.dimens.radius3,
)
}
}
}
}
@Suppress("LongMethod")
@Composable
private fun MarketsListItemPlaceholderV2() {
val windowSize = LocalWindowSize.current val windowSize = LocalWindowSize.current
Row( Row(
modifier = Modifier.padding( modifier = Modifier.padding(

View file

@ -1,330 +0,0 @@
package com.tangem.common.ui.markets
import android.content.res.Configuration
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import com.tangem.common.ui.charts.MarketChartMini
import com.tangem.common.ui.charts.state.MarketChartLook
import com.tangem.common.ui.charts.state.MarketChartRawData
import com.tangem.common.ui.markets.models.MarketsListItemUM
import com.tangem.common.ui.markets.preview.MarketChartListItemPreviewDataProvider
import com.tangem.common.ui.tokens.TokenPriceText
import com.tangem.core.ui.R
import com.tangem.core.ui.components.*
import com.tangem.core.ui.components.currency.icon.CoinIcon
import com.tangem.core.ui.components.marketprice.PriceChangeInPercent
import com.tangem.core.ui.components.marketprice.PriceChangeType
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.res.LocalWindowSize
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.test.MarketsTestTags
import com.tangem.core.ui.windowsize.WindowSizeType
import com.tangem.utils.StringsSigns.MINUS
import java.math.BigDecimal
import kotlin.random.Random
@Composable
fun MarketsListItemV1(model: MarketsListItemUM, modifier: Modifier = Modifier, onClick: () -> Unit = {}) {
MarketsListItemContentV1(
modifier = modifier
.fillMaxWidth()
.clip(RectangleShape)
.clickable(onClick = onClick)
.testTag(MarketsTestTags.TOKENS_LIST_ITEM),
model = model,
)
}
@Composable
private fun MarketsListItemContentV1(model: MarketsListItemUM, modifier: Modifier = Modifier) {
val windowSize = LocalWindowSize.current
Row(
modifier = modifier.padding(
horizontal = TangemTheme.dimens.spacing16,
vertical = TangemTheme.dimens.spacing15,
),
verticalAlignment = Alignment.CenterVertically,
) {
CoinIcon(
modifier = Modifier.size(TangemTheme.dimens.size36),
url = model.iconUrl,
alpha = 1f,
colorFilter = null,
fallbackResId = R.drawable.ic_custom_token_44,
)
SpacerW12()
Column(modifier = Modifier.weight(1f)) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
) {
TokenTitle(
modifier = Modifier.weight(1f, fill = false),
name = model.name,
currencySymbol = model.currencySymbol,
)
SpacerW8()
TokenPriceText(
modifier = Modifier.alignByBaseline(),
price = model.price.text,
priceChangeType = model.price.changeType,
priceAnnotated = model.price.annotated,
priceValue = model.price.fiatPrice,
)
}
SpacerH(height = TangemTheme.dimens.spacing2)
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.Bottom,
) {
TokenSubtitle(
modifier = Modifier
.weight(1f, fill = false)
.alignByBaseline(),
ratingPosition = model.ratingPosition,
marketCap = model.marketCap,
stakingRate = model.stakingRate,
)
PriceChangeInPercent(
modifier = Modifier.alignByBaseline(),
textStyle = TangemTheme.typography.caption2,
type = model.trendType,
valueInPercent = model.trendPercentText,
)
}
}
if (windowSize.widthAtLeast(WindowSizeType.Small)) {
Spacer(Modifier.width(TangemTheme.dimens.spacing10))
Chart(
chartType = model.chartType,
chartRawData = model.chartData,
)
}
}
}
@Composable
private fun TokenTitle(name: String, currencySymbol: String, modifier: Modifier = Modifier) {
Row(modifier = modifier) {
Text(
modifier = Modifier
.weight(1f, fill = false)
.alignByBaseline(),
text = name,
color = TangemTheme.colors.text.primary1,
style = TangemTheme.typography.subtitle2,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
SpacerW4()
Text(
modifier = Modifier.alignByBaseline(),
text = currencySymbol,
color = TangemTheme.colors.text.tertiary,
style = TangemTheme.typography.caption1,
maxLines = 1,
overflow = TextOverflow.Visible,
)
}
}
@Composable
private fun TokenSubtitle(
ratingPosition: String?,
marketCap: String?,
stakingRate: TextReference?,
modifier: Modifier = Modifier,
) {
Row(
modifier = modifier,
verticalAlignment = Alignment.CenterVertically,
) {
TokenRatingPlace(ratingPosition = ratingPosition)
if (marketCap != null) {
SpacerW4()
TokenMarketCapText(
modifier = Modifier.weight(1f, fill = false),
text = marketCap,
)
}
if (stakingRate != null) {
SpacerW4()
StakingRate(stakingRate = stakingRate.resolveReference())
}
}
}
@Composable
private fun RowScope.TokenRatingPlace(ratingPosition: String?) {
Box(
modifier = Modifier
.alignByBaseline()
.heightIn(min = TangemTheme.dimens.size16)
.background(
color = TangemTheme.colors.button.secondary,
shape = TangemTheme.shapes.roundedCornersSmall2,
)
.padding(horizontal = TangemTheme.dimens.spacing5),
) {
Text(
text = ratingPosition ?: MINUS,
color = TangemTheme.colors.text.tertiary,
style = TangemTheme.typography.caption1,
maxLines = 1,
)
}
}
@Composable
private fun RowScope.StakingRate(stakingRate: String) {
Box(
modifier = Modifier
.alignByBaseline()
.heightIn(min = TangemTheme.dimens.size16)
.border(
width = TangemTheme.dimens.size1,
color = TangemTheme.colors.field.primary,
shape = TangemTheme.shapes.roundedCornersSmall2,
)
.padding(horizontal = TangemTheme.dimens.spacing5),
) {
Text(
text = stakingRate,
color = TangemTheme.colors.text.tertiary,
style = TangemTheme.typography.caption1,
maxLines = 1,
)
}
}
@Composable
private fun RowScope.TokenMarketCapText(text: String, modifier: Modifier = Modifier) {
Text(
modifier = modifier.alignByBaseline(),
text = text,
color = TangemTheme.colors.text.tertiary,
style = TangemTheme.typography.caption2,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}
@Composable
private fun Chart(chartType: MarketChartLook.Type, chartRawData: MarketChartRawData?) {
val chartWidth = TangemTheme.dimens.size56
Box(
modifier = Modifier
.padding(vertical = TangemTheme.dimens.spacing2)
.size(height = TangemTheme.dimens.size24, width = chartWidth),
) {
if (chartRawData != null) {
MarketChartMini(
rawData = chartRawData,
type = chartType,
)
} else {
RectangleShimmer(
modifier = Modifier
.fillMaxWidth()
.height(TangemTheme.dimens.size12)
.align(Alignment.Center),
radius = TangemTheme.dimens.radius3,
)
}
}
}
// region 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 = 260, name = "small width")
@Composable
private fun Preview(@PreviewParameter(MarketChartListItemPreviewDataProvider::class) state: MarketsListItemUM) {
TangemThemePreview {
var state1 by remember { mutableStateOf(state) }
var state2 by remember { mutableStateOf(state) }
var prices by remember {
mutableStateOf(
listOf(
100 to PriceChangeType.NEUTRAL,
200 to PriceChangeType.NEUTRAL,
),
)
}
Column(modifier = Modifier.background(TangemTheme.colors.background.primary)) {
MarketsListItemV1(
modifier = Modifier,
model = state1,
)
MarketsListItemV1(
modifier = Modifier,
model = state2,
)
Row {
Button(
onClick = {
state1 = state1.copy(
trendType = PriceChangeType.entries.random(),
)
state2 = state2.copy(
trendType = PriceChangeType.entries.random(),
)
},
) { Text(text = "trend") }
Button(
onClick = {
prices = prices.map { (price, _) ->
if (Random.nextBoolean()) {
price.inc() to PriceChangeType.UP
} else {
price.dec() to PriceChangeType.DOWN
}
}
state1 = state1.copy(
price = MarketsListItemUM.Price(
text = "0.${prices[0].first}023 $",
changeType = prices[0].second,
fiatPrice = BigDecimal(123123),
annotated = stringReference("0.${prices[0].first}023 $"),
),
)
state2 = state2.copy(
price = MarketsListItemUM.Price(
text = "0.${prices[1].first}023 $",
changeType = prices[1].second,
fiatPrice = BigDecimal(123123),
annotated = stringReference("0.${prices[0].first}023 $"),
),
)
},
) { Text(text = "price") }
}
}
}
}

View file

@ -10,81 +10,18 @@ import androidx.compose.ui.text.style.TextOverflow
import com.tangem.core.ui.components.marketprice.PriceChangeType import com.tangem.core.ui.components.marketprice.PriceChangeType
import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resolveAnnotatedReference import com.tangem.core.ui.extensions.resolveAnnotatedReference
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemTheme
import java.math.BigDecimal import java.math.BigDecimal
/** /**
* Text view for token price. * Text view for token price.
* *
* @param price Plain price string (used by V1 and as fallback for V2 when [priceAnnotated] is null). * @param priceValue Numeric price; drives the change animation (used as the LaunchedEffect key).
* @param priceAnnotated Optional styled price (locale-aware decimal separator + secondary fractional color). * @param priceAnnotated Styled price (locale-aware decimal separator + secondary fractional color).
* @param priceChangeType Type of the price change. * @param priceChangeType Type of the price change.
*/ */
@Composable @Composable
fun TokenPriceText( fun TokenPriceText(
priceValue: BigDecimal,
price: String,
priceAnnotated: TextReference,
modifier: Modifier = Modifier,
priceChangeType: PriceChangeType? = null,
) {
if (LocalRedesignEnabled.current) {
TokenPriceTextV2(
priceValue = priceValue,
priceAnnotated = priceAnnotated,
modifier = modifier,
priceChangeType = priceChangeType,
)
} else {
TokenPriceTextV1(
price = price,
modifier = modifier,
priceChangeType = priceChangeType,
)
}
}
@Composable
private fun TokenPriceTextV1(price: String, modifier: Modifier = Modifier, priceChangeType: PriceChangeType? = null) {
val growColor = TangemTheme.colors.text.accent
val fallColor = TangemTheme.colors.text.warning
val generalColor = TangemTheme.colors.text.primary1
val color = remember(generalColor) { Animatable(generalColor) }
var isAnimationSkipped by remember { mutableStateOf(false) }
LaunchedEffect(price) {
if (isAnimationSkipped.not()) {
isAnimationSkipped = true
return@LaunchedEffect
}
if (priceChangeType != null) {
val nextColor = when (priceChangeType) {
PriceChangeType.UP,
-> growColor
PriceChangeType.DOWN -> fallColor
PriceChangeType.NEUTRAL -> return@LaunchedEffect
}
color.animateTo(nextColor, snap())
color.animateTo(generalColor, tween(durationMillis = 500))
}
}
Text(
modifier = modifier,
text = price,
color = color.value,
maxLines = 1,
style = TangemTheme.typography.body2,
overflow = TextOverflow.Visible,
)
}
@Composable
private fun TokenPriceTextV2(
priceValue: BigDecimal, priceValue: BigDecimal,
priceAnnotated: TextReference, priceAnnotated: TextReference,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,

View file

@ -4,7 +4,6 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.State import androidx.compose.runtime.State
import com.arkivanov.decompose.router.stack.ChildStack import com.arkivanov.decompose.router.stack.ChildStack
import com.tangem.core.ui.decompose.ComposableContentComponent import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.features.commonfeatures.impl.addtoportfolio.model.AddToPortfolioRoutes import com.tangem.features.commonfeatures.impl.addtoportfolio.model.AddToPortfolioRoutes
import com.tangem.features.commonfeatures.impl.userportfolio.model.UserPortfolioUM import com.tangem.features.commonfeatures.impl.userportfolio.model.UserPortfolioUM
@ -16,19 +15,11 @@ internal fun AddToPortfolioBottomSheetSwitch(
userPortfolioState: State<UserPortfolioUM?>? = null, userPortfolioState: State<UserPortfolioUM?>? = null,
onAddFromUserPortfolioClick: (() -> Unit)? = null, onAddFromUserPortfolioClick: (() -> Unit)? = null,
) { ) {
if (LocalRedesignEnabled.current) { AddToPortfolioBottomSheetV2(
AddToPortfolioBottomSheetV2( childStack = childStack,
childStack = childStack, onBack = onBack,
onBack = onBack, onDismiss = onDismiss,
onDismiss = onDismiss, userPortfolioState = userPortfolioState,
userPortfolioState = userPortfolioState, onAddFromUserPortfolioClick = onAddFromUserPortfolioClick,
onAddFromUserPortfolioClick = onAddFromUserPortfolioClick, )
)
} else {
AddToPortfolioBottomSheet(
childStack = childStack,
onBack = onBack,
onDismiss = onDismiss,
)
}
} }

View file

@ -3,13 +3,11 @@ package com.tangem.features.commonfeatures.impl.addtoportfolio
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tangem.common.ui.addtoken.AddTokenContent
import com.tangem.common.ui.addtoken.AddTokenContentV2 import com.tangem.common.ui.addtoken.AddTokenContentV2
import com.tangem.core.decompose.context.AppComponentContext import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.factory.ComponentFactory import com.tangem.core.decompose.factory.ComponentFactory
import com.tangem.core.decompose.model.getOrCreateModel import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.ui.decompose.ComposableContentComponent import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.features.commonfeatures.api.addtoportfolio.SelectedNetwork import com.tangem.features.commonfeatures.api.addtoportfolio.SelectedNetwork
import com.tangem.features.commonfeatures.api.addtoportfolio.SelectedPortfolio import com.tangem.features.commonfeatures.api.addtoportfolio.SelectedPortfolio
@ -31,17 +29,10 @@ internal class AddTokenComponent @AssistedInject constructor(
override fun Content(modifier: Modifier) { override fun Content(modifier: Modifier) {
val state = model.uiState.collectAsStateWithLifecycle() val state = model.uiState.collectAsStateWithLifecycle()
val um = state.value ?: return val um = state.value ?: return
if (LocalRedesignEnabled.current) { AddTokenContentV2(
AddTokenContentV2( modifier = modifier,
modifier = modifier, state = um,
state = um, )
)
} else {
AddTokenContent(
modifier = modifier,
state = um,
)
}
} }
data class Params( data class Params(

View file

@ -13,7 +13,6 @@ import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.core.decompose.ui.UiMessageSender import com.tangem.core.decompose.ui.UiMessageSender
import com.tangem.core.ui.DesignFeatureToggles
import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.message.ToastMessage import com.tangem.core.ui.message.ToastMessage
import com.tangem.domain.account.status.usecase.GetAccountCurrencyStatusUseCase import com.tangem.domain.account.status.usecase.GetAccountCurrencyStatusUseCase
@ -56,7 +55,6 @@ internal class AddToPortfolioModel @Inject constructor(
paramsContainer: ParamsContainer, paramsContainer: ParamsContainer,
override val dispatchers: CoroutineDispatcherProvider, override val dispatchers: CoroutineDispatcherProvider,
val portfolioSelectorController: PortfolioSelectorController, val portfolioSelectorController: PortfolioSelectorController,
private val designFeatureToggles: DesignFeatureToggles,
private val callbackDelegate: AddToPortfolioCallbackDelegate, private val callbackDelegate: AddToPortfolioCallbackDelegate,
private val getCryptoCurrencyActionsUseCase: GetCryptoCurrencyActionsUseCaseV2, private val getCryptoCurrencyActionsUseCase: GetCryptoCurrencyActionsUseCaseV2,
private val getTokenMarketCryptoCurrency: GetTokenMarketCryptoCurrency, private val getTokenMarketCryptoCurrency: GetTokenMarketCryptoCurrency,
@ -182,9 +180,9 @@ internal class AddToPortfolioModel @Inject constructor(
setupPortfolioSelector(initialData, launchMode) setupPortfolioSelector(initialData, launchMode)
val shouldShowUserPortfolio = designFeatureToggles.isRedesignEnabled && val shouldShowUserPortfolio =
launchMode is AddToPortfolioManager.LaunchMode.ViaUserPortfolio && launchMode is AddToPortfolioManager.LaunchMode.ViaUserPortfolio &&
initialData.hasAnyAddedCurrency(tokenMarketParams.id) initialData.hasAnyAddedCurrency(tokenMarketParams.id)
if (shouldShowUserPortfolio) { if (shouldShowUserPortfolio) {
// suspend, must prepare UM before navigate to UserPortfolio // suspend, must prepare UM before navigate to UserPortfolio
@ -200,11 +198,7 @@ internal class AddToPortfolioModel @Inject constructor(
AddToPortfolioManager.LaunchMode.Preselected -> null AddToPortfolioManager.LaunchMode.Preselected -> null
is AddToPortfolioManager.LaunchMode.ViaUserPortfolio, is AddToPortfolioManager.LaunchMode.ViaUserPortfolio,
AddToPortfolioManager.LaunchMode.DirectAdd, AddToPortfolioManager.LaunchMode.DirectAdd,
-> if (designFeatureToggles.isRedesignEnabled) { -> getInitialSelection(initialData)
getInitialSelection(initialData)
} else {
null
}
} }
val firstSelectedPortfolioFlow: Flow<SelectedPortfolio> = val firstSelectedPortfolioFlow: Flow<SelectedPortfolio> =
@ -272,15 +266,11 @@ internal class AddToPortfolioModel @Inject constructor(
callbackDelegate.onChangePortfolioClick.receiveAsFlow() callbackDelegate.onChangePortfolioClick.receiveAsFlow()
.onEach { .onEach {
middleNavigationJob?.cancel() middleNavigationJob?.cancel()
middleNavigationJob = if (designFeatureToggles.isRedesignEnabled) { middleNavigationJob = changePortfolioNavigationNewFlow(
changePortfolioNavigationNewFlow( data = initialData,
data = initialData, orderedNetworks = paramsSnapshot.networks,
orderedNetworks = paramsSnapshot.networks, tokenParams = tokenMarketParams,
tokenParams = tokenMarketParams, ).launchIn(this)
)
} else {
changePortfolioNavigationFlow(initialData)
}.launchIn(this)
logAccountSelector(isAccountMode) logAccountSelector(isAccountMode)
navigation.pushNew(AddToPortfolioRoutes.PortfolioSelector) navigation.pushNew(AddToPortfolioRoutes.PortfolioSelector)
} }
@ -369,33 +359,6 @@ internal class AddToPortfolioModel @Inject constructor(
} }
} }
private fun changePortfolioNavigationFlow(data: AvailableToAddData): Flow<Unit> = flow {
val selectedPortfolioValue = selectedPortfolio.first()
val selectedAccount = selectedPortfolioValue.account.account.account.accountId
portfolioSelectorController.selectAccount(selectedAccount)
val changedPortfolio = setupPortfolioFlow(data)
.drop(1)
.onEach { portfolio ->
val isSingleAvailableNetwork = portfolio.account.isSingleNetwork
if (isSingleAvailableNetwork) {
val singleNetwork = portfolio.account.availableToAddNetworks.first()
callbackDelegate.onNetworkSelected.send(singleNetwork)
} else {
navigation.pushNew(routeToNetworkSelector(portfolio))
}
}
val changedNetwork = setupNetworkFlow(changedPortfolio)
combine(
flow = changedPortfolio,
flow2 = changedNetwork,
transform = { newPortfolio, newNetwork ->
selectedPortfolio.tryEmit(newPortfolio)
selectedNetwork.tryEmit(newNetwork)
navigation.popToFirst()
},
).collect { emit(it) }
}
private fun changePortfolioNavigationNewFlow( private fun changePortfolioNavigationNewFlow(
data: AvailableToAddData, data: AvailableToAddData,
orderedNetworks: List<TokenMarketInfo.Network>, orderedNetworks: List<TokenMarketInfo.Network>,
@ -432,7 +395,7 @@ internal class AddToPortfolioModel @Inject constructor(
currency = addedToken.currency, currency = addedToken.currency,
accountId = selectedPortfolio.account.account.account.accountId, accountId = selectedPortfolio.account.account.account.accountId,
).onEach { state -> ).onEach { state ->
val requestedQuickActions = toQuickActions(state.states, designFeatureToggles.isRedesignEnabled) val requestedQuickActions = toQuickActions(state.states, isRedesignEnabled = true)
when { when {
requestedQuickActions.isNotEmpty() -> { requestedQuickActions.isNotEmpty() -> {
timerJob.cancel() timerJob.cancel()

View file

@ -49,17 +49,10 @@ private const val DISABLED_ALPHA = 0.4f
@Composable @Composable
internal fun ChooseNetworkContent(state: ChooseNetworkUM, modifier: Modifier = Modifier) { internal fun ChooseNetworkContent(state: ChooseNetworkUM, modifier: Modifier = Modifier) {
if (LocalRedesignEnabled.current) { ChooseNetworkContentV2(
ChooseNetworkContentV2( state = state,
state = state, modifier = modifier,
modifier = modifier, )
)
} else {
ChooseNetworkContentV1(
state = state,
modifier = modifier,
)
}
} }
@Composable @Composable

View file

@ -75,7 +75,6 @@ import com.tangem.core.ui.ds.row.token.TangemTokenRow
import com.tangem.core.ui.ds.row.token.TangemTokenRowUM import com.tangem.core.ui.ds.row.token.TangemTokenRowUM
import com.tangem.core.ui.ds.row.token.internal.TokenRowTitle import com.tangem.core.ui.ds.row.token.internal.TokenRowTitle
import com.tangem.core.ui.extensions.* import com.tangem.core.ui.extensions.*
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.core.ui.res.TangemColorPalette import com.tangem.core.ui.res.TangemColorPalette
import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemThemePreview
@ -136,13 +135,7 @@ private val ChooseTokenFullUM.isEmptyState: Boolean
internal fun ChooseTokenScreen(state: ChooseTokenFullUM, modifier: Modifier = Modifier) { internal fun ChooseTokenScreen(state: ChooseTokenFullUM, modifier: Modifier = Modifier) {
Column( Column(
modifier = modifier modifier = modifier
.background( .background(color = TangemTheme.colors2.surface.level2)
color = if (LocalRedesignEnabled.current) {
TangemTheme.colors2.surface.level2
} else {
TangemTheme.colors.background.secondary
},
)
.fillMaxSize() .fillMaxSize()
.imePadding(), .imePadding(),
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
@ -747,11 +740,7 @@ private fun LazyListScope.predefinedTokensListItems(state: PredefinedTokensUM) {
.roundedShapeItemDecoration( .roundedShapeItemDecoration(
currentIndex = index, currentIndex = index,
lastIndex = state.items.lastIndex, lastIndex = state.items.lastIndex,
backgroundColor = if (LocalRedesignEnabled.current) { backgroundColor = TangemTheme.colors2.surface.level1,
TangemTheme.colors2.surface.level1
} else {
TangemTheme.colors.background.primary
},
) )
.testTag(BuyTokenScreenTestTags.LAZY_LIST_ITEM) .testTag(BuyTokenScreenTestTags.LAZY_LIST_ITEM)
.semantics { lazyListItemPosition = index }, .semantics { lazyListItemPosition = index },
@ -811,13 +800,7 @@ private fun LazyListScope.emptyTokensList(modifier: Modifier = Modifier) {
item("EmptyTokensList") { item("EmptyTokensList") {
Box( Box(
modifier = modifier modifier = modifier
.background( .background(color = TangemTheme.colors2.surface.level2)
color = if (LocalRedesignEnabled.current) {
TangemTheme.colors2.surface.level2
} else {
TangemTheme.colors.background.secondary
},
)
.fillParentMaxSize(), .fillParentMaxSize(),
) { ) {
Column(modifier = Modifier.align(Alignment.Center)) { Column(modifier = Modifier.align(Alignment.Center)) {
@ -848,13 +831,7 @@ private fun LazyListScope.tokensNotFound(modifier: Modifier = Modifier) {
item("TokensNotFound") { item("TokensNotFound") {
Box( Box(
modifier = modifier modifier = modifier
.background( .background(color = TangemTheme.colors2.surface.level2)
color = if (LocalRedesignEnabled.current) {
TangemTheme.colors2.surface.level2
} else {
TangemTheme.colors.background.secondary
},
)
.fillParentMaxSize(), .fillParentMaxSize(),
) { ) {
Text( Text(

View file

@ -9,10 +9,8 @@ import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.model.getOrCreateModel import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.ui.components.bottomsheets.LocalTangemBottomSheetContentBottomInset import com.tangem.core.ui.components.bottomsheets.LocalTangemBottomSheetContentBottomInset
import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.features.commonfeatures.api.portfolioselector.PortfolioSelectorComponent import com.tangem.features.commonfeatures.api.portfolioselector.PortfolioSelectorComponent
import com.tangem.features.commonfeatures.impl.portfolioselector.ui.PortfolioSelectorBS import com.tangem.features.commonfeatures.impl.portfolioselector.ui.PortfolioSelectorBS
import com.tangem.features.commonfeatures.impl.portfolioselector.ui.PortfolioSelectorContent
import com.tangem.features.commonfeatures.impl.portfolioselector.ui.PortfolioSelectorContentV2 import com.tangem.features.commonfeatures.impl.portfolioselector.ui.PortfolioSelectorContentV2
import dagger.assisted.Assisted import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory import dagger.assisted.AssistedFactory
@ -48,19 +46,11 @@ internal class DefaultPortfolioSelectorComponent @AssistedInject constructor(
override fun Content(modifier: Modifier) { override fun Content(modifier: Modifier) {
val state by model.state.collectAsStateWithLifecycle() val state by model.state.collectAsStateWithLifecycle()
val listBottomPadding = PaddingValues(bottom = LocalTangemBottomSheetContentBottomInset.current) val listBottomPadding = PaddingValues(bottom = LocalTangemBottomSheetContentBottomInset.current)
if (LocalRedesignEnabled.current) { PortfolioSelectorContentV2(
PortfolioSelectorContentV2( state = state,
state = state, modifier = modifier,
modifier = modifier, contentPadding = listBottomPadding,
contentPadding = listBottomPadding, )
)
} else {
PortfolioSelectorContent(
state = state,
modifier = modifier,
contentPadding = listBottomPadding,
)
}
} }
@AssistedFactory @AssistedFactory

View file

@ -45,19 +45,11 @@ internal fun PortfolioSelectorBS(
) )
}, },
content = { content = {
if (LocalRedesignEnabled.current) { PortfolioSelectorContentV2(
PortfolioSelectorContentV2( state = state,
state = state, contentPadding = PaddingValues(bottom = 16.dp),
contentPadding = PaddingValues(bottom = 16.dp), modifier = modifier.padding(horizontal = 16.dp),
modifier = modifier.padding(horizontal = 16.dp), )
)
} else {
PortfolioSelectorContent(
state = state,
contentPadding = PaddingValues(bottom = 16.dp),
modifier = modifier.padding(horizontal = 16.dp),
)
}
}, },
) )
} }

View file

@ -17,11 +17,9 @@ import com.tangem.core.decompose.factory.ComponentFactory
import com.tangem.core.decompose.model.getOrCreateModel import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
import com.tangem.core.ui.decompose.ComposableContentComponent import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.domain.models.TokenReceiveConfig import com.tangem.domain.models.TokenReceiveConfig
import com.tangem.features.commonfeatures.api.tokenactions.BottomAction import com.tangem.features.commonfeatures.api.tokenactions.BottomAction
import com.tangem.features.commonfeatures.impl.tokenactions.model.TokenActionsModel import com.tangem.features.commonfeatures.impl.tokenactions.model.TokenActionsModel
import com.tangem.features.commonfeatures.impl.tokenactions.ui.TokenActionsContent
import com.tangem.features.commonfeatures.impl.tokenactions.ui.TokenActionsContentV2 import com.tangem.features.commonfeatures.impl.tokenactions.ui.TokenActionsContentV2
import com.tangem.features.tokenreceive.TokenReceiveComponent import com.tangem.features.tokenreceive.TokenReceiveComponent
import dagger.assisted.Assisted import dagger.assisted.Assisted
@ -49,17 +47,10 @@ internal class TokenActionsComponent @AssistedInject constructor(
val state = model.uiState.collectAsStateWithLifecycle() val state = model.uiState.collectAsStateWithLifecycle()
val bottomSheet by bottomSheetSlot.subscribeAsState() val bottomSheet by bottomSheetSlot.subscribeAsState()
val tokenActionsUM = state.value ?: return val tokenActionsUM = state.value ?: return
if (LocalRedesignEnabled.current || params.isRedesignForced) { TokenActionsContentV2(
TokenActionsContentV2( modifier = modifier,
modifier = modifier, state = tokenActionsUM,
state = tokenActionsUM, )
)
} else {
TokenActionsContent(
modifier = modifier,
state = tokenActionsUM,
)
}
bottomSheet.child?.instance?.BottomSheet() bottomSheet.child?.instance?.BottomSheet()
} }

View file

@ -15,7 +15,6 @@ import com.tangem.features.commonfeatures.api.tokenactions.BottomAction
import com.tangem.common.ui.userwallet.converter.WalletIconUMConverter import com.tangem.common.ui.userwallet.converter.WalletIconUMConverter
import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.core.ui.DesignFeatureToggles
import com.tangem.core.ui.components.token.state.TokenItemState import com.tangem.core.ui.components.token.state.TokenItemState
import com.tangem.core.ui.ds.badge.TangemBadgeIconPosition import com.tangem.core.ui.ds.badge.TangemBadgeIconPosition
import com.tangem.core.ui.ds.badge.TangemBadgeShape import com.tangem.core.ui.ds.badge.TangemBadgeShape
@ -41,7 +40,6 @@ import javax.inject.Inject
@ModelScoped @ModelScoped
internal class TokenActionsUiBuilder @Inject constructor( internal class TokenActionsUiBuilder @Inject constructor(
paramsContainer: ParamsContainer, paramsContainer: ParamsContainer,
private val designFeatureToggles: DesignFeatureToggles,
private val getWalletIconUseCase: GetWalletIconUseCase, private val getWalletIconUseCase: GetWalletIconUseCase,
private val getWalletsUseCase: GetWalletsUseCase, private val getWalletsUseCase: GetWalletsUseCase,
private val walletIconUMConverter: WalletIconUMConverter, private val walletIconUMConverter: WalletIconUMConverter,
@ -55,51 +53,12 @@ internal class TokenActionsUiBuilder @Inject constructor(
isBalanceHidden: Boolean, isBalanceHidden: Boolean,
bottomAction: BottomAction, bottomAction: BottomAction,
): TokenActionsUM { ): TokenActionsUM {
return if (designFeatureToggles.isRedesignEnabled || params.isRedesignForced) { return buildV2(
buildV2( cryptoCurrencyData = cryptoCurrencyData,
cryptoCurrencyData = cryptoCurrencyData, tokenActionsHandler = tokenActionsHandler,
tokenActionsHandler = tokenActionsHandler, appCurrency = appCurrency,
appCurrency = appCurrency, isBalanceHidden = isBalanceHidden,
isBalanceHidden = isBalanceHidden, bottomAction = bottomAction,
bottomAction = bottomAction,
)
} else {
buildV1(
cryptoCurrencyData = cryptoCurrencyData,
tokenActionsHandler = tokenActionsHandler,
bottomAction = bottomAction,
)
}
}
private fun buildV1(
cryptoCurrencyData: CryptoCurrencyData,
tokenActionsHandler: TokenActionsHandler,
bottomAction: BottomAction,
): TokenActionsUM {
val status = cryptoCurrencyData.status
val tokenUM = TokenItemState.Content(
id = status.currency.id.value,
iconState = CryptoCurrencyToIconStateConverter().convert(status.currency),
titleState = TokenItemState.TitleState.Content(stringReference(status.currency.name)),
fiatAmountState = null,
subtitle2State = null,
subtitleState = TokenItemState.SubtitleState.TextContent(stringReference(status.currency.symbol)),
onItemClick = null,
onItemLongClick = null,
)
return TokenActionsUM(
token = tokenUM,
quickActions = quickActions(
cryptoData = cryptoCurrencyData,
tokenActionsHandler = tokenActionsHandler,
isRedesignEnabled = false,
context = params.context,
),
bottomActionText = bottomActionText(bottomAction),
onBottomActionClick = {
params.callbacks.onBottomActionClick(bottomAction)
},
) )
} }

View file

@ -7,12 +7,10 @@ import androidx.compose.ui.Modifier
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tangem.core.decompose.context.AppComponentContext import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.model.getOrCreateModel import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.features.markets.token.block.TokenMarketBlockComponent import com.tangem.features.markets.token.block.TokenMarketBlockComponent
import com.tangem.features.markets.token.block.TokenMarketBlockComponent.Params import com.tangem.features.markets.token.block.TokenMarketBlockComponent.Params
import com.tangem.features.markets.token.block.impl.model.TokenMarketBlockModel import com.tangem.features.markets.token.block.impl.model.TokenMarketBlockModel
import com.tangem.features.markets.token.block.impl.ui.TokenMarketBlock import com.tangem.features.markets.token.block.impl.ui.TokenMarketBlock
import com.tangem.features.markets.token.block.impl.ui.TokenMarketBlockLegacy
import dagger.assisted.Assisted import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
@ -29,17 +27,10 @@ internal class DefaultTokenMarketBlockComponent @AssistedInject constructor(
override fun Content(modifier: Modifier) { override fun Content(modifier: Modifier) {
val state by model.state.collectAsStateWithLifecycle() val state by model.state.collectAsStateWithLifecycle()
if (LocalRedesignEnabled.current) { TokenMarketBlock(
TokenMarketBlock( tokenMarketBlockUM = state,
tokenMarketBlockUM = state, modifier = modifier,
modifier = modifier, )
)
} else {
TokenMarketBlockLegacy(
state = state,
modifier = modifier,
)
}
} }
@AssistedFactory @AssistedFactory

View file

@ -1,229 +0,0 @@
package com.tangem.features.markets.token.block.impl.ui
import android.content.res.Configuration
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
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.vector.ImageVector
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.tooling.preview.Preview
import com.tangem.common.ui.charts.MarketChartMini
import com.tangem.common.ui.charts.state.MarketChartRawData
import com.tangem.common.ui.tokens.TokenPriceText
import com.tangem.core.ui.components.RectangleShimmer
import com.tangem.core.ui.components.SpacerW8
import com.tangem.core.ui.components.TextShimmer
import com.tangem.core.ui.components.block.BlockCard
import com.tangem.core.ui.components.marketprice.PriceChangeInPercent
import com.tangem.core.ui.components.marketprice.PriceChangeType
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
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 java.math.BigDecimal
import kotlin.random.Random
@Composable
internal fun TokenMarketBlockLegacy(state: TokenMarketBlockUM, modifier: Modifier = Modifier) {
BlockCard(
modifier = modifier,
enabled = state.currentPrice != null,
onClick = state.onClick,
content = {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(TangemTheme.dimens.spacing12),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
) {
LeftSide(
modifier = Modifier.weight(1f),
symbol = state.currencySymbol,
priceText = state.currentPrice,
priceValue = state.currentPriceValue,
priceAnnotated = state.priceAnnotated,
percentText = state.h24Percent,
type = state.priceChangeType,
)
SpacerW8()
RightSide(
modifier = Modifier,
priceChangeType = state.priceChangeType,
chartRawData = state.chartData,
)
}
},
)
}
@Suppress("LongParameterList")
@OptIn(ExperimentalLayoutApi::class)
@Composable
private fun LeftSide(
symbol: String,
priceText: String?,
priceValue: BigDecimal?,
priceAnnotated: TextReference?,
percentText: String?,
type: PriceChangeType,
modifier: Modifier = Modifier,
) {
Column(
modifier = modifier,
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing4),
) {
Text(
text = stringResourceSafe(id = R.string.wallet_marketplace_block_title, symbol),
color = TangemTheme.colors.text.tertiary,
style = TangemTheme.typography.subtitle2,
)
if (priceText != null && percentText != null) {
FlowRow(
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8),
) {
TokenPriceText(
modifier = Modifier.alignByBaseline(),
price = priceText,
priceChangeType = type,
priceValue = priceValue ?: BigDecimal.ZERO,
priceAnnotated = priceAnnotated ?: TextReference.EMPTY,
)
Row(
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8),
) {
PriceChangeInPercent(
modifier = Modifier.alignByBaseline(),
valueInPercent = percentText,
type = type,
textStyle = TangemTheme.typography.body2,
)
Text(
modifier = Modifier.alignByBaseline(),
text = stringResourceSafe(id = R.string.wallet_marketprice_block_update_time),
style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.tertiary,
)
}
}
} else {
TextShimmer(
modifier = Modifier.fillMaxWidth(fraction = 0.6f),
style = TangemTheme.typography.body2,
)
}
}
}
@Composable
private fun RightSide(
priceChangeType: PriceChangeType?,
chartRawData: MarketChartRawData?,
modifier: Modifier = Modifier,
) {
Row(
modifier = modifier.padding(vertical = TangemTheme.dimens.spacing10),
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8),
verticalAlignment = Alignment.CenterVertically,
) {
if (chartRawData != null && priceChangeType != null) {
MarketChartMini(
rawData = chartRawData,
type = priceChangeType.toChartType(),
modifier = Modifier
.requiredSize(
width = TangemTheme.dimens.size56,
height = TangemTheme.dimens.size24,
),
)
} else {
RectangleShimmer(
modifier = Modifier
.padding(vertical = TangemTheme.dimens.spacing2)
.requiredSize(
width = TangemTheme.dimens.size56,
height = TangemTheme.dimens.size20,
),
)
}
if (priceChangeType != null) {
Icon(
modifier = Modifier.requiredSize(TangemTheme.dimens.size20),
imageVector = ImageVector.vectorResource(id = R.drawable.ic_chevron_right_24),
tint = TangemTheme.colors.icon.informative,
contentDescription = null,
)
} else {
RectangleShimmer(
modifier = Modifier
.requiredSize(
width = TangemTheme.dimens.size20,
height = TangemTheme.dimens.size20,
),
)
}
}
}
@Preview(widthDp = 360)
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES, widthDp = 360)
@Composable
private fun Preview() {
val data = MarketChartRawData(
x = List(20) { Random.nextFloat().toDouble() }.toImmutableList(),
y = List(20) { Random.nextFloat().toDouble() }.toImmutableList(),
)
val state = TokenMarketBlockUM(
currencySymbol = "XRP",
currentPrice = "0,5$",
h24Percent = "0,5%",
priceChangeType = PriceChangeType.UP,
chartData = data,
onClick = {},
currentPriceValue = BigDecimal(1234),
priceAnnotated = stringReference("0,5$"),
)
TangemThemePreview {
Column(
modifier = Modifier.background(TangemTheme.colors.background.tertiary),
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8),
) {
TokenMarketBlockLegacy(
modifier = Modifier.fillMaxWidth(),
state = state,
)
TokenMarketBlockLegacy(
modifier = Modifier.fillMaxWidth(),
state = state.copy(
currentPrice = "0,0000000000012356786789$",
),
)
TokenMarketBlockLegacy(
modifier = Modifier.fillMaxWidth(),
state = state.copy(
currentPrice = null,
chartData = null,
),
)
TokenMarketBlockLegacy(
modifier = Modifier.fillMaxWidth(),
state = state.copy(
chartData = null,
),
)
}
}
}

View file

@ -20,12 +20,10 @@ import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.lerp import androidx.compose.ui.util.lerp
import com.tangem.core.ui.components.SpacerH8 import com.tangem.core.ui.components.SpacerH8
import com.tangem.core.ui.components.notifications.Notification
import com.tangem.core.ui.components.notifications.NotificationConfig import com.tangem.core.ui.components.notifications.NotificationConfig
import com.tangem.core.ui.components.pager.PagerIndicator import com.tangem.core.ui.components.pager.PagerIndicator
import com.tangem.core.ui.ds.message.TangemMessage import com.tangem.core.ui.ds.message.TangemMessage
import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.res.TangemThemePreviewRedesign import com.tangem.core.ui.res.TangemThemePreviewRedesign
@ -67,41 +65,23 @@ internal fun PromoBannersBlock(state: PromoBannersBlockUM, horizontalItemPadding
} }
@Composable @Composable
private fun bannerContainerColor(placeholder: Placeholder): Color = if (LocalRedesignEnabled.current) { private fun bannerContainerColor(placeholder: Placeholder): Color = when (placeholder) {
when (placeholder) { Placeholder.MAIN -> TangemTheme.colors2.surface.level1
Placeholder.MAIN -> TangemTheme.colors2.surface.level1 Placeholder.FEED -> TangemTheme.colors2.surface.level3
Placeholder.FEED -> TangemTheme.colors2.surface.level3 Placeholder.PAYMENT_ACCOUNT_MAIN -> TangemTheme.colors3.bg.opaque.primary
Placeholder.PAYMENT_ACCOUNT_MAIN -> TangemTheme.colors3.bg.opaque.primary
}
} else {
when (placeholder) {
Placeholder.MAIN,
Placeholder.PAYMENT_ACCOUNT_MAIN,
-> TangemTheme.colors.background.primary
Placeholder.FEED -> TangemTheme.colors.background.action
}
} }
/** /**
* Renders a banner either with the redesigned [TangemMessage] component or the legacy [Notification], * Renders a banner with the [TangemMessage] component. Uses the same [NotificationConfig] as the banner
* depending on [LocalRedesignEnabled]. Both accept the same [NotificationConfig], so the banner model * model and converters, so they stay untouched.
* and converters stay untouched.
*/ */
@Composable @Composable
private fun BannerNotification(config: NotificationConfig, containerColor: Color, modifier: Modifier = Modifier) { private fun BannerNotification(config: NotificationConfig, containerColor: Color, modifier: Modifier = Modifier) {
if (LocalRedesignEnabled.current) { TangemMessage(
TangemMessage( config = config,
config = config, modifier = modifier.fillMaxWidth(),
modifier = modifier.fillMaxWidth(), contentColor = containerColor,
contentColor = containerColor, )
)
} else {
Notification(
config = config,
modifier = modifier.fillMaxWidth(),
containerColor = containerColor,
)
}
} }
@Composable @Composable

View file

@ -26,7 +26,6 @@ import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameter
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import com.tangem.common.ui.account.AccountIcon import com.tangem.common.ui.account.AccountIcon
import com.tangem.core.res.getStringSafe
import com.tangem.core.ui.components.PrimaryButtonIconStart import com.tangem.core.ui.components.PrimaryButtonIconStart
import com.tangem.core.ui.components.SpacerH12 import com.tangem.core.ui.components.SpacerH12
import com.tangem.core.ui.components.SpacerW12 import com.tangem.core.ui.components.SpacerW12
@ -38,7 +37,6 @@ import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.message.SnackbarMessage import com.tangem.core.ui.message.SnackbarMessage
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.core.ui.res.LocalTopSnackbarHostState import com.tangem.core.ui.res.LocalTopSnackbarHostState
import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemThemePreview
@ -56,7 +54,6 @@ internal fun ParticipateBottomBlock(
code: String, code: String,
shareLink: String, shareLink: String,
expectedAwards: ExpectedAwards?, expectedAwards: ExpectedAwards?,
snackbarHostState: SnackbarHostState,
onAgreementClick: () -> Unit, onAgreementClick: () -> Unit,
onCopyClick: () -> Unit, onCopyClick: () -> Unit,
onShareClick: (String) -> Unit, onShareClick: (String) -> Unit,
@ -75,7 +72,6 @@ internal fun ParticipateBottomBlock(
AdditionalButtons( AdditionalButtons(
code = code, code = code,
shareLink = shareLink, shareLink = shareLink,
snackbarHostState = snackbarHostState,
onCopyClick = onCopyClick, onCopyClick = onCopyClick,
onShareClick = onShareClick, onShareClick = onShareClick,
) )
@ -350,7 +346,6 @@ private fun AwardAccount(accountAward: ReferralStateHolder.AccountAward) {
private fun AdditionalButtons( private fun AdditionalButtons(
code: String, code: String,
shareLink: String, shareLink: String,
snackbarHostState: SnackbarHostState,
onCopyClick: () -> Unit, onCopyClick: () -> Unit,
onShareClick: (String) -> Unit, onShareClick: (String) -> Unit,
) { ) {
@ -358,8 +353,6 @@ private fun AdditionalButtons(
val hapticFeedback = LocalHapticFeedback.current val hapticFeedback = LocalHapticFeedback.current
val coroutineScope = rememberCoroutineScope() val coroutineScope = rememberCoroutineScope()
val resources = LocalContext.current.resources
val isRedesignEnabled = LocalRedesignEnabled.current
val tangemTopSnackbarHostState = LocalTopSnackbarHostState.current val tangemTopSnackbarHostState = LocalTopSnackbarHostState.current
Row( Row(
@ -375,19 +368,12 @@ private fun AdditionalButtons(
clipboardManager.setText(AnnotatedString(code)) clipboardManager.setText(AnnotatedString(code))
coroutineScope.launch { coroutineScope.launch {
if (isRedesignEnabled) { tangemTopSnackbarHostState.showSnackbar(
tangemTopSnackbarHostState.showSnackbar( SnackbarMessage(
SnackbarMessage( startIconId = R.drawable.ic_check_24,
startIconId = R.drawable.ic_check_24, message = resourceReference(R.string.referral_promo_code_copied),
message = resourceReference(R.string.referral_promo_code_copied), ),
), )
)
} else {
snackbarHostState.showSnackbar(
message = resources.getStringSafe(R.string.referral_promo_code_copied),
duration = SnackbarDuration.Short,
)
}
} }
}, },
modifier = Modifier.weight(1f), modifier = Modifier.weight(1f),
@ -435,7 +421,6 @@ private fun ParticipateBottomBlockPreview(
code = data.code, code = data.code,
shareLink = data.shareLink, shareLink = data.shareLink,
expectedAwards = data.expectedAwards, expectedAwards = data.expectedAwards,
snackbarHostState = SnackbarHostState(),
onAgreementClick = data.onAgreementClick, onAgreementClick = data.onAgreementClick,
onCopyClick = data.onCopyClick, onCopyClick = data.onCopyClick,
onShareClick = data.onShareClick, onShareClick = data.onShareClick,

View file

@ -11,7 +11,6 @@ import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.* import androidx.compose.material3.*
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.SideEffect import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@ -38,15 +37,12 @@ import com.tangem.core.ui.components.SpacerH24
import com.tangem.core.ui.components.SpacerH32 import com.tangem.core.ui.components.SpacerH32
import com.tangem.core.ui.components.appbar.AppBarWithBackButton import com.tangem.core.ui.components.appbar.AppBarWithBackButton
import com.tangem.core.ui.components.currency.icon.CurrencyIconState import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.core.ui.components.snackbar.CopiedTextSnackbar
import com.tangem.core.ui.components.snackbar.TangemSnackbar
import com.tangem.core.ui.components.token.TokenItem import com.tangem.core.ui.components.token.TokenItem
import com.tangem.core.ui.components.token.state.TokenItemState import com.tangem.core.ui.components.token.state.TokenItemState
import com.tangem.core.ui.components.token.state.TokenItemState.FiatAmountState import com.tangem.core.ui.components.token.state.TokenItemState.FiatAmountState
import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.message.SnackbarMessage import com.tangem.core.ui.message.SnackbarMessage
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.core.ui.res.LocalTopSnackbarHostState import com.tangem.core.ui.res.LocalTopSnackbarHostState
import com.tangem.core.ui.res.TangemColorPalette import com.tangem.core.ui.res.TangemColorPalette
import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemTheme
@ -69,8 +65,6 @@ import java.util.UUID
*/ */
@Composable @Composable
internal fun ReferralScreen(stateHolder: ReferralStateHolder) { internal fun ReferralScreen(stateHolder: ReferralStateHolder) {
val snackbarHostState = remember(::SnackbarHostState)
Scaffold( Scaffold(
topBar = { topBar = {
AppBarWithBackButton( AppBarWithBackButton(
@ -79,61 +73,30 @@ internal fun ReferralScreen(stateHolder: ReferralStateHolder) {
onBackClick = stateHolder.headerState.onBackClicked, onBackClick = stateHolder.headerState.onBackClicked,
) )
}, },
snackbarHost = {
SnackbarHost(
hostState = snackbarHostState,
modifier = Modifier
.padding(horizontal = TangemTheme.dimens.spacing16)
.padding(bottom = TangemTheme.dimens.spacing108),
) {
// TODO: use StateEvent
if (stateHolder.errorSnackbar != null) {
TangemSnackbar(data = it, actionOnNewLine = true)
} else {
CopiedTextSnackbar(it)
}
}
},
containerColor = TangemTheme.colors.background.secondary, containerColor = TangemTheme.colors.background.secondary,
) { ) { paddingValues ->
ReferralContent( ReferralContent(
stateHolder = stateHolder, stateHolder = stateHolder,
snackbarHostState = snackbarHostState,
onAgreementClick = stateHolder.analytics.onAgreementClicked, onAgreementClick = stateHolder.analytics.onAgreementClicked,
modifier = Modifier.padding(it), modifier = Modifier.padding(paddingValues),
) )
} }
val errorSnackbar = stateHolder.errorSnackbar val errorSnackbar = stateHolder.errorSnackbar
val coroutineScope = rememberCoroutineScope() val coroutineScope = rememberCoroutineScope()
val resources = LocalContext.current.resources val resources = LocalContext.current.resources
val isRedesignEnabled = LocalRedesignEnabled.current
val topSnackbarHostState = LocalTopSnackbarHostState.current val topSnackbarHostState = LocalTopSnackbarHostState.current
SideEffect { SideEffect {
if (errorSnackbar != null) { if (errorSnackbar != null) {
coroutineScope.launch { coroutineScope.launch {
if (isRedesignEnabled) { topSnackbarHostState.showSnackbar(
topSnackbarHostState.showSnackbar(
message = resources.getMessageForErrorSnackbar(errorSnackbar.throwable),
actionLabel = resources.getStringSafe(R.string.warning_button_ok),
duration = SnackbarMessage.Duration.Indefinite,
)
errorSnackbar.onOkClicked()
return@launch
}
val result = snackbarHostState.showSnackbar(
message = resources.getMessageForErrorSnackbar(errorSnackbar.throwable), message = resources.getMessageForErrorSnackbar(errorSnackbar.throwable),
actionLabel = resources.getStringSafe(R.string.warning_button_ok), actionLabel = resources.getStringSafe(R.string.warning_button_ok),
duration = SnackbarDuration.Indefinite, duration = SnackbarMessage.Duration.Indefinite,
) )
if (result == SnackbarResult.ActionPerformed) { errorSnackbar.onOkClicked()
errorSnackbar.onOkClicked()
}
} }
} }
} }
@ -142,7 +105,6 @@ internal fun ReferralScreen(stateHolder: ReferralStateHolder) {
@Composable @Composable
private fun ReferralContent( private fun ReferralContent(
stateHolder: ReferralStateHolder, stateHolder: ReferralStateHolder,
snackbarHostState: SnackbarHostState,
onAgreementClick: () -> Unit, onAgreementClick: () -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
@ -155,7 +117,6 @@ private fun ReferralContent(
item { item {
ReferralInfo( ReferralInfo(
stateHolder = stateHolder, stateHolder = stateHolder,
snackbarHostState = snackbarHostState,
onAgreementClick = onAgreementClick, onAgreementClick = onAgreementClick,
) )
} }
@ -189,11 +150,7 @@ private fun Header() {
} }
@Composable @Composable
private fun ReferralInfo( private fun ReferralInfo(stateHolder: ReferralStateHolder, onAgreementClick: () -> Unit) {
stateHolder: ReferralStateHolder,
snackbarHostState: SnackbarHostState,
onAgreementClick: () -> Unit,
) {
when (val state = stateHolder.referralInfoState) { when (val state = stateHolder.referralInfoState) {
is ReferralInfoState.ParticipantContent -> { is ReferralInfoState.ParticipantContent -> {
Conditions(state = state) Conditions(state = state)
@ -203,7 +160,6 @@ private fun ReferralInfo(
shareLink = state.shareLink, shareLink = state.shareLink,
expectedAwards = state.expectedAwards, expectedAwards = state.expectedAwards,
accountAward = state.accountAward, accountAward = state.accountAward,
snackbarHostState = snackbarHostState,
onAgreementClick = onAgreementClick, onAgreementClick = onAgreementClick,
onCopyClick = stateHolder.analytics.onCopyClicked, onCopyClick = stateHolder.analytics.onCopyClicked,
onShareClick = stateHolder.analytics.onShareClicked, onShareClick = stateHolder.analytics.onShareClicked,

View file

@ -11,12 +11,10 @@ import com.tangem.core.decompose.context.childByContext
import com.tangem.core.decompose.model.getOrCreateModel import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.decompose.navigation.inner.InnerRouter import com.tangem.core.decompose.navigation.inner.InnerRouter
import com.tangem.core.ui.decompose.ComposableContentComponent import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.features.tokenreceive.TokenReceiveComponent import com.tangem.features.tokenreceive.TokenReceiveComponent
import com.tangem.features.tokenreceive.model.TokenReceiveModel import com.tangem.features.tokenreceive.model.TokenReceiveModel
import com.tangem.features.tokenreceive.route.TokenReceiveRoutes import com.tangem.features.tokenreceive.route.TokenReceiveRoutes
import com.tangem.features.tokenreceive.ui.TokenReceiveContentSheet import com.tangem.features.tokenreceive.ui.TokenReceiveContentSheet
import com.tangem.features.tokenreceive.ui.TokenReceiveContentSheetLegacy
import dagger.assisted.Assisted import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
@ -45,21 +43,12 @@ internal class DefaultTokenReceiveComponent @AssistedInject constructor(
override fun BottomSheet() { override fun BottomSheet() {
val content by contentStack.subscribeAsState() val content by contentStack.subscribeAsState()
val currentRoute = content.active.configuration val currentRoute = content.active.configuration
if (LocalRedesignEnabled.current) { TokenReceiveContentSheet(
TokenReceiveContentSheet( route = currentRoute,
route = currentRoute, onCloseClick = ::dismiss,
onCloseClick = ::dismiss, onBackClick = ::onChildBack,
onBackClick = ::onChildBack, contentStack = content,
contentStack = content, )
)
} else {
TokenReceiveContentSheetLegacy(
route = currentRoute,
onCloseClick = ::dismiss,
onBackClick = ::onChildBack,
contentStack = content,
)
}
} }
override fun dismiss() = model.params.onDismiss() override fun dismiss() = model.params.onDismiss()

View file

@ -9,12 +9,10 @@ import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.model.getOrCreateModel import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.ui.components.currency.icon.CurrencyIconState import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.core.ui.decompose.ComposableContentComponent import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.domain.tokens.model.analytics.TokenReceiveCopyActionSource import com.tangem.domain.tokens.model.analytics.TokenReceiveCopyActionSource
import com.tangem.features.tokenreceive.entity.ReceiveAddress import com.tangem.features.tokenreceive.entity.ReceiveAddress
import com.tangem.features.tokenreceive.model.TokenReceiveAssetsModel import com.tangem.features.tokenreceive.model.TokenReceiveAssetsModel
import com.tangem.features.tokenreceive.ui.TokenReceiveAssetsContent import com.tangem.features.tokenreceive.ui.TokenReceiveAssetsContent
import com.tangem.features.tokenreceive.ui.TokenReceiveAssetsContentLegacy
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
internal class TokenReceiveAssetsComponent( internal class TokenReceiveAssetsComponent(
@ -27,11 +25,7 @@ internal class TokenReceiveAssetsComponent(
@Composable @Composable
override fun Content(modifier: Modifier) { override fun Content(modifier: Modifier) {
val state by model.state.collectAsStateWithLifecycle() val state by model.state.collectAsStateWithLifecycle()
if (LocalRedesignEnabled.current) { TokenReceiveAssetsContent(assetsUM = state)
TokenReceiveAssetsContent(assetsUM = state)
} else {
TokenReceiveAssetsContentLegacy(assetsUM = state)
}
} }
internal interface TokenReceiveAssetsModelCallback { internal interface TokenReceiveAssetsModelCallback {

View file

@ -7,14 +7,12 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tangem.core.decompose.context.AppComponentContext import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.model.getOrCreateModel import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.ui.decompose.ComposableContentComponent import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.domain.models.TokenReceiveType import com.tangem.domain.models.TokenReceiveType
import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.tokens.model.analytics.TokenReceiveCopyActionSource import com.tangem.domain.tokens.model.analytics.TokenReceiveCopyActionSource
import com.tangem.features.tokenreceive.entity.ReceiveAddress import com.tangem.features.tokenreceive.entity.ReceiveAddress
import com.tangem.features.tokenreceive.model.TokenReceiveQrCodeModel import com.tangem.features.tokenreceive.model.TokenReceiveQrCodeModel
import com.tangem.features.tokenreceive.ui.TokenReceiveQrCodeContent import com.tangem.features.tokenreceive.ui.TokenReceiveQrCodeContent
import com.tangem.features.tokenreceive.ui.TokenReceiveQrCodeContentLegacy
internal class TokenReceiveQrCodeComponent( internal class TokenReceiveQrCodeComponent(
appComponentContext: AppComponentContext, appComponentContext: AppComponentContext,
@ -26,11 +24,7 @@ internal class TokenReceiveQrCodeComponent(
@Composable @Composable
override fun Content(modifier: Modifier) { override fun Content(modifier: Modifier) {
val state by model.state.collectAsStateWithLifecycle() val state by model.state.collectAsStateWithLifecycle()
if (LocalRedesignEnabled.current) { TokenReceiveQrCodeContent(qrCodeUM = state)
TokenReceiveQrCodeContent(qrCodeUM = state)
} else {
TokenReceiveQrCodeContentLegacy(qrCodeUM = state)
}
} }
internal interface TokenReceiveQrCodeModelCallback { internal interface TokenReceiveQrCodeModelCallback {

View file

@ -8,11 +8,9 @@ import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.model.getOrCreateModel import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.ui.components.currency.icon.CurrencyIconState import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.core.ui.decompose.ComposableContentComponent import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.domain.models.network.Network import com.tangem.domain.models.network.Network
import com.tangem.features.tokenreceive.model.TokenReceiveWarningModel import com.tangem.features.tokenreceive.model.TokenReceiveWarningModel
import com.tangem.features.tokenreceive.ui.TokenReceiveWarningContent import com.tangem.features.tokenreceive.ui.TokenReceiveWarningContent
import com.tangem.features.tokenreceive.ui.TokenReceiveWarningContentLegacy
internal class TokenReceiveWarningComponent( internal class TokenReceiveWarningComponent(
appComponentContext: AppComponentContext, appComponentContext: AppComponentContext,
@ -24,11 +22,7 @@ internal class TokenReceiveWarningComponent(
@Composable @Composable
override fun Content(modifier: Modifier) { override fun Content(modifier: Modifier) {
val state by model.state.collectAsStateWithLifecycle() val state by model.state.collectAsStateWithLifecycle()
if (LocalRedesignEnabled.current) { TokenReceiveWarningContent(warningUM = state)
TokenReceiveWarningContent(warningUM = state)
} else {
TokenReceiveWarningContentLegacy(warningUM = state)
}
} }
internal interface TokenReceiveWarningModelCallback { internal interface TokenReceiveWarningModelCallback {

View file

@ -1,30 +0,0 @@
package com.tangem.features.tokenreceive.ui
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.SnackbarHostState
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.components.snackbar.CopiedTextSnackbarHost
import com.tangem.core.ui.res.LocalRedesignEnabled
@Deprecated(
"Use TangemTopSnackbarHost instead. Will be removed with redesign",
)
@Composable
internal fun ContainerWithSnackbarHost(snackbarHostState: SnackbarHostState, content: @Composable () -> Unit) {
Box {
content()
if (LocalRedesignEnabled.current.not()) {
CopiedTextSnackbarHost(
hostState = snackbarHostState,
modifier = Modifier
.align(Alignment.BottomCenter)
.padding(bottom = 80.dp),
)
}
}
}

View file

@ -1,672 +0,0 @@
package com.tangem.features.tokenreceive.ui
import android.content.res.Configuration
import androidx.compose.animation.animateColorAsState
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.TextAutoSize
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.res.painterResource
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.PreviewParameterProvider
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.util.fastFilter
import androidx.compose.ui.util.fastForEach
import com.tangem.common.ui.notifications.NotificationUM
import com.tangem.core.res.getStringSafe
import com.tangem.core.ui.R
import com.tangem.core.ui.components.*
import com.tangem.core.ui.components.atoms.text.EllipsisText
import com.tangem.core.ui.components.atoms.text.TextEllipsis
import com.tangem.core.ui.components.buttons.actions.ActionBaseButton
import com.tangem.core.ui.components.buttons.actions.ActionButtonConfig
import com.tangem.core.ui.components.buttons.actions.ActionButtonContent
import com.tangem.core.ui.components.buttons.small.TangemIconButton
import com.tangem.core.ui.components.currency.icon.CurrencyIcon
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.core.ui.components.notifications.Notification
import com.tangem.core.ui.extensions.*
import com.tangem.core.ui.message.SnackbarMessage
import com.tangem.core.ui.res.*
import com.tangem.features.tokenreceive.entity.ReceiveAddress
import com.tangem.features.tokenreceive.ui.state.ReceiveAssetsUM
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.launch
@Composable
internal fun TokenReceiveAssetsContentLegacy(assetsUM: ReceiveAssetsUM) {
val snackbarHostState = remember(::SnackbarHostState)
ContainerWithSnackbarHost(snackbarHostState = snackbarHostState) {
Column(
modifier = Modifier
.fillMaxWidth()
.background(color = TangemTheme.colors.background.tertiary)
.padding(bottom = 16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
AddressBlock(
assetsUM = assetsUM,
snackbarHostState = snackbarHostState,
)
if (assetsUM.isEnsResultLoading) {
SpacerH8()
LoadingBlock(modifier = Modifier.padding(horizontal = 16.dp))
}
SpacerH12()
Info(
showMemoDisclaimer = assetsUM.showMemoDisclaimer,
notificationConfigs = assetsUM.notificationConfigs,
currencyIconState = assetsUM.currencyIconState,
)
}
}
}
@Composable
private fun Info(
currencyIconState: CurrencyIconState,
notificationConfigs: ImmutableList<NotificationUM>,
showMemoDisclaimer: Boolean,
modifier: Modifier = Modifier,
) {
Column(
modifier = modifier.padding(horizontal = 16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(space = 16.dp),
) {
if (showMemoDisclaimer) {
Text(
modifier = Modifier
.padding(horizontal = 18.dp)
.fillMaxWidth(),
text = stringResourceSafe(R.string.receive_bottom_sheet_no_memo_required_message),
style = TangemTheme.typography.caption1,
color = TangemTheme.colors.text.tertiary,
textAlign = TextAlign.Center,
)
}
notificationConfigs.fastForEach { notificationConfig ->
key(notificationConfig.hashCode()) {
if (notificationConfig is NotificationUM.Warning.YieldSupplyIsActive) {
YieldSupplyDepositedWarning(
currencyIconState = currencyIconState,
title = stringResourceSafe(
R.string.yield_module_balance_info_sheet_title,
notificationConfig.tokenName,
),
subtitle = stringResourceSafe(R.string.yield_module_balance_info_sheet_subtitle),
)
} else {
Notification(config = notificationConfig.config)
}
}
}
}
}
@Composable
private fun YieldSupplyDepositedWarning(
currencyIconState: CurrencyIconState,
title: String,
subtitle: String,
modifier: Modifier = Modifier,
) {
Surface(
modifier = modifier
.defaultMinSize(minHeight = TangemTheme.dimens.size44)
.fillMaxWidth(),
shape = TangemTheme.shapes.roundedCornersXMedium,
color = TangemTheme.colors.button.disabled,
) {
Row(
modifier = Modifier
.padding(all = TangemTheme.dimens.spacing12),
) {
Box(
modifier = Modifier
.height(22.dp)
.width(22.dp),
contentAlignment = Alignment.TopStart,
) {
CurrencyIcon(
modifier = Modifier
.align(Alignment.TopStart)
.size(13.dp),
state = currencyIconState,
shouldDisplayNetwork = false,
iconSize = 13.dp,
)
Image(
modifier = Modifier
.background(TangemTheme.colors.background.tertiary, RoundedCornerShape(15.dp))
.padding(1.dp)
.size(15.dp)
.align(Alignment.BottomEnd),
imageVector = ImageVector.vectorResource(R.drawable.img_aave_22),
contentDescription = null,
)
}
SpacerW(width = TangemTheme.dimens.spacing8)
Column(modifier = Modifier.weight(1f)) {
Text(
text = title,
color = TangemTheme.colors.text.primary1,
style = TangemTheme.typography.button,
)
SpacerH(height = TangemTheme.dimens.spacing2)
Text(
text = subtitle,
color = TangemTheme.colors.text.tertiary,
style = TangemTheme.typography.caption2,
)
}
}
}
}
@Composable
private fun AddressBlock(assetsUM: ReceiveAssetsUM, snackbarHostState: SnackbarHostState) {
val hapticFeedback = LocalHapticFeedback.current
val coroutineScope = rememberCoroutineScope()
val context = LocalContext.current
val resources = context.resources
val isRedesignEnabled = LocalRedesignEnabled.current
val topSnackbarHostState = LocalTopSnackbarHostState.current
assetsUM.addresses
.fastFilter { it.type is ReceiveAddress.Type.Ens }
.fastForEach { address ->
key(address.value) {
EnsItem(
modifier = Modifier.padding(horizontal = 16.dp),
onCopyClick = {
assetsUM.onCopyClick(address)
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
coroutineScope.launch {
if (isRedesignEnabled) {
topSnackbarHostState.showSnackbar(
SnackbarMessage(
startIconId = R.drawable.ic_check_24,
message = resourceReference(R.string.wallet_notification_address_copied),
),
)
} else {
snackbarHostState.showSnackbar(
message = resources.getStringSafe(
R.string.wallet_notification_address_copied,
),
)
}
}
},
address = address.value,
)
SpacerH8()
}
}
PrimaryAddressesItems(
addresses = assetsUM.addresses.fastFilter { it.type is ReceiveAddress.Type.Primary }.toImmutableList(),
currencyIconState = assetsUM.currencyIconState,
snackbarHostState = snackbarHostState,
onShareClick = { address ->
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
assetsUM.onShareClick(address)
},
onCopyClick = { address ->
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
coroutineScope.launch {
snackbarHostState.showSnackbar(
message = resources.getStringSafe(
R.string.wallet_notification_address_copied,
),
)
}
assetsUM.onCopyClick(address)
},
onOpenQrCodeClick = { address ->
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
assetsUM.onOpenQrCodeClick(address)
},
)
}
@Suppress("LongParameterList")
@Composable
private fun PrimaryAddressesItems(
addresses: ImmutableList<ReceiveAddress>,
currencyIconState: CurrencyIconState,
snackbarHostState: SnackbarHostState,
onShareClick: (String) -> Unit,
onCopyClick: (ReceiveAddress) -> Unit,
onOpenQrCodeClick: (String) -> Unit,
) {
if (addresses.isEmpty()) return
var selectedAddress by remember { mutableStateOf(addresses.first()) }
val pagerState = rememberPagerState(
initialPage = 0,
initialPageOffsetFraction = 0f,
pageCount = addresses::count,
)
LaunchedEffect(key1 = pagerState.currentPage) {
selectedAddress = addresses[pagerState.currentPage]
}
HorizontalPager(
state = pagerState,
contentPadding = PaddingValues(horizontal = 16.dp),
pageSpacing = 16.dp,
) {
AddressItem(
currencyIconState = currencyIconState,
onOpenQrCodeClick = { onOpenQrCodeClick(selectedAddress.value) },
onCopyClick = { onCopyClick(selectedAddress) },
onShareClick = { onShareClick(selectedAddress.value) },
primaryType = selectedAddress.type as ReceiveAddress.Type.Primary,
address = selectedAddress.value,
isDynamicAddress = selectedAddress.type is ReceiveAddress.Type.Primary.Dynamic,
snackbarHostState = snackbarHostState,
)
}
SpacerH(4.dp)
if (pagerState.pageCount > 1) {
val indicatorState = rememberLazyListState()
val selectedColor = TangemTheme.colors.icon.primary1
val unselectedColor = TangemTheme.colors.icon.informative
LazyRow(
modifier = Modifier.height(20.dp),
state = indicatorState,
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically,
) {
repeat(pagerState.pageCount) { iteration ->
item(key = iteration) {
val color by animateColorAsState(
targetValue = if (pagerState.currentPage == iteration) selectedColor else unselectedColor,
label = "",
)
Box(
modifier = Modifier
.padding(horizontal = 4.dp, vertical = 6.dp)
.background(color = color, shape = CircleShape)
.size(7.dp),
)
}
}
}
}
}
@Suppress("LongParameterList")
@Composable
private fun AddressItem(
currencyIconState: CurrencyIconState,
onOpenQrCodeClick: () -> Unit,
onCopyClick: () -> Unit,
onShareClick: () -> Unit,
primaryType: ReceiveAddress.Type.Primary,
address: String,
isDynamicAddress: Boolean,
snackbarHostState: SnackbarHostState,
modifier: Modifier = Modifier,
) {
Card(
modifier = modifier.fillMaxWidth(),
shape = RoundedCornerShape(16.dp),
colors = CardDefaults.cardColors(containerColor = TangemTheme.colors.background.action),
onClick = onOpenQrCodeClick,
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 12.dp, vertical = 32.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
CurrencyIcon(
modifier = Modifier.size(56.dp),
state = currencyIconState,
shouldDisplayNetwork = true,
iconSize = 56.dp,
)
SpacerH(12.dp)
if (isDynamicAddress) {
DynamicAddressBadge()
SpacerH(8.dp)
}
Text(
text = primaryType.displayName.resolveReference(),
color = TangemTheme.colors.text.primary1,
style = TangemTheme.typography.subtitle1,
textAlign = TextAlign.Center,
)
Text(
modifier = Modifier
.heightIn(min = 40.dp)
.padding(horizontal = 16.dp),
text = address,
color = TangemTheme.colors.text.tertiary,
style = TangemTheme.typography.body2,
textAlign = TextAlign.Center,
)
SpacerH8()
Row(
modifier = Modifier
.clip(RoundedCornerShape(16.dp))
.clickable(onClick = onOpenQrCodeClick)
.padding(vertical = 6.dp, horizontal = 12.dp),
horizontalArrangement = Arrangement.Center,
) {
Icon(
modifier = Modifier.size(16.dp),
imageVector = ImageVector.vectorResource(R.drawable.ic_qrcode_new_24),
contentDescription = null,
)
SpacerW(4.dp)
Text(
text = stringResourceSafe(R.string.token_receive_show_qr_code_title),
style = TangemTheme.typography.caption1,
color = TangemTheme.colors.text.secondary,
)
}
SpacerH(20.dp)
ButtonsBlock(
snackbarHostState = snackbarHostState,
onCopyClick = onCopyClick,
onShareClick = onShareClick,
)
}
}
}
@Composable
private fun ButtonsBlock(snackbarHostState: SnackbarHostState, onCopyClick: () -> Unit, onShareClick: () -> Unit) {
val hapticFeedback = LocalHapticFeedback.current
val coroutineScope = rememberCoroutineScope()
val context = LocalContext.current
val resources = context.resources
val isRedesignEnabled = LocalRedesignEnabled.current
val topSnackbarHostState = LocalTopSnackbarHostState.current
Row(
modifier = Modifier.width(IntrinsicSize.Min),
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
ActionButtonWithResizableText(
modifier = Modifier.weight(1f),
config = ActionButtonConfig(
text = TextReference.Res(id = R.string.common_copy),
iconResId = R.drawable.ic_copy_new_24,
onClick = {
onCopyClick()
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
coroutineScope.launch {
if (isRedesignEnabled) {
topSnackbarHostState.showSnackbar(
SnackbarMessage(
startIconId = R.drawable.ic_check_24,
message = resourceReference(R.string.wallet_notification_address_copied),
),
)
} else {
snackbarHostState.showSnackbar(
message = resources.getStringSafe(R.string.wallet_notification_address_copied),
)
}
}
},
),
)
ActionButtonWithResizableText(
modifier = Modifier.weight(1f),
config = ActionButtonConfig(
text = TextReference.Res(id = R.string.common_share),
iconResId = R.drawable.ic_share_24,
onClick = {
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
onShareClick()
},
),
)
}
}
@Composable
private fun EnsItem(onCopyClick: () -> Unit, address: String, modifier: Modifier = Modifier) {
Card(
modifier = modifier.fillMaxWidth(),
shape = RoundedCornerShape(16.dp),
colors = CardDefaults.cardColors(containerColor = TangemTheme.colors.background.action),
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 14.dp, horizontal = 12.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Image(
modifier = Modifier.size(36.dp),
imageVector = ImageVector.vectorResource(R.drawable.ic_ens_36),
contentDescription = null,
)
SpacerW12()
EllipsisText(
modifier = Modifier.weight(1f),
text = address,
ellipsis = TextEllipsis.Middle,
color = TangemTheme.colors.text.primary1,
style = TangemTheme.typography.subtitle1,
)
TangemIconButton(
modifier = Modifier.size(TangemTheme.dimens.size28),
iconRes = R.drawable.ic_share_24,
innerPadding = 6.dp,
onClick = onCopyClick,
)
}
}
}
@Composable
private fun LoadingBlock(modifier: Modifier = Modifier) {
Card(
modifier = modifier.fillMaxWidth(),
shape = RoundedCornerShape(16.dp),
colors = CardDefaults.cardColors(containerColor = TangemTheme.colors.background.action),
) {
Box(
modifier = Modifier
.fillMaxWidth()
.heightIn(min = 64.dp),
contentAlignment = Alignment.Center,
) {
CircularProgressIndicator(
color = TangemTheme.colors.icon.informative,
modifier = Modifier.padding(TangemTheme.dimens.spacing8),
)
}
}
}
@Composable
private fun ActionButtonWithResizableText(config: ActionButtonConfig, modifier: Modifier = Modifier) {
ActionBaseButton(
config = config,
shape = RoundedCornerShape(size = TangemTheme.dimens.radius24),
content = { contentModifier ->
ActionButtonContent(
config = config,
text = { color ->
Text(
text = config.text.resolveReference(),
autoSize = TextAutoSize.StepBased(
minFontSize = 10.sp,
maxFontSize = TangemTheme.typography.button.fontSize,
),
color = color,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
style = TangemTheme.typography.button,
)
},
modifier = contentModifier.padding(horizontal = 16.dp),
paddingBetweenIconAndText = 4.dp,
)
},
modifier = modifier,
color = TangemTheme.colors.button.secondary,
)
}
@Composable
private fun DynamicAddressBadge(modifier: Modifier = Modifier) {
Row(
modifier = modifier
.background(
color = TangemTheme.colors.text.accent.copy(alpha = 0.1f),
shape = RoundedCornerShape(percent = 50),
)
.padding(horizontal = 12.dp, vertical = 4.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(4.dp),
) {
Icon(
painter = painterResource(
id = R.drawable.ic_dynamic_addresses_badge_16,
),
contentDescription = null,
modifier = Modifier.size(16.dp),
tint = TangemTheme.colors.icon.accent,
)
Text(
text = stringResourceSafe(R.string.dynamic_addresses_receive_badge),
style = TangemTheme.typography.caption1,
color = TangemTheme.colors.text.accent,
)
}
}
@Composable
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
private fun Preview_TokenReceiveAssetsContent(
@PreviewParameter(TokenReceiveAssetsContentProvider::class) params: ReceiveAssetsUM,
) {
TangemThemePreview {
TokenReceiveAssetsContentLegacy(assetsUM = params)
}
}
internal class TokenReceiveAssetsContentProvider : PreviewParameterProvider<ReceiveAssetsUM> {
val address = ReceiveAddress(
value = "0xe5178c7d4d0e861ed2e9414e045b501226b0de8d",
type = ReceiveAddress.Type.Primary.Default(
displayName = stringReference("Etherium address"),
),
)
private val dynamicAddress = ReceiveAddress(
value = "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb",
type = ReceiveAddress.Type.Primary.Dynamic(
displayName = stringReference("Ethereum address"),
),
)
private val tokenIconState = CurrencyIconState.TokenIcon(
url = null,
topBadgeIconResId = null,
fallbackTint = TangemColorPalette.Black,
fallbackBackground = TangemColorPalette.Meadow,
isGrayscale = false,
shouldShowCustomBadge = false,
)
private val config = ReceiveAssetsUM(
notificationConfigs =
persistentListOf(
NotificationUM.Warning(
title = stringReference("Send only XLM on the Ethereum network"),
subtitle = resourceReference(R.string.receive_bottom_sheet_warning_message_description),
),
),
addresses = persistentListOf(
address,
address,
address.copy(type = ReceiveAddress.Type.Ens, value = "papasha.eth"),
),
showMemoDisclaimer = false,
onCopyClick = {},
onOpenQrCodeClick = {},
isEnsResultLoading = true,
network = "USDT",
currencyIconState = CurrencyIconState.Locked,
onShareClick = {},
)
private val yieldSupplyIsActiveConfig = config.copy(
notificationConfigs = persistentListOf(
NotificationUM.Warning.YieldSupplyIsActive(tokenName = "USDT"),
),
isEnsResultLoading = false,
currencyIconState = tokenIconState,
)
private val dynamicAddressConfig = config.copy(
addresses = persistentListOf(
dynamicAddress,
address,
address.copy(type = ReceiveAddress.Type.Ens, value = "papasha.eth"),
),
isEnsResultLoading = false,
notificationConfigs = persistentListOf(),
currencyIconState = tokenIconState,
)
override val values: Sequence<ReceiveAssetsUM>
get() = sequenceOf(config, yieldSupplyIsActiveConfig, dynamicAddressConfig)
}

View file

@ -1,94 +0,0 @@
package com.tangem.features.tokenreceive.ui
import androidx.compose.animation.animateContentSize
import androidx.compose.animation.core.tween
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.arkivanov.decompose.extensions.compose.stack.Children
import com.arkivanov.decompose.extensions.compose.stack.animation.fade
import com.arkivanov.decompose.extensions.compose.stack.animation.stackAnimation
import com.arkivanov.decompose.router.stack.ChildStack
import com.tangem.core.ui.R
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheet
import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheetTitle
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.features.tokenreceive.route.TokenReceiveRoutes
@Composable
internal fun TokenReceiveContentSheetLegacy(
route: TokenReceiveRoutes,
onCloseClick: () -> Unit,
onBackClick: () -> Unit,
contentStack: ChildStack<TokenReceiveRoutes, ComposableContentComponent>,
) {
TangemModalBottomSheet<TangemBottomSheetConfigContent.Empty>(
config = TangemBottomSheetConfig(
isShown = true,
onDismissRequest = onCloseClick,
content = TangemBottomSheetConfigContent.Empty,
),
onBack = onBackClick,
containerColor = TangemTheme.colors.background.tertiary,
title = {
Title(
route = route,
onBackClick = onBackClick,
onCloseClick = onCloseClick,
)
},
content = {
TokenReceiveContent(
stackState = contentStack,
modifier = Modifier,
)
},
)
}
@Composable
private fun Title(route: TokenReceiveRoutes, onBackClick: () -> Unit, onCloseClick: () -> Unit) {
when (route) {
is TokenReceiveRoutes.QrCode -> {
TangemModalBottomSheetTitle(
startIconRes = R.drawable.ic_back_24,
onStartClick = onBackClick,
endIconRes = R.drawable.ic_close_24,
onEndClick = onCloseClick,
)
}
TokenReceiveRoutes.ReceiveAssets -> {
TangemModalBottomSheetTitle(
title = resourceReference(R.string.domain_receive_assets_navigation_title),
endIconRes = R.drawable.ic_close_24,
onEndClick = onCloseClick,
)
}
TokenReceiveRoutes.Warning -> {
TangemModalBottomSheetTitle(
endIconRes = R.drawable.ic_close_24,
onEndClick = onCloseClick,
)
}
}
}
@Composable
private fun TokenReceiveContent(
stackState: ChildStack<TokenReceiveRoutes, ComposableContentComponent>,
modifier: Modifier = Modifier,
) {
Children(
stack = stackState,
animation = stackAnimation(fade(animationSpec = tween(durationMillis = 100))),
modifier = modifier
.fillMaxSize()
.animateContentSize(),
) {
it.instance.Content(Modifier.fillMaxSize())
}
}

View file

@ -0,0 +1,111 @@
package com.tangem.features.tokenreceive.ui
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import com.tangem.common.ui.notifications.NotificationUM
import com.tangem.core.ui.R
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.res.TangemColorPalette
import com.tangem.features.tokenreceive.entity.ReceiveAddress
import com.tangem.features.tokenreceive.ui.state.QrCodeUM
import com.tangem.features.tokenreceive.ui.state.ReceiveAssetsUM
import com.tangem.features.tokenreceive.ui.state.WarningUM
import kotlinx.collections.immutable.persistentListOf
internal class TokenReceiveAssetsContentProvider : PreviewParameterProvider<ReceiveAssetsUM> {
val address = ReceiveAddress(
value = "0xe5178c7d4d0e861ed2e9414e045b501226b0de8d",
type = ReceiveAddress.Type.Primary.Default(
displayName = stringReference("Ethereum address"),
),
)
private val dynamicAddress = ReceiveAddress(
value = "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb",
type = ReceiveAddress.Type.Primary.Dynamic(
displayName = stringReference("Ethereum address"),
),
)
private val tokenIconState = CurrencyIconState.TokenIcon(
url = null,
topBadgeIconResId = null,
fallbackTint = TangemColorPalette.Black,
fallbackBackground = TangemColorPalette.Meadow,
isGrayscale = false,
shouldShowCustomBadge = false,
)
private val config = ReceiveAssetsUM(
notificationConfigs =
persistentListOf(
NotificationUM.Warning(
title = stringReference("Send only XLM on the Ethereum network"),
subtitle = resourceReference(R.string.receive_bottom_sheet_warning_message_description),
),
),
addresses = persistentListOf(
address,
address,
address.copy(type = ReceiveAddress.Type.Ens, value = "papasha.eth"),
),
showMemoDisclaimer = false,
onCopyClick = {},
onOpenQrCodeClick = {},
isEnsResultLoading = true,
network = "USDT",
currencyIconState = CurrencyIconState.Locked,
onShareClick = {},
)
private val yieldSupplyIsActiveConfig = config.copy(
notificationConfigs = persistentListOf(
NotificationUM.Warning.YieldSupplyIsActive(tokenName = "USDT"),
),
isEnsResultLoading = false,
currencyIconState = tokenIconState,
)
private val dynamicAddressConfig = config.copy(
addresses = persistentListOf(
dynamicAddress,
address,
address.copy(type = ReceiveAddress.Type.Ens, value = "papasha.eth"),
),
isEnsResultLoading = false,
notificationConfigs = persistentListOf(),
currencyIconState = tokenIconState,
)
override val values: Sequence<ReceiveAssetsUM>
get() = sequenceOf(config, yieldSupplyIsActiveConfig, dynamicAddressConfig)
}
internal class TokenReceiveQrCodeContentPreviewProvider : PreviewParameterProvider<QrCodeUM> {
private val config = QrCodeUM(
network = "Ethereum",
addressName = stringReference("Ethereum"),
addressValue = "0xe5178c7d4d0e861ed2e9414e045b501226b0de8d",
onCopyClick = {},
onShareClick = {},
)
override val values: Sequence<QrCodeUM>
get() = sequenceOf(config)
}
internal class TokenReceiveWarningContentProvider : PreviewParameterProvider<WarningUM> {
val iconState = CurrencyIconState.TokenIcon(
url = null,
topBadgeIconResId = null,
fallbackTint = TangemColorPalette.Black,
fallbackBackground = TangemColorPalette.Meadow,
isGrayscale = false,
shouldShowCustomBadge = false,
)
override val values: Sequence<WarningUM>
get() = sequenceOf(
WarningUM(
iconState = iconState,
onWarningAcknowledged = {},
network = "Ethereum",
),
)
}

View file

@ -1,231 +0,0 @@
package com.tangem.features.tokenreceive.ui
import android.content.res.Configuration
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.graphics.painter.BitmapPainter
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.text.style.TextAlign
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.core.res.getStringSafe
import com.tangem.core.ui.components.*
import com.tangem.core.ui.extensions.*
import com.tangem.core.ui.message.SnackbarMessage
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.core.ui.res.LocalTopSnackbarHostState
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.test.TokenReceiveQrCodeBottomSheetTestTags
import com.tangem.features.tokenreceive.impl.R
import com.tangem.features.tokenreceive.ui.state.QrCodeUM
import kotlinx.coroutines.launch
@Composable
internal fun TokenReceiveQrCodeContentLegacy(qrCodeUM: QrCodeUM) {
val snackbarHostState = remember(::SnackbarHostState)
val qrCodePainter = rememberQrPainter(content = qrCodeUM.addressValue)
ContainerWithSnackbarHost(snackbarHostState = snackbarHostState) {
Column(
modifier = Modifier
.fillMaxWidth()
.background(color = TangemTheme.colors.background.tertiary)
.padding(bottom = 16.dp)
.padding(horizontal = 16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
SpacerH8()
QrCodePage(
addressFullName = qrCodeUM.addressName,
addressValue = qrCodeUM.addressValue,
network = qrCodeUM.network,
qrCodePainter = qrCodePainter,
)
SpacerH24()
Buttons(
onShareClick = { qrCodeUM.onShareClick(qrCodeUM.addressValue) },
onCopyClick = qrCodeUM.onCopyClick,
snackbarHostState = snackbarHostState,
)
}
}
}
@Composable
private fun QrCodePage(addressFullName: TextReference, addressValue: String, network: String, qrCodePainter: Painter) {
Column(
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Column(
modifier = Modifier.padding(horizontal = TangemTheme.dimens.size36),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text(
text = stringResourceSafe(
R.string.receive_bottom_sheet_warning_message_compact,
addressFullName.resolveReference(),
network,
),
color = TangemTheme.colors.text.primary1,
textAlign = TextAlign.Center,
style = TangemTheme.typography.h3,
modifier = Modifier.testTag(TokenReceiveQrCodeBottomSheetTestTags.TITLE),
)
SpacerH(20.dp)
Box(
modifier = Modifier
.border(
width = 8.dp,
color = TangemTheme.colors.icon.constant,
shape = RoundedCornerShape(8.dp),
)
.padding(8.dp)
.testTag(TokenReceiveQrCodeBottomSheetTestTags.QR_CODE),
) {
Image(
painter = qrCodePainter,
contentDescription = null,
contentScale = ContentScale.Fit,
modifier = Modifier.sizeIn(minWidth = 148.dp),
)
}
SpacerH24()
}
Text(
text = stringResourceSafe(R.string.wc_common_address),
color = TangemTheme.colors.text.tertiary,
textAlign = TextAlign.Center,
style = TangemTheme.typography.subtitle2,
)
SpacerH2()
Text(
text = addressValue,
color = TangemTheme.colors.text.primary1,
textAlign = TextAlign.Center,
style = TangemTheme.typography.subtitle1,
modifier = Modifier.testTag(TokenReceiveQrCodeBottomSheetTestTags.ADDRESS),
)
}
}
@Composable
private fun Buttons(
snackbarHostState: SnackbarHostState,
onShareClick: () -> Unit,
onCopyClick: () -> Unit,
modifier: Modifier = Modifier,
) {
val hapticFeedback = LocalHapticFeedback.current
val coroutineScope = rememberCoroutineScope()
val context = LocalContext.current
val resources = context.resources
val isRedesignEnabled = LocalRedesignEnabled.current
val tangemTopSnackbarHostState = LocalTopSnackbarHostState.current
Row(
modifier = modifier,
horizontalArrangement = Arrangement.spacedBy(16.dp),
) {
SecondaryButtonIconStart(
modifier = Modifier.weight(1f),
text = stringResourceSafe(id = R.string.common_copy),
iconResId = R.drawable.ic_copy_24,
onClick = {
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
onCopyClick()
coroutineScope.launch {
if (isRedesignEnabled) {
tangemTopSnackbarHostState.showSnackbar(
SnackbarMessage(
startIconId = R.drawable.ic_check_24,
message = resourceReference(R.string.wallet_notification_address_copied),
),
)
} else {
snackbarHostState.showSnackbar(
message = resources.getStringSafe(R.string.wallet_notification_address_copied),
)
}
}
},
)
SecondaryButtonIconStart(
modifier = Modifier.weight(1f),
text = stringResourceSafe(id = R.string.common_share),
iconResId = R.drawable.ic_share_24,
onClick = {
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
onShareClick()
},
)
}
}
@Composable
private fun rememberQrPainter(content: String, size: Dp = 248.dp, padding: Dp = 0.dp): BitmapPainter {
val density = LocalDensity.current
return remember(content) {
BitmapPainter(
content.toQrCode(
sizePx = with(density) { size.roundToPx() },
paddingPx = with(density) { padding.roundToPx() },
).asImageBitmap(),
)
}
}
@Composable
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
private fun Preview_TokenReceiveQrCodeContent(
@PreviewParameter(TokenReceiveQrCodeContentPreviewProvider::class) qrCodeUM: QrCodeUM,
) {
TangemThemePreview {
TokenReceiveQrCodeContentLegacy(qrCodeUM = qrCodeUM)
}
}
internal class TokenReceiveQrCodeContentPreviewProvider : PreviewParameterProvider<QrCodeUM> {
private val config = QrCodeUM(
network = "Ethereum",
addressName = stringReference("Etherium"),
addressValue = "0xe5178c7d4d0e861ed2e9414e045b501226b0de8d",
onCopyClick = {},
onShareClick = {},
)
override val values: Sequence<QrCodeUM>
get() = sequenceOf(config)
}

View file

@ -1,139 +0,0 @@
package com.tangem.features.tokenreceive.ui
import android.content.res.Configuration
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.layout.size
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.text.style.TextAlign
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 com.tangem.core.ui.components.SecondaryButton
import com.tangem.core.ui.components.SpacerH
import com.tangem.core.ui.components.SpacerH12
import com.tangem.core.ui.components.SpacerH24
import com.tangem.core.ui.components.currency.icon.CurrencyIcon
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemColorPalette
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.test.TokenReceiveWarningBottomSheetTestTags
import com.tangem.features.tokenreceive.impl.R
import com.tangem.features.tokenreceive.ui.state.WarningUM
@Composable
internal fun TokenReceiveWarningContentLegacy(warningUM: WarningUM) {
val hapticFeedback = LocalHapticFeedback.current
Column(
modifier = Modifier
.fillMaxWidth()
.background(color = TangemTheme.colors.background.tertiary)
.padding(
start = 16.dp,
end = 16.dp,
bottom = 16.dp,
)
.testTag(TokenReceiveWarningBottomSheetTestTags.BOTTOM_SHEET),
horizontalAlignment = Alignment.CenterHorizontally,
) {
CurrencyIcon(
modifier = Modifier
.padding(8.dp)
.size(size = 64.dp),
state = warningUM.iconState,
shouldDisplayNetwork = true,
iconSize = 56.dp,
)
SpacerH24()
WarningBlock(networkName = warningUM.network)
SpacerH12()
Text(
textAlign = TextAlign.Center,
text = stringResourceSafe(R.string.domain_receive_assets_onboarding_description),
style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.secondary,
)
SpacerH(48.dp)
SecondaryButton(
modifier = Modifier.fillMaxWidth(),
text = stringResourceSafe(R.string.common_got_it),
onClick = {
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
warningUM.onWarningAcknowledged()
},
)
}
}
@Composable
private fun WarningBlock(networkName: String, modifier: Modifier = Modifier) {
Column(
modifier = modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text(
textAlign = TextAlign.Center,
text = stringResourceSafe(R.string.domain_receive_assets_onboarding_title),
style = TangemTheme.typography.h3,
color = TangemTheme.colors.text.primary1,
)
SpacerH(6.dp)
Text(
textAlign = TextAlign.Center,
text = stringResourceSafe(R.string.domain_receive_assets_onboarding_network_name, networkName),
style = TangemTheme.typography.h3,
color = TangemTheme.colors.text.primary1,
)
}
}
@Composable
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
private fun Preview_TokenReceiveWarningContent(
@PreviewParameter(TokenReceiveWarningContentProvider::class) warningUM: WarningUM,
) {
TangemThemePreview {
TokenReceiveWarningContentLegacy(warningUM = warningUM)
}
}
internal class TokenReceiveWarningContentProvider : PreviewParameterProvider<WarningUM> {
val iconState = CurrencyIconState.TokenIcon(
url = null,
topBadgeIconResId = null,
fallbackTint = TangemColorPalette.Black,
fallbackBackground = TangemColorPalette.Meadow,
isGrayscale = false,
shouldShowCustomBadge = false,
)
override val values: Sequence<WarningUM>
get() = sequenceOf(
WarningUM(
iconState = iconState,
onWarningAcknowledged = {},
network = "Etherium",
),
)
}

View file

@ -15,13 +15,11 @@ import com.tangem.core.decompose.context.childByContext
import com.tangem.core.decompose.model.getOrCreateModel import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.ui.components.NavigationBar3ButtonsScrim import com.tangem.core.ui.components.NavigationBar3ButtonsScrim
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.tokens.model.details.NavigationAction import com.tangem.domain.tokens.model.details.NavigationAction
import com.tangem.feature.tokendetails.presentation.tokendetails.model.TokenDetailsModel import com.tangem.feature.tokendetails.presentation.tokendetails.model.TokenDetailsModel
import com.tangem.feature.tokendetails.presentation.tokendetails.route.TokenDetailsBottomSheetConfig import com.tangem.feature.tokendetails.presentation.tokendetails.route.TokenDetailsBottomSheetConfig
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.TokenDetailsScreen import com.tangem.feature.tokendetails.presentation.tokendetails.ui.TokenDetailsScreen
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.TokenDetailsScreenLegacy
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.bottomsheet.ChooseAddressBottomSheetComponent import com.tangem.feature.tokendetails.presentation.tokendetails.ui.bottomsheet.ChooseAddressBottomSheetComponent
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.bottomsheet.CloreMigrationBottomSheetComponent import com.tangem.feature.tokendetails.presentation.tokendetails.ui.bottomsheet.CloreMigrationBottomSheetComponent
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.bottomsheet.DynamicAddressesBottomSheetComponent import com.tangem.feature.tokendetails.presentation.tokendetails.ui.bottomsheet.DynamicAddressesBottomSheetComponent
@ -154,31 +152,18 @@ internal class DefaultTokenDetailsComponent @AssistedInject constructor(
val txHistoryDetails by txHistoryDetailsSlot.subscribeAsState() val txHistoryDetails by txHistoryDetailsSlot.subscribeAsState()
NavigationBar3ButtonsScrim() NavigationBar3ButtonsScrim()
if (LocalRedesignEnabled.current) { val tokenDetailsUM by model.redesignUiState.collectAsStateWithLifecycle()
val tokenDetailsUM by model.redesignUiState.collectAsStateWithLifecycle()
TokenDetailsScreen( TokenDetailsScreen(
tokenDetailsUM = tokenDetailsUM, tokenDetailsUM = tokenDetailsUM,
tokenMarketBlockComponent = tokenMarketBlockComponent, tokenMarketBlockComponent = tokenMarketBlockComponent,
yieldSupplyComponent = yieldSupplyComponent, yieldSupplyComponent = yieldSupplyComponent,
txHistoryComponent = txHistoryComponent, txHistoryComponent = txHistoryComponent,
expressTransactionsComponent = expressTransactionsComponent, expressTransactionsComponent = expressTransactionsComponent,
ratingComponent = ratingSlotState.child?.instance, ratingComponent = ratingSlotState.child?.instance,
marketingBannerComponent = marketingBannerComponent, marketingBannerComponent = marketingBannerComponent,
modifier = modifier, modifier = modifier,
) )
} else {
val state by model.uiState.collectAsStateWithLifecycle()
TokenDetailsScreenLegacy(
state = state,
tokenMarketBlockComponent = tokenMarketBlockComponent,
txHistoryComponent = txHistoryComponent,
yieldSupplyComponent = yieldSupplyComponent,
expressTransactionsComponent = expressTransactionsComponent,
ratingComponent = ratingSlotState.child?.instance,
marketingBannerComponent = marketingBannerComponent,
)
}
bottomSheet.child?.instance?.BottomSheet() bottomSheet.child?.instance?.BottomSheet()
txHistoryDetails.child?.instance?.BottomSheet() txHistoryDetails.child?.instance?.BottomSheet()

View file

@ -33,7 +33,6 @@ import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.core.decompose.ui.UiMessageSender import com.tangem.core.decompose.ui.UiMessageSender
import com.tangem.core.navigation.url.UrlOpener import com.tangem.core.navigation.url.UrlOpener
import com.tangem.core.ui.DesignFeatureToggles
import com.tangem.core.ui.clipboard.ClipboardManager import com.tangem.core.ui.clipboard.ClipboardManager
import com.tangem.core.ui.ds.image.DeviceIconUM import com.tangem.core.ui.ds.image.DeviceIconUM
import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.TextReference
@ -184,7 +183,6 @@ internal class TokenDetailsModel @Inject constructor(
private val getWalletIconUseCase: GetWalletIconUseCase, private val getWalletIconUseCase: GetWalletIconUseCase,
private val walletIconUMConverter: WalletIconUMConverter, private val walletIconUMConverter: WalletIconUMConverter,
private val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase, private val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase,
private val designFeatureToggles: DesignFeatureToggles,
private val redesignStateController: TokenDetailsStateController, private val redesignStateController: TokenDetailsStateController,
private val swapFeedbackUseCase: SwapFeedbackUseCase, private val swapFeedbackUseCase: SwapFeedbackUseCase,
private val quickTopUpBlockFactory: QuickTopUpBlockFactory, private val quickTopUpBlockFactory: QuickTopUpBlockFactory,
@ -346,10 +344,8 @@ internal class TokenDetailsModel @Inject constructor(
uiState.value = stateFactory.getStateWithUpdatedHidden( uiState.value = stateFactory.getStateWithUpdatedHidden(
isBalanceHidden = settings.isBalanceHidden, isBalanceHidden = settings.isBalanceHidden,
) )
if (designFeatureToggles.isRedesignEnabled) { redesignStateController.update { state ->
redesignStateController.update { state -> state.copy(isBalanceHidden = settings.isBalanceHidden)
state.copy(isBalanceHidden = settings.isBalanceHidden)
}
} }
} }
.launchIn(modelScope) .launchIn(modelScope)
@ -366,38 +362,36 @@ internal class TokenDetailsModel @Inject constructor(
latestTokenActions = state.states latestTokenActions = state.states
sendButtonsEvents(state.states) sendButtonsEvents(state.states)
uiState.value = stateFactory.getManageButtonsState(actions = state.states) uiState.value = stateFactory.getManageButtonsState(actions = state.states)
if (designFeatureToggles.isRedesignEnabled) { val networkSource = currencyStatus.value.sources.networkSource
val networkSource = currencyStatus.value.sources.networkSource redesignStateController.update(
redesignStateController.update( UpdateActionButtonsTransformer(
UpdateActionButtonsTransformer( actions = state.states,
actions = state.states, clickIntents = this@TokenDetailsModel,
clickIntents = this@TokenDetailsModel, ),
), )
) redesignStateController.update(
redesignStateController.update( UpdateAddFundsTransformer(
UpdateAddFundsTransformer( actions = state.states,
actions = state.states, networkSource = networkSource,
networkSource = networkSource, clickIntents = this@TokenDetailsModel,
clickIntents = this@TokenDetailsModel, onActionDispatched = bottomSheetNavigation::dismiss,
onActionDispatched = bottomSheetNavigation::dismiss, ),
), )
) redesignStateController.update(
redesignStateController.update( UpdateTransferTransformer(
UpdateTransferTransformer( actions = state.states,
actions = state.states, networkSource = networkSource,
networkSource = networkSource, clickIntents = this@TokenDetailsModel,
clickIntents = this@TokenDetailsModel, analyticsEventHandler = analyticsEventsHandler,
analyticsEventHandler = analyticsEventsHandler, onActionDispatched = bottomSheetNavigation::dismiss,
onActionDispatched = bottomSheetNavigation::dismiss, ),
), )
) redesignStateController.update(
redesignStateController.update( UpdateZeroBalanceActionsTransformer(
UpdateZeroBalanceActionsTransformer( actions = state.states,
actions = state.states, clickIntents = this@TokenDetailsModel,
clickIntents = this@TokenDetailsModel, ),
), )
)
}
} }
.flowOn(dispatchers.main) .flowOn(dispatchers.main)
.launchIn(modelScope) .launchIn(modelScope)
@ -1502,7 +1496,6 @@ internal class TokenDetailsModel @Inject constructor(
} }
private fun initRedesign() { private fun initRedesign() {
if (!designFeatureToggles.isRedesignEnabled) return
initRedesignState() initRedesignState()
observeRedesignBalance() observeRedesignBalance()
updateRedesignTopBarMenu() updateRedesignTopBarMenu()

View file

@ -1,261 +0,0 @@
package com.tangem.feature.tokendetails.presentation.tokendetails.ui
import android.content.res.Configuration
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.*
import androidx.compose.material3.Scaffold
import androidx.compose.material3.ScaffoldDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateUM
import com.tangem.common.ui.expressStatus.state.ExpressTransactionsBlockState
import com.tangem.features.rating.RatingComponent
import com.tangem.core.ui.components.containers.pullToRefresh.TangemPullToRefreshContainer
import com.tangem.core.ui.components.marketprice.MarketPriceBlock
import com.tangem.core.ui.components.marketprice.MarketPriceBlockState
import com.tangem.core.ui.components.notifications.Notification
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemeRedesign
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.test.TokenDetailsScreenTestTags
import com.tangem.feature.tokendetails.presentation.tokendetails.TokenDetailsPreviewData
import com.tangem.feature.tokendetails.presentation.tokendetails.state.StakingBlockUM
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsState
import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.TokenDetailsNotification
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.TokenDetailsBalanceBlockLegacy
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.TokenDetailsTopAppBar
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.TokenInfoBlock
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.staking.TokenStakingBlockLegacy
import com.tangem.features.markets.token.block.TokenMarketBlockComponent
import com.tangem.features.marketing.api.MarketingBannerComponent
import com.tangem.features.tokendetails.ExpressTransactionsComponent
import com.tangem.features.txhistory.component.TxHistoryComponent
import com.tangem.features.txhistory.entity.TxHistoryItemsUM
import com.tangem.features.txhistory.entity.TxHistoryUM
import com.tangem.features.yield.supply.api.YieldSupplyComponent
import kotlinx.collections.immutable.PersistentList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
// TODO: Split to blocks [REDACTED_JIRA]
@Suppress("LongMethod", "CyclomaticComplexMethod", "LongParameterList")
@Composable
internal fun TokenDetailsScreenLegacy(
state: TokenDetailsState,
tokenMarketBlockComponent: TokenMarketBlockComponent?,
txHistoryComponent: TxHistoryComponent,
yieldSupplyComponent: YieldSupplyComponent,
expressTransactionsComponent: ExpressTransactionsComponent,
ratingComponent: RatingComponent?,
marketingBannerComponent: MarketingBannerComponent,
) {
val bottomBarHeight = with(LocalDensity.current) { WindowInsets.systemBars.getBottom(this).toDp() }
Scaffold(
topBar = { TokenDetailsTopAppBar(config = state.topAppBarConfig) },
contentWindowInsets = ScaffoldDefaults.contentWindowInsets.exclude(WindowInsets.navigationBars),
containerColor = TangemTheme.colors.background.secondary,
) { scaffoldPaddings ->
val listState = rememberLazyListState()
val txHistoryComponentState by txHistoryComponent.legacyTxHistoryState.collectAsStateWithLifecycle()
val expressState by expressTransactionsComponent.state.collectAsStateWithLifecycle()
val betweenItemsPadding = TangemTheme.dimens.spacing12
val horizontalPadding = TangemTheme.dimens.spacing16
val itemModifier = Modifier
.padding(top = betweenItemsPadding)
.padding(horizontal = horizontalPadding)
TangemPullToRefreshContainer(
config = state.pullToRefreshConfig,
modifier = Modifier.padding(scaffoldPaddings),
) {
LazyColumn(
modifier = Modifier
.fillMaxSize()
.testTag(TokenDetailsScreenTestTags.SCREEN_CONTAINER),
state = listState,
contentPadding = PaddingValues(
bottom = TangemTheme.dimens.spacing16 + bottomBarHeight,
),
) {
item {
TokenInfoBlock(
modifier = Modifier.padding(horizontal = horizontalPadding),
state = state.tokenInfoBlockState,
)
}
item {
TokenDetailsBalanceBlockLegacy(
modifier = itemModifier,
isBalanceHidden = state.isBalanceHidden,
state = state.tokenBalanceBlockState,
)
}
items(
items = state.notifications,
key = { it::class.java },
contentType = { it.config::class.java },
itemContent = { notification ->
Notification(
modifier = itemModifier.animateItem(),
config = notification.config,
iconTint = when (notification) {
is TokenDetailsNotification.Informational -> TangemTheme.colors.icon.accent
is TokenDetailsNotification.UsedOutdatedData -> TangemTheme.colors.text.attention
else -> null
},
)
},
)
when {
tokenMarketBlockComponent != null -> {
item(
key = TokenMarketBlockComponent::class.java,
contentType = TokenMarketBlockComponent::class.java,
content = { tokenMarketBlockComponent.Content(modifier = itemModifier) },
)
}
state.isMarketPriceAvailable -> {
item(
key = MarketPriceBlockState::class.java,
contentType = MarketPriceBlockState::class.java,
content = {
MarketPriceBlock(
modifier = itemModifier,
state = state.marketPriceBlockState,
)
},
)
}
}
if (state.stakingBlocksState != null) {
item(
key = StakingBlockUM::class.java,
contentType = StakingBlockUM::class.java,
content = {
TokenStakingBlockLegacy(
state = state.stakingBlocksState,
isBalanceHidden = state.isBalanceHidden,
modifier = itemModifier,
)
},
)
}
item {
yieldSupplyComponent.Content(modifier = itemModifier)
}
item(key = "marketing_banner_block") {
TangemThemeRedesign {
marketingBannerComponent.Content(modifier = itemModifier)
}
}
with(expressTransactionsComponent) {
expressTransactionsContentLegacy(
state = expressState.transactionsToDisplay,
modifier = itemModifier,
)
}
state.quickTopUpBlock?.let { quickTopUpBlock ->
item(key = "quick_top_up_block") {
TangemThemeRedesign {
QuickTopUpBlock(
state = quickTopUpBlock,
modifier = itemModifier,
)
}
}
}
with(txHistoryComponent) {
txHistoryContentLegacy(listState = listState, state = txHistoryComponentState)
}
}
}
expressState.bottomSheetSlot?.content(
ratingComponent?.let { comp -> { comp.Content(modifier = Modifier.fillMaxWidth()) } },
)
}
}
// region Preview
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun TokenDetailsScreenPreview(
@PreviewParameter(TokenDetailsScreenParameterProvider::class) state: TokenDetailsState,
) {
TangemThemePreview {
TokenDetailsScreenLegacy(
state = state,
tokenMarketBlockComponent = null,
txHistoryComponent = object : TxHistoryComponent {
override val legacyTxHistoryState: StateFlow<TxHistoryUM> = MutableStateFlow(
value = TxHistoryUM.Empty(isBalanceHidden = false, onExploreClick = {}),
)
override val txHistoryState: StateFlow<TxHistoryItemsUM> = MutableStateFlow(
value = TxHistoryItemsUM.Empty(isBalanceHidden = false, onExploreClick = {}),
)
override fun LazyListScope.txHistoryContentLegacy(listState: LazyListState, state: TxHistoryUM) = Unit
override fun LazyListScope.txHistoryContent(listState: LazyListState, state: TxHistoryItemsUM) = Unit
},
yieldSupplyComponent = object : YieldSupplyComponent {
@Composable
override fun Content(modifier: Modifier) {
}
},
expressTransactionsComponent = PreviewExpressTransactionsComponent,
ratingComponent = null,
marketingBannerComponent = object : MarketingBannerComponent {
@Composable
override fun Content(modifier: Modifier) = Unit
},
)
}
}
private val PreviewExpressTransactionsComponent = object : ExpressTransactionsComponent {
override val state: StateFlow<ExpressTransactionsBlockState> = MutableStateFlow(
ExpressTransactionsBlockState(
transactions = persistentListOf(),
transactionsToDisplay = persistentListOf(),
bottomSheetSlot = null,
),
)
override fun LazyListScope.expressTransactionsContentLegacy(
state: PersistentList<ExpressTransactionStateUM>,
modifier: Modifier,
) = Unit
override fun LazyListScope.expressTransactionsContent(
state: PersistentList<ExpressTransactionStateUM>,
modifier: Modifier,
) = Unit
}
private class TokenDetailsScreenParameterProvider : CollectionPreviewParameterProvider<TokenDetailsState>(
collection = listOf(
TokenDetailsPreviewData.tokenDetailsState_1,
TokenDetailsPreviewData.tokenDetailsState_2,
TokenDetailsPreviewData.tokenDetailsState_3,
),
)
// endregion Preview

View file

@ -1,281 +0,0 @@
package com.tangem.feature.tokendetails.presentation.tokendetails.ui.components
import android.content.res.Configuration
import androidx.compose.animation.Crossfade
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
import androidx.compose.material3.Surface
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.material3.ripple
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
import androidx.constraintlayout.compose.ConstraintLayout
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.RectangleShimmer
import com.tangem.core.ui.components.buttons.HorizontalActionChips
import com.tangem.core.ui.components.buttons.segmentedbutton.SegmentedButtons
import com.tangem.core.ui.components.text.TextAnimatedCounter
import com.tangem.core.ui.components.text.applyBladeBrush
import com.tangem.core.ui.extensions.orMaskWithStars
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.feature.tokendetails.presentation.tokendetails.TokenDetailsPreviewData
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsBalanceBlockState
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsYieldSupplyState
import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.TokenDetailsActionButton
import com.tangem.features.tokendetails.impl.R
import com.tangem.utils.StringsSigns.DASH_SIGN
import kotlinx.collections.immutable.toImmutableList
@Suppress("DestructuringDeclarationWithTooManyEntries")
@Composable
internal fun TokenDetailsBalanceBlockLegacy(
state: TokenDetailsBalanceBlockState,
isBalanceHidden: Boolean,
modifier: Modifier = Modifier,
) {
Surface(
modifier = modifier.fillMaxWidth(),
shape = TangemTheme.shapes.roundedCornersXMedium,
color = TangemTheme.colors.background.primary,
) {
val spacing4 = TangemTheme.dimens.spacing4
val spacing10 = TangemTheme.dimens.spacing10
val spacing12 = TangemTheme.dimens.spacing12
ConstraintLayout(
modifier = Modifier.fillMaxWidth(),
) {
val (balanceTitle, toggleButtons, fiatBalance, cryptoBalance, actionChips) = createRefs()
Text(
text = stringResourceSafe(id = R.string.common_balance_title),
color = TangemTheme.colors.text.tertiary,
style = TangemTheme.typography.subtitle2,
modifier = Modifier.constrainAs(balanceTitle) {
top.linkTo(anchor = parent.top, margin = spacing12)
start.linkTo(anchor = parent.start, margin = spacing12)
},
)
BalanceButtons(
state = state,
modifier = Modifier.constrainAs(toggleButtons) {
top.linkTo(anchor = parent.top)
end.linkTo(anchor = parent.end, margin = spacing10)
},
)
FiatBalance(
state = state,
isBalanceHidden = isBalanceHidden,
modifier = Modifier.constrainAs(fiatBalance) {
top.linkTo(anchor = balanceTitle.bottom, margin = spacing4)
start.linkTo(anchor = parent.start, margin = spacing12)
},
)
CryptoBalance(
state = state,
isBalanceHidden = isBalanceHidden,
modifier = Modifier.constrainAs(cryptoBalance) {
top.linkTo(anchor = fiatBalance.bottom, margin = spacing4)
start.linkTo(anchor = parent.start, margin = spacing12)
},
)
HorizontalActionChips(
buttons = state.actionButtons.map(TokenDetailsActionButton::config).toImmutableList(),
modifier = Modifier.constrainAs(actionChips) {
top.linkTo(anchor = cryptoBalance.bottom, margin = spacing12)
start.linkTo(anchor = parent.start)
end.linkTo(anchor = parent.end)
bottom.linkTo(anchor = parent.bottom, margin = spacing12)
},
containerColor = TangemTheme.colors.background.primary,
contentPadding = PaddingValues(horizontal = TangemTheme.dimens.spacing12),
)
}
}
}
@Composable
private fun FiatBalance(
state: TokenDetailsBalanceBlockState,
isBalanceHidden: Boolean,
modifier: Modifier = Modifier,
) {
when (state) {
is TokenDetailsBalanceBlockState.Loading -> RectangleShimmer(
modifier = modifier.size(
width = TangemTheme.dimens.size102,
height = TangemTheme.dimens.size32,
),
)
is TokenDetailsBalanceBlockState.Content -> if (state.displayYieldSupplyFiatBalance != null &&
!isBalanceHidden
) {
TextAnimatedCounter(
modifier = modifier,
text = state.displayYieldSupplyFiatBalance,
style = TangemTheme.typography.h2.applyBladeBrush(
isEnabled = state.isBalanceFlickering,
textColor = TangemTheme.colors.text.primary1,
),
)
} else {
Text(
modifier = modifier,
text = (state.displayYieldSupplyFiatBalance ?: state.displayFiatBalance).orMaskWithStars(
isBalanceHidden,
),
style = TangemTheme.typography.h2.applyBladeBrush(
isEnabled = state.isBalanceFlickering,
textColor = TangemTheme.colors.text.primary1,
),
)
}
is TokenDetailsBalanceBlockState.Error -> Text(
modifier = modifier,
text = DASH_SIGN.orMaskWithStars(isBalanceHidden),
style = TangemTheme.typography.h2,
color = TangemTheme.colors.text.primary1,
)
}
}
@Composable
private fun CryptoBalance(
state: TokenDetailsBalanceBlockState,
isBalanceHidden: Boolean,
modifier: Modifier = Modifier,
) {
when (state) {
is TokenDetailsBalanceBlockState.Loading -> RectangleShimmer(
modifier = modifier.size(
width = TangemTheme.dimens.size70,
height = TangemTheme.dimens.size16,
),
)
is TokenDetailsBalanceBlockState.Content -> {
Crossfade(
modifier = modifier,
targetState = state.yieldSupplyState,
) { yieldSupplyState ->
when (yieldSupplyState) {
is TokenDetailsYieldSupplyState.Active -> {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(4.dp),
) {
Icon(
modifier = Modifier.size(TangemTheme.dimens.size16),
painter = painterResource(id = R.drawable.ic_exchange_horizontal_24),
tint = TangemTheme.colors.icon.inactive,
contentDescription = null,
)
TextAnimatedCounter(
text = (state.displayYieldSupplyCryptoBalance ?: state.displayCryptoBalance)
.orMaskWithStars(isBalanceHidden),
style = TangemTheme.typography.caption2.applyBladeBrush(
isEnabled = state.isBalanceFlickering,
textColor = TangemTheme.colors.text.tertiary,
),
)
Icon(
modifier = Modifier
.size(TangemTheme.dimens.size16)
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = ripple(bounded = false),
onClick = { yieldSupplyState.yieldInfoClick() },
),
painter = painterResource(id = R.drawable.ic_information_24),
tint = TangemTheme.colors.icon.inactive,
contentDescription = null,
)
}
}
is TokenDetailsYieldSupplyState.Empty -> Text(
text = state.displayCryptoBalance.orMaskWithStars(isBalanceHidden),
style = TangemTheme.typography.caption2.applyBladeBrush(
isEnabled = state.isBalanceFlickering,
textColor = TangemTheme.colors.text.tertiary,
),
)
}
}
}
is TokenDetailsBalanceBlockState.Error -> Text(
modifier = modifier,
text = DASH_SIGN.orMaskWithStars(isBalanceHidden),
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.tertiary,
)
}
}
@Composable
private fun BalanceButtons(state: TokenDetailsBalanceBlockState, modifier: Modifier = Modifier) {
if (state !is TokenDetailsBalanceBlockState.Content || !state.isBalanceSelectorEnabled) return
SegmentedButtons(
config = state.balanceSegmentedButtonConfig,
onClick = state.onBalanceSelect,
showIndication = false,
modifier = modifier
.padding(top = TangemTheme.dimens.spacing11)
.width(IntrinsicSize.Min),
) { config ->
val style = if (state.selectedBalanceType == config.type) {
TangemTheme.typography.caption1
} else {
TangemTheme.typography.caption2
}
Text(
text = config.title.resolveReference(),
color = TangemTheme.colors.text.primary1,
style = style,
maxLines = 1,
modifier = Modifier
.padding(
horizontal = TangemTheme.dimens.spacing6,
vertical = TangemTheme.dimens.spacing4,
)
.align(Alignment.Center),
)
}
}
@Preview(widthDp = 328, heightDp = 152)
@Preview(widthDp = 328, heightDp = 152, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun Preview_TokenDetailsBalanceBlock(
@PreviewParameter(TokenDetailsBalanceBlockStateProvider::class) state: TokenDetailsBalanceBlockState,
) {
TangemThemePreview {
TokenDetailsBalanceBlockLegacy(state = state, isBalanceHidden = false)
}
}
private class TokenDetailsBalanceBlockStateProvider : CollectionPreviewParameterProvider<TokenDetailsBalanceBlockState>(
collection = listOf(
TokenDetailsPreviewData.balanceLoading,
TokenDetailsPreviewData.balanceContent,
TokenDetailsPreviewData.balanceContent.copy(isBalanceFlickering = true),
TokenDetailsPreviewData.balanceContent.copy(
yieldSupplyState = TokenDetailsYieldSupplyState.Active({}),
),
TokenDetailsPreviewData.balanceError,
),
)

View file

@ -1,198 +0,0 @@
package com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.staking
import android.content.res.Configuration
import androidx.compose.animation.AnimatedContent
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
import androidx.compose.material3.Text
import androidx.compose.material3.ripple
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
import com.tangem.core.ui.components.RectangleShimmer
import com.tangem.core.ui.components.SecondaryButton
import com.tangem.core.ui.components.SpacerW8
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.test.TokenDetailsScreenTestTags
import com.tangem.core.ui.utils.getGreyScaleColorFilter
import com.tangem.feature.tokendetails.presentation.tokendetails.TokenDetailsPreviewData.stakingAvailableBlock
import com.tangem.feature.tokendetails.presentation.tokendetails.TokenDetailsPreviewData.stakingBalanceBlock
import com.tangem.feature.tokendetails.presentation.tokendetails.TokenDetailsPreviewData.stakingLoadingBlock
import com.tangem.feature.tokendetails.presentation.tokendetails.TokenDetailsPreviewData.stakingTemporaryUnavailableBlock
import com.tangem.feature.tokendetails.presentation.tokendetails.state.StakingBlockUM
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.CurrencyIcon
import com.tangem.features.tokendetails.impl.R
import com.tangem.utils.StringsSigns
/**
* Token staking block
*
* @param state component state
* @param isBalanceHidden whether to hide balance
* @param modifier modifier
*/
@Composable
internal fun TokenStakingBlockLegacy(state: StakingBlockUM, isBalanceHidden: Boolean, modifier: Modifier = Modifier) {
Column(
modifier = modifier
.clip(TangemTheme.shapes.roundedCornersXMedium)
.background(TangemTheme.colors.background.primary)
.clickable(
enabled = state is StakingBlockUM.Staked,
interactionSource = remember { MutableInteractionSource() },
indication = ripple(),
onClick = { (state as? StakingBlockUM.Staked)?.onStakeClicked?.invoke() },
)
.fillMaxWidth()
.padding(all = TangemTheme.dimens.spacing12),
) {
AnimatedContent(
targetState = state,
contentAlignment = Alignment.CenterStart,
label = "Staking block animation",
) { stakingBlock ->
when (stakingBlock) {
is StakingBlockUM.TemporaryUnavailable -> StakingTemporaryUnavailableBlock()
is StakingBlockUM.Loading -> StakingLoading()
is StakingBlockUM.Staked -> StakingBalanceBlock(
state = stakingBlock,
isBalanceHidden = isBalanceHidden,
)
is StakingBlockUM.StakeAvailable -> StakingAvailableContent(
state = stakingBlock,
)
}
}
}
}
@Composable
private fun StakingAvailableContent(state: StakingBlockUM.StakeAvailable, modifier: Modifier = Modifier) {
Column(
modifier = modifier
.fillMaxWidth()
.testTag(TokenDetailsScreenTestTags.STAKING_AVAILABLE_BLOCK),
) {
Row {
val (alpha, colorFilter) = remember(state.iconState.isGrayscale) {
getGreyScaleColorFilter(state.iconState.isGrayscale)
}
CurrencyIcon(
modifier = Modifier
.size(TangemTheme.dimens.size20)
.clip(TangemTheme.shapes.roundedCorners8)
.align(Alignment.CenterVertically)
.testTag(TokenDetailsScreenTestTags.STAKING_CURRENCY_ICON),
icon = state.iconState,
alpha = alpha,
colorFilter = colorFilter,
)
SpacerW8()
Column {
Row(horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing4)) {
Text(
text = state.titleText.resolveReference(),
color = TangemTheme.colors.text.primary1,
style = TangemTheme.typography.subtitle2,
modifier = Modifier.testTag(TokenDetailsScreenTestTags.STAKING_SERVICE_TITLE),
)
if (state.isBetaMode) {
Text(
text = StringsSigns.DOT,
color = TangemTheme.colors.text.primary1,
style = TangemTheme.typography.subtitle2,
)
Text(
text = stringResourceSafe(R.string.beta_mode_warning_title),
color = TangemTheme.colors.text.primary1,
style = TangemTheme.typography.subtitle2,
)
}
}
Spacer(modifier = Modifier.size(TangemTheme.dimens.size4))
Text(
text = state.subtitleText.resolveReference(),
color = TangemTheme.colors.text.tertiary,
style = TangemTheme.typography.body2,
modifier = Modifier.testTag(TokenDetailsScreenTestTags.STAKING_SERVICE_TEXT),
)
Spacer(modifier = Modifier.size(TangemTheme.dimens.size8))
}
}
SecondaryButton(
modifier = Modifier.fillMaxWidth(),
text = stringResourceSafe(id = R.string.common_stake),
enabled = state.isEnabled,
onClick = state.onStakeClicked,
)
}
}
@Composable
private fun StakingLoading(modifier: Modifier = Modifier) {
Column(
modifier = modifier.fillMaxWidth(),
) {
Row {
Column {
RectangleShimmer(
modifier = Modifier
.fillMaxWidth()
.height(TangemTheme.dimens.size20),
)
Spacer(modifier = Modifier.size(TangemTheme.dimens.size8))
RectangleShimmer(
modifier = Modifier
.fillMaxWidth()
.height(TangemTheme.dimens.size36),
)
Spacer(modifier = Modifier.size(TangemTheme.dimens.size8))
}
}
RectangleShimmer(
modifier = Modifier
.fillMaxWidth()
.height(TangemTheme.dimens.size48),
)
}
}
// region Preview
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun Preview_TokenStakingBlock(
@PreviewParameter(StakingBlockStateProvider::class)
state: StakingBlockUM,
) {
TangemThemePreview {
TokenStakingBlockLegacy(
state = state,
isBalanceHidden = false,
)
}
}
private class StakingBlockStateProvider : CollectionPreviewParameterProvider<StakingBlockUM>(
collection = listOf(
stakingLoadingBlock,
stakingAvailableBlock,
stakingTemporaryUnavailableBlock,
stakingBalanceBlock,
),
)
// endregion Preview

View file

@ -9,11 +9,9 @@ import com.tangem.common.ui.earn.EarnBlock
import com.tangem.common.ui.earn.EarnBlockUM import com.tangem.common.ui.earn.EarnBlockUM
import com.tangem.core.decompose.context.AppComponentContext import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.model.getOrCreateModel import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.core.ui.test.TokenDetailsScreenTestTags import com.tangem.core.ui.test.TokenDetailsScreenTestTags
import com.tangem.features.yield.supply.api.YieldSupplyComponent import com.tangem.features.yield.supply.api.YieldSupplyComponent
import com.tangem.features.yield.supply.impl.main.model.YieldSupplyModel import com.tangem.features.yield.supply.impl.main.model.YieldSupplyModel
import com.tangem.features.yield.supply.impl.main.ui.YieldSupplyBlockContentLegacy
import dagger.assisted.Assisted import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
@ -27,21 +25,16 @@ internal class DefaultYieldSupplyComponent @AssistedInject constructor(
@Composable @Composable
override fun Content(modifier: Modifier) { override fun Content(modifier: Modifier) {
if (LocalRedesignEnabled.current) { val earnBlockUM by model.uiState.collectAsStateWithLifecycle()
val earnBlockUM by model.uiState.collectAsStateWithLifecycle() earnBlockUM?.let { blockUM ->
earnBlockUM?.let { blockUM -> val tag = if (blockUM is EarnBlockUM.Content &&
val tag = if (blockUM is EarnBlockUM.Content && blockUM.backgroundUM is EarnBlockUM.BackgroundUM.AccentSoft
blockUM.backgroundUM is EarnBlockUM.BackgroundUM.AccentSoft ) {
) { TokenDetailsScreenTestTags.YIELD_SUPPLY_AVAILABLE_BLOCK
TokenDetailsScreenTestTags.YIELD_SUPPLY_AVAILABLE_BLOCK } else {
} else { TokenDetailsScreenTestTags.YIELD_SUPPLY_BLOCK
TokenDetailsScreenTestTags.YIELD_SUPPLY_BLOCK
}
EarnBlock(state = blockUM, modifier = modifier.testTag(tag))
} }
} else { EarnBlock(state = blockUM, modifier = modifier.testTag(tag))
val yieldSupplyUM by model.uiStateLegacy.collectAsStateWithLifecycle()
YieldSupplyBlockContentLegacy(yieldSupplyUM = yieldSupplyUM, modifier = modifier)
} }
} }

View file

@ -1,453 +0,0 @@
package com.tangem.features.yield.supply.impl.main.ui
import android.content.res.Configuration
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.vectorResource
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 com.tangem.core.ui.components.PrimaryButton
import com.tangem.core.ui.components.SecondaryButton
import com.tangem.core.ui.components.SpacerW12
import com.tangem.core.ui.components.SpacerW8
import com.tangem.core.ui.components.TextShimmer
import com.tangem.core.ui.components.buttons.common.TangemButtonSize
import com.tangem.core.ui.extensions.*
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.features.yield.supply.impl.R
import com.tangem.features.yield.supply.impl.main.entity.YieldSupplyUM
import com.tangem.utils.StringsSigns
@Composable
internal fun YieldSupplyBlockContentLegacy(yieldSupplyUM: YieldSupplyUM, modifier: Modifier = Modifier) {
AnimatedContent(
targetState = yieldSupplyUM,
contentKey = { it::class },
) { supplyUM ->
when (supplyUM) {
is YieldSupplyUM.Available -> SupplyAvailable(supplyUM, modifier)
YieldSupplyUM.Loading -> SupplyLoading(modifier)
is YieldSupplyUM.Content -> SupplyContent(supplyUM, modifier)
YieldSupplyUM.Processing.Enter -> SupplyProcessing(
resourceReference(R.string.yield_module_token_details_earn_notification_processing),
modifier,
)
YieldSupplyUM.Processing.Exit -> SupplyProcessing(
resourceReference(R.string.yield_module_stop_earning),
modifier,
)
YieldSupplyUM.Unavailable,
YieldSupplyUM.Initial,
-> Unit
}
}
}
@Composable
private fun SupplyAvailable(supplyUM: YieldSupplyUM.Available, modifier: Modifier = Modifier) {
if (supplyUM.isBoostAvailable) {
SupplyAvailableBoosted(supplyUM = supplyUM, modifier = modifier)
} else {
SupplyInfo(
title = resourceReference(
R.string.yield_module_token_details_earn_notification_earning_on_your_balance_title,
),
subtitle = resourceReference(R.string.yield_module_token_details_earn_notification_description),
rewardsApy = supplyUM.apyText,
iconTint = TangemTheme.colors.icon.accent,
modifier = modifier,
button = {
SecondaryButton(
text = stringResourceSafe(R.string.common_learn_more),
onClick = supplyUM.onClick,
size = TangemButtonSize.WideAction,
modifier = Modifier.fillMaxWidth(),
)
},
)
}
}
@Composable
private fun SupplyAvailableBoosted(supplyUM: YieldSupplyUM.Available, modifier: Modifier = Modifier) {
Column(
verticalArrangement = Arrangement.spacedBy(12.dp),
modifier = modifier
.clip(RoundedCornerShape(16.dp))
.background(TangemTheme.colors.background.primary)
.padding(12.dp),
) {
Row(
horizontalArrangement = Arrangement.spacedBy(10.dp),
verticalAlignment = Alignment.Top,
modifier = Modifier.fillMaxWidth(),
) {
Icon(
imageVector = ImageVector.vectorResource(R.drawable.ic_analytics_up_24),
contentDescription = null,
tint = TangemTheme.colors.icon.accent,
modifier = Modifier
.background(TangemTheme.colors.icon.accent.copy(alpha = 0.1f), CircleShape)
.padding(6.dp)
.size(24.dp),
)
Column(
verticalArrangement = Arrangement.spacedBy(2.dp),
modifier = Modifier.weight(1f),
) {
Text(
text = supplyUM.title.resolveReference(),
style = TangemTheme.typography.subtitle1,
color = TangemTheme.colors.text.primary1,
)
Text(
text = supplyUM.apyText.resolveAnnotatedReference(),
style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.accent,
)
Text(
text = stringResourceSafe(R.string.yield_apy_boost_banner_subtitle),
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.tertiary,
)
}
}
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp),
modifier = Modifier.fillMaxWidth(),
) {
SecondaryButton(
text = stringResourceSafe(R.string.common_learn_more),
onClick = supplyUM.onLearnMoreClick,
size = TangemButtonSize.WideAction,
modifier = Modifier.weight(1f),
)
PrimaryButton(
text = stringResourceSafe(R.string.common_activate),
onClick = supplyUM.onClick,
size = TangemButtonSize.WideAction,
modifier = Modifier.weight(1f),
)
}
}
}
@Suppress("LongMethod")
@Composable
private fun SupplyContent(supplyUM: YieldSupplyUM.Content, modifier: Modifier = Modifier) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = modifier
.clip(RoundedCornerShape(16.dp))
.background(TangemTheme.colors.background.primary)
.clickable(onClick = supplyUM.onClick)
.padding(12.dp),
) {
Image(
painter = painterResource(R.drawable.img_aave_22),
modifier = Modifier.size(36.dp),
contentDescription = null,
)
SpacerW12()
Column(
verticalArrangement = Arrangement.spacedBy(4.dp),
modifier = Modifier.weight(1f),
) {
Row(
horizontalArrangement = Arrangement.spacedBy(4.dp),
) {
Text(
modifier = Modifier.weight(1.0f, fill = false),
text = supplyUM.title.resolveReference(),
style = TangemTheme.typography.subtitle2,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
color = TangemTheme.colors.text.primary1,
)
AnimatedVisibility(supplyUM.rewardsApy != TextReference.EMPTY) {
Row(
horizontalArrangement = Arrangement.spacedBy(4.dp),
) {
Text(
text = StringsSigns.DOT,
style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.tertiary,
)
Text(
text = supplyUM.rewardsApy.resolveReference(),
style = TangemTheme.typography.subtitle2,
maxLines = 1,
color = TangemTheme.colors.text.accent,
)
}
}
}
Text(
text = supplyUM.subtitle.resolveReference(),
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.primary1,
)
}
SpacerW8()
AnimatedContent(
targetState = supplyUM,
) { currentState ->
when {
currentState.shouldShowWarningIcon -> Icon(
imageVector = ImageVector.vectorResource(R.drawable.ic_alert_triangle_20),
contentDescription = null,
tint = TangemTheme.colors.icon.attention,
)
currentState.shouldShowInfoIcon -> Icon(
imageVector = ImageVector.vectorResource(R.drawable.ic_alert_circle_red_20),
contentDescription = null,
tint = TangemTheme.colors.icon.accent,
)
}
}
Icon(
imageVector = ImageVector.vectorResource(R.drawable.ic_chevron_right_24),
contentDescription = null,
tint = TangemTheme.colors.icon.informative,
modifier = Modifier.size(20.dp),
)
}
}
@Composable
private fun SupplyProcessing(text: TextReference, modifier: Modifier = Modifier) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = modifier
.clip(RoundedCornerShape(16.dp))
.background(TangemTheme.colors.background.primary)
.padding(12.dp),
) {
Image(
painter = painterResource(R.drawable.img_aave_22),
modifier = Modifier.size(36.dp),
contentDescription = null,
)
SpacerW12()
Column(
verticalArrangement = Arrangement.spacedBy(4.dp),
modifier = Modifier.weight(1f),
) {
Text(
text = stringResourceSafe(
R.string.yield_module_token_details_earn_notification_earning_on_your_balance_title,
),
style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.tertiary,
)
Text(
text = text.resolveReference(),
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.primary1,
)
}
SpacerW8()
CircularProgressIndicator(
modifier = Modifier.size(20.dp),
color = TangemTheme.colors.icon.accent,
strokeWidth = 2.dp,
)
}
}
@Composable
private fun SupplyLoading(modifier: Modifier = Modifier) {
Column(
verticalArrangement = Arrangement.spacedBy(12.dp),
horizontalAlignment = Alignment.CenterHorizontally,
modifier = modifier
.clip(RoundedCornerShape(16.dp))
.background(TangemTheme.colors.background.primary)
.padding(12.dp),
) {
Row(
horizontalArrangement = Arrangement.spacedBy(10.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Icon(
imageVector = ImageVector.vectorResource(R.drawable.ic_analytics_up_24),
contentDescription = null,
tint = TangemTheme.colors.icon.inactive,
modifier = Modifier
.background(TangemTheme.colors.icon.inactive.copy(alpha = 0.1f), CircleShape)
.padding(6.dp)
.size(24.dp),
)
Column(
verticalArrangement = Arrangement.spacedBy(2.dp),
) {
TextShimmer(
text = stringResourceSafe(R.string.yield_module_unavailable_title),
style = TangemTheme.typography.button,
)
TextShimmer(
modifier = Modifier.fillMaxWidth(),
text = stringResourceSafe(R.string.yield_module_unavailable_subtitle),
style = TangemTheme.typography.caption2,
)
TextShimmer(
modifier = Modifier.width(100.dp),
text = stringResourceSafe(R.string.yield_module_unavailable_subtitle),
style = TangemTheme.typography.caption2,
)
}
}
SecondaryButton(
text = stringResourceSafe(R.string.common_learn_more),
onClick = { },
showProgress = true,
enabled = false,
size = TangemButtonSize.WideAction,
modifier = Modifier.fillMaxWidth(),
)
}
}
@Composable
private fun SupplyInfo(
title: TextReference,
subtitle: TextReference,
rewardsApy: TextReference?,
iconTint: Color,
modifier: Modifier = Modifier,
button: (@Composable () -> Unit)? = null,
) {
Column(
verticalArrangement = Arrangement.spacedBy(12.dp),
horizontalAlignment = Alignment.CenterHorizontally,
modifier = modifier
.clip(RoundedCornerShape(16.dp))
.background(TangemTheme.colors.background.primary)
.padding(12.dp),
) {
Row(
horizontalArrangement = Arrangement.spacedBy(10.dp),
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth(),
) {
Icon(
imageVector = ImageVector.vectorResource(R.drawable.ic_analytics_up_24),
contentDescription = null,
tint = iconTint,
modifier = Modifier
.background(iconTint.copy(alpha = 0.1f), CircleShape)
.padding(6.dp)
.size(24.dp),
)
Column(
verticalArrangement = Arrangement.spacedBy(2.dp),
) {
Row(
horizontalArrangement = Arrangement.spacedBy(4.dp),
) {
Text(
text = title.resolveReference(),
style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.primary1,
)
if (rewardsApy != null) {
Text(
text = StringsSigns.DOT,
style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.tertiary,
)
Text(
text = rewardsApy.resolveReference(),
style = TangemTheme.typography.subtitle2,
maxLines = 1,
color = TangemTheme.colors.text.accent,
)
}
}
Text(
text = subtitle.resolveReference(),
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.tertiary,
)
}
}
button?.invoke()
}
}
// region Preview
@Composable
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
private fun YieldSupplyBlockContent_Preview(@PreviewParameter(PreviewProvider::class) params: YieldSupplyUM) {
TangemThemePreview {
YieldSupplyBlockContentLegacy(yieldSupplyUM = params, modifier = Modifier)
}
}
private class PreviewProvider : PreviewParameterProvider<YieldSupplyUM> {
override val values: Sequence<YieldSupplyUM>
get() = sequenceOf(
YieldSupplyUM.Available(
title = TextReference.Res(
R.string.yield_module_token_details_earn_notification_title,
wrappedList("5.1"),
),
apy = "5.1",
apyText = stringReference("5.1 % APY"),
onClick = {},
onLearnMoreClick = {},
),
YieldSupplyUM.Available(
title = TextReference.Res(R.string.yield_apy_boost_banner_title),
apy = "5.1",
apyText = stringReference("APY 5.1% x3 → 15.3%"),
onClick = {},
onLearnMoreClick = {},
isBoostAvailable = true,
),
YieldSupplyUM.Content(
title = stringReference("Aave l"),
subtitle = stringReference("Interest accrues automatically"),
rewardsApy = stringReference("APY 5.1%"),
onClick = {},
apy = "5.1",
shouldShowWarningIcon = false,
shouldShowInfoIcon = true,
),
YieldSupplyUM.Content(
title = stringReference("Aave lending is active "),
subtitle = stringReference("Interest accrues automatically"),
rewardsApy = stringReference("APY 5.1%"),
onClick = {},
apy = "5.1",
shouldShowWarningIcon = true,
shouldShowInfoIcon = false,
),
YieldSupplyUM.Loading,
YieldSupplyUM.Processing.Enter,
YieldSupplyUM.Processing.Exit,
YieldSupplyUM.Unavailable,
)
}
// endregion