Updated on 2026-08-14

This commit is contained in:
Tangem 2026-04-27 13:44:10 +02:00
parent 2d0bc9d9e6
commit ac9a3d15c3
28 changed files with 1528 additions and 127 deletions

View file

@ -1,5 +1,6 @@
package com.tangem.common.ui.markets.action
import com.tangem.domain.models.account.AccountStatus
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.tokens.model.TokenActionsState
@ -8,4 +9,6 @@ data class CryptoCurrencyData(
val userWallet: UserWallet,
val status: CryptoCurrencyStatus,
val actions: List<TokenActionsState.ActionState>,
val isAccountMode: Boolean,
val account: AccountStatus.CryptoPortfolio,
)

View file

@ -9,43 +9,103 @@ import com.tangem.core.ui.extensions.wrappedList
@Immutable
sealed class QuickActionUM(
val title: TextReference,
val description: TextReference,
@DrawableRes val icon: Int,
val isLongClickAvailable: Boolean = false,
open val title: TextReference,
open val description: TextReference,
@param:DrawableRes open val icon: Int,
open val isLongClickAvailable: Boolean = false,
) {
data object Buy : QuickActionUM(
title = resourceReference(R.string.common_buy),
description = resourceReference(R.string.buy_token_description),
icon = R.drawable.ic_plus_24,
)
data class Exchange(
val shouldShowBadge: Boolean,
sealed class V1(
override val title: TextReference,
override val description: TextReference,
@param:DrawableRes override val icon: Int,
override val isLongClickAvailable: Boolean = false,
) : QuickActionUM(
title = resourceReference(R.string.common_exchange),
description = resourceReference(R.string.exсhange_token_description),
icon = R.drawable.ic_exchange_vertical_24,
)
title = title,
description = description,
icon = icon,
isLongClickAvailable = isLongClickAvailable,
) {
data object Buy : V1(
title = resourceReference(R.string.common_buy),
description = resourceReference(R.string.buy_token_description),
icon = R.drawable.ic_plus_24,
)
data object Receive : QuickActionUM(
title = resourceReference(R.string.common_receive),
description = resourceReference(R.string.receive_token_description),
icon = R.drawable.ic_arrow_down_24,
isLongClickAvailable = true,
)
data class Exchange(
val shouldShowBadge: Boolean,
) : V1(
title = resourceReference(R.string.common_exchange),
description = resourceReference(R.string.exсhange_token_description),
icon = R.drawable.ic_exchange_vertical_24,
)
data object Stake : QuickActionUM(
title = resourceReference(R.string.common_stake),
description = resourceReference(R.string.stake_token_description),
icon = R.drawable.ic_staking_24,
)
data object Receive : V1(
title = resourceReference(R.string.common_receive),
description = resourceReference(R.string.receive_token_description),
icon = R.drawable.ic_arrow_down_24,
isLongClickAvailable = true,
)
data class YieldMode(
private val apy: String,
data object Stake : V1(
title = resourceReference(R.string.common_stake),
description = resourceReference(R.string.stake_token_description),
icon = R.drawable.ic_staking_24,
)
data class YieldMode(
private val apy: String,
) : V1(
title = resourceReference(R.string.common_yield_mode),
description = resourceReference(R.string.yield_module_main_screen_promo_banner_message, wrappedList(apy)),
icon = R.drawable.ic_analytics_up_mini_24,
)
}
sealed class V2(
override val title: TextReference,
override val description: TextReference,
@param:DrawableRes override val icon: Int,
override val isLongClickAvailable: Boolean = false,
) : QuickActionUM(
title = resourceReference(R.string.common_yield_mode),
description = resourceReference(R.string.yield_module_main_screen_promo_banner_message, wrappedList(apy)),
icon = R.drawable.ic_analytics_up_mini_24,
)
title = title,
description = description,
icon = icon,
isLongClickAvailable = isLongClickAvailable,
) {
data object Buy : V2(
title = resourceReference(R.string.common_buy),
description = resourceReference(R.string.quick_action_buy_description),
icon = R.drawable.ic_credit_card_20,
)
data class Exchange(
val shouldShowBadge: Boolean,
) : V2(
title = resourceReference(R.string.common_exchange),
description = resourceReference(R.string.quick_action_swap_description),
icon = R.drawable.ic_exchange_mini_24,
)
data object Receive : V2(
title = resourceReference(R.string.common_receive),
description = resourceReference(R.string.quick_action_receive_description),
icon = R.drawable.ic_qrcode_new_24,
isLongClickAvailable = true,
)
data object Stake : V2(
title = resourceReference(R.string.common_stake),
description = resourceReference(R.string.stake_token_description),
icon = R.drawable.ic_staking_24,
)
data class YieldMode(
private val apy: String,
) : V2(
title = resourceReference(R.string.common_yield_mode),
description = resourceReference(R.string.yield_module_main_screen_promo_banner_message, wrappedList(apy)),
icon = R.drawable.ic_analytics_up_mini_24,
)
}
}

View file

@ -2,39 +2,64 @@ package com.tangem.common.ui.markets.action
import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason
import com.tangem.domain.tokens.model.TokenActionsState
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
object QuickActionsConverter {
fun quickActions(cryptoData: CryptoCurrencyData, tokenActionsHandler: TokenActionsHandler): QuickActions {
fun quickActions(
cryptoData: CryptoCurrencyData,
tokenActionsHandler: TokenActionsHandler,
isRedesignEnabled: Boolean,
): QuickActions {
return QuickActions(
actions = toQuickActions(cryptoData.actions),
actions = toQuickActions(cryptoData.actions, isRedesignEnabled),
onQuickActionClick = { quickActionUM ->
when (quickActionUM) {
QuickActionUM.Buy -> tokenActionsHandler.handle(
QuickActionUM.V1.Buy -> tokenActionsHandler.handle(
action = TokenActionsBSContentUM.Action.Buy,
cryptoCurrencyData = cryptoData,
)
is QuickActionUM.Exchange -> tokenActionsHandler.handle(
is QuickActionUM.V1.Exchange -> tokenActionsHandler.handle(
action = TokenActionsBSContentUM.Action.Exchange,
cryptoCurrencyData = cryptoData,
)
QuickActionUM.Receive -> tokenActionsHandler.handle(
QuickActionUM.V1.Receive -> tokenActionsHandler.handle(
action = TokenActionsBSContentUM.Action.Receive,
cryptoCurrencyData = cryptoData,
)
QuickActionUM.Stake -> tokenActionsHandler.handle(
QuickActionUM.V1.Stake -> tokenActionsHandler.handle(
action = TokenActionsBSContentUM.Action.Stake,
cryptoCurrencyData = cryptoData,
)
is QuickActionUM.YieldMode -> tokenActionsHandler.handle(
is QuickActionUM.V1.YieldMode -> tokenActionsHandler.handle(
action = TokenActionsBSContentUM.Action.YieldMode,
cryptoCurrencyData = cryptoData,
)
QuickActionUM.V2.Buy -> tokenActionsHandler.handle(
action = TokenActionsBSContentUM.Action.Buy,
cryptoCurrencyData = cryptoData,
)
is QuickActionUM.V2.Exchange -> tokenActionsHandler.handle(
action = TokenActionsBSContentUM.Action.Exchange,
cryptoCurrencyData = cryptoData,
)
QuickActionUM.V2.Receive -> tokenActionsHandler.handle(
action = TokenActionsBSContentUM.Action.Receive,
cryptoCurrencyData = cryptoData,
)
QuickActionUM.V2.Stake -> tokenActionsHandler.handle(
action = TokenActionsBSContentUM.Action.Stake,
cryptoCurrencyData = cryptoData,
)
is QuickActionUM.V2.YieldMode -> tokenActionsHandler.handle(
action = TokenActionsBSContentUM.Action.YieldMode,
cryptoCurrencyData = cryptoData,
)
}
},
onQuickActionLongClick = { actionUM ->
if (actionUM == QuickActionUM.Receive) {
if (actionUM == QuickActionUM.V1.Receive || actionUM == QuickActionUM.V2.Receive) {
tokenActionsHandler.handle(
action = TokenActionsBSContentUM.Action.CopyAddress,
cryptoCurrencyData = cryptoData,
@ -44,18 +69,44 @@ object QuickActionsConverter {
)
}
fun toQuickActions(actions: List<TokenActionsState.ActionState>) = buildList {
actions.forEach { action ->
if (action.unavailabilityReason == ScenarioUnavailabilityReason.None) {
when (action) {
is TokenActionsState.ActionState.Buy -> QuickActionUM.Buy
is TokenActionsState.ActionState.Swap -> QuickActionUM.Exchange(action.shouldShowBadge)
is TokenActionsState.ActionState.Receive -> QuickActionUM.Receive
is TokenActionsState.ActionState.Stake -> QuickActionUM.Stake
is TokenActionsState.ActionState.YieldMode -> QuickActionUM.YieldMode(action.apy)
else -> null
}?.let(::add)
}
fun toQuickActions(actions: List<TokenActionsState.ActionState>, isRedesignEnabled: Boolean) =
if (isRedesignEnabled) {
redesignedQuickActions(actions)
} else {
legacyQuickActions(actions)
}
}.toImmutableList()
private fun redesignedQuickActions(actions: List<TokenActionsState.ActionState>): ImmutableList<QuickActionUM> {
return buildList {
actions.forEach { action ->
if (action.unavailabilityReason == ScenarioUnavailabilityReason.None) {
when (action) {
is TokenActionsState.ActionState.Buy -> QuickActionUM.V2.Buy
is TokenActionsState.ActionState.Swap -> QuickActionUM.V2.Exchange(action.shouldShowBadge)
is TokenActionsState.ActionState.Receive -> QuickActionUM.V2.Receive
is TokenActionsState.ActionState.Stake -> QuickActionUM.V2.Stake
is TokenActionsState.ActionState.YieldMode -> QuickActionUM.V2.YieldMode(action.apy)
else -> null
}?.let(::add)
}
}
}.toImmutableList()
}
private fun legacyQuickActions(actions: List<TokenActionsState.ActionState>): ImmutableList<QuickActionUM> {
return buildList {
actions.forEach { action ->
if (action.unavailabilityReason == ScenarioUnavailabilityReason.None) {
when (action) {
is TokenActionsState.ActionState.Buy -> QuickActionUM.V1.Buy
is TokenActionsState.ActionState.Swap -> QuickActionUM.V1.Exchange(action.shouldShowBadge)
is TokenActionsState.ActionState.Receive -> QuickActionUM.V1.Receive
is TokenActionsState.ActionState.Stake -> QuickActionUM.V1.Stake
is TokenActionsState.ActionState.YieldMode -> QuickActionUM.V1.YieldMode(action.apy)
else -> null
}?.let(::add)
}
}
}.toImmutableList()
}
}

View file

@ -3,16 +3,15 @@ package com.tangem.common.ui.account
import android.content.res.Configuration
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.*
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.Immutable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.layoutId
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
@ -22,12 +21,16 @@ import androidx.compose.ui.unit.dp
import com.tangem.common.ui.R
import com.tangem.core.ui.components.SpacerW12
import com.tangem.core.ui.components.account.AccountIconSize
import com.tangem.core.ui.ds.row.TangemRowContainer
import com.tangem.core.ui.ds.row.TangemRowLayoutId
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.res.TangemThemePreviewRedesign
import com.tangem.domain.models.account.AccountName
@Composable
@ -80,6 +83,66 @@ fun PortfolioSelectRow(
}
}
@Composable
fun PortfolioSelectRowV2(
state: PortfolioSelectUM,
modifier: Modifier = Modifier,
leftContent: @Composable RowScope.() -> Unit = {},
) {
val leftText = if (state.isAccountMode) R.string.account_details_title else R.string.wc_common_wallet
TangemRowContainer(
modifier.clickable(enabled = state.isMultiChoice, onClick = state.onClick),
) {
if (state.icon != null) {
Box(
modifier = Modifier.layoutId(TangemRowLayoutId.HEAD),
contentAlignment = Alignment.Center,
) {
AccountIcon(
modifier = Modifier.padding(end = TangemTheme.dimens2.x3),
name = state.name,
icon = state.icon,
size = AccountIconSize.RedesignedDefault,
)
}
}
Row(
modifier = Modifier.layoutId(TangemRowLayoutId.START_TOP),
verticalAlignment = Alignment.Bottom,
) {
leftContent()
Text(
modifier = Modifier.weight(1f),
text = stringResourceSafe(leftText),
maxLines = 1,
overflow = TextOverflow.Ellipsis,
style = TangemTheme.typography2.captionMedium12,
color = TangemTheme.colors2.text.neutral.secondary,
)
}
Text(
modifier = Modifier.layoutId(TangemRowLayoutId.START_BOTTOM),
maxLines = 1,
overflow = TextOverflow.Ellipsis,
text = state.name.resolveReference(),
style = TangemTheme.typography2.bodyMedium16,
color = TangemTheme.colors2.text.neutral.primary,
)
if (state.isMultiChoice) {
Icon(
modifier = Modifier
.layoutId(TangemRowLayoutId.TAIL)
.size(TangemTheme.dimens2.x5),
painter = painterResource(id = R.drawable.ic_select_choice_20),
contentDescription = null,
tint = TangemTheme.colors2.graphic.neutral.secondary,
)
}
}
}
@Immutable
data class PortfolioSelectUM(
val icon: AccountIconUM?,
@ -101,6 +164,20 @@ private fun PortfolioSelectRowPreview(@PreviewParameter(PreviewProvider::class)
}
}
@Preview(widthDp = 360, showBackground = true)
@Preview(widthDp = 360, showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun PortfolioSelectRowPreviewV2(@PreviewParameter(PreviewProvider::class) state: PortfolioSelectUM) {
TangemThemePreviewRedesign {
CompositionLocalProvider(LocalRedesignEnabled provides true) {
PortfolioSelectRowV2(
state = state,
modifier = Modifier.background(TangemTheme.colors2.surface.level3),
)
}
}
}
private class PreviewProvider : PreviewParameterProvider<PortfolioSelectUM> {
override val values: Sequence<PortfolioSelectUM>

View file

@ -154,7 +154,7 @@ private fun Preview(@PreviewParameter(PreviewProvider::class) state: AddTokenUM)
}
}
private class PreviewProvider : PreviewParameterProvider<AddTokenUM> {
internal class PreviewProvider : PreviewParameterProvider<AddTokenUM> {
private val tokenState
get() = TokenItemState.Content(
id = UUID.randomUUID().toString(),

View file

@ -0,0 +1,213 @@
package com.tangem.common.ui.addtoken
import android.content.res.Configuration
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.layout.layoutId
import androidx.compose.ui.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.unit.dp
import com.tangem.common.ui.account.PortfolioSelectRowV2
import com.tangem.core.ui.R
import com.tangem.core.ui.components.RectangleShimmer
import com.tangem.core.ui.components.SpacerH
import com.tangem.core.ui.components.currency.icon.CurrencyIcon
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.core.ui.components.token.state.TokenItemState
import com.tangem.core.ui.ds.button.PrimaryTangemButton
import com.tangem.core.ui.ds.button.TangemButtonIconPosition
import com.tangem.core.ui.ds.button.TangemButtonShape
import com.tangem.core.ui.ds.button.TangemButtonSize
import com.tangem.core.ui.ds.image.TangemIconUM
import com.tangem.core.ui.ds.row.TangemRowContainer
import com.tangem.core.ui.ds.row.TangemRowLayoutId
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreviewRedesign
@Composable
fun AddTokenContentV2(state: AddTokenUM, modifier: Modifier = Modifier) {
Column(
modifier = modifier.fillMaxWidth(),
) {
TokenHeader(
cryptoCurrencyIconState = state.tokenToAdd.iconState,
tokenName = state.tokenToAdd.titleState,
)
Column {
if (state.portfolio.isMultiChoice) {
PortfolioSelectRowV2(
modifier = Modifier.background(
color = TangemTheme.colors2.surface.level3,
shape = RoundedCornerShape(TangemTheme.dimens2.x5),
),
state = state.portfolio,
)
SpacerH(TangemTheme.dimens2.x2)
}
NetworkRow(state.network)
}
SpacerH(TangemTheme.dimens2.x4)
AddButton(
modifier = Modifier.fillMaxWidth(),
state = state.button,
)
}
}
@Composable
private fun TokenHeader(
cryptoCurrencyIconState: CurrencyIconState,
tokenName: TokenItemState.TitleState,
modifier: Modifier = Modifier,
) {
Column(
modifier = modifier
.fillMaxWidth()
.padding(vertical = TangemTheme.dimens2.x8),
horizontalAlignment = Alignment.CenterHorizontally,
) {
CurrencyIcon(
state = cryptoCurrencyIconState,
iconSize = 70.dp,
shouldDisplayNetwork = false,
)
SpacerH(TangemTheme.dimens2.x4)
when (tokenName) {
is TokenItemState.TitleState.Content -> {
Text(
text = tokenName.text.resolveReference(),
style = TangemTheme.typography2.headingSemibold28,
color = TangemTheme.colors2.text.neutral.primary,
)
}
TokenItemState.TitleState.Loading -> {
RectangleShimmer(
modifier = Modifier.size(width = TangemTheme.dimens2.x17, height = TangemTheme.dimens2.x9),
radius = TangemTheme.dimens2.x25,
)
}
TokenItemState.TitleState.Locked -> {
Box(
modifier = Modifier.background(
color = TangemTheme.colors2.surface.level4,
shape = RoundedCornerShape(TangemTheme.dimens2.x25),
),
)
}
}
}
}
@Composable
private fun NetworkRow(state: AddTokenUM.Network, modifier: Modifier = Modifier) {
TangemRowContainer(
modifier
.clickable(enabled = state.editable, onClick = state.onClick)
.background(
color = TangemTheme.colors2.surface.level3,
shape = RoundedCornerShape(TangemTheme.dimens2.x5),
),
) {
Icon(
modifier = Modifier
.layoutId(TangemRowLayoutId.HEAD)
.size(TangemTheme.dimens2.x10)
.padding(end = TangemTheme.dimens2.x3),
tint = Color.Unspecified,
imageVector = ImageVector.vectorResource(id = state.icon),
contentDescription = null,
)
Text(
modifier = Modifier.layoutId(TangemRowLayoutId.START_TOP),
text = stringResourceSafe(R.string.wc_common_network),
style = TangemTheme.typography2.captionMedium12,
color = TangemTheme.colors2.text.neutral.secondary,
)
Text(
modifier = Modifier.layoutId(TangemRowLayoutId.START_BOTTOM),
maxLines = 1,
overflow = TextOverflow.Ellipsis,
text = state.name.resolveReference(),
style = TangemTheme.typography2.bodyMedium16,
color = TangemTheme.colors2.text.neutral.primary,
)
if (state.editable) {
Icon(
modifier = Modifier
.layoutId(TangemRowLayoutId.TAIL)
.size(TangemTheme.dimens2.x5),
painter = painterResource(id = com.tangem.common.ui.R.drawable.ic_select_choice_20),
contentDescription = null,
tint = TangemTheme.colors2.graphic.neutral.secondary,
)
}
}
}
@Composable
private fun AddButton(state: AddTokenUM.Button, modifier: Modifier = Modifier) {
val isIconExists = state.isEnabled && state.isTangemIconVisible
PrimaryTangemButton(
modifier = modifier,
text = state.text,
onClick = state.onConfirmClick,
tangemIconUM = if (isIconExists) {
TangemIconUM.Icon(
iconRes = R.drawable.ic_tangem_24,
tintReference = {
if (state.isEnabled) {
TangemTheme.colors2.graphic.neutral.primaryInverted
} else {
TangemTheme.colors2.graphic.neutral.quaternary
}
},
)
} else {
null
},
iconPosition = TangemButtonIconPosition.Start,
isEnabled = state.isEnabled,
size = TangemButtonSize.X12,
shape = TangemButtonShape.Rounded,
)
}
@Preview(widthDp = 360, showBackground = true)
@Preview(widthDp = 360, showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun Preview(@PreviewParameter(PreviewProvider::class) state: AddTokenUM) {
TangemThemePreviewRedesign {
CompositionLocalProvider(LocalRedesignEnabled provides true) {
Box(
modifier = Modifier
.fillMaxWidth()
.background(TangemTheme.colors2.surface.level2)
.padding(horizontal = 16.dp),
) {
AddTokenContentV2(state = state)
}
}
}
}

View file

@ -1177,6 +1177,9 @@
<string name="qr_scanner_error_unsupported_network_title">No supported tokens found</string>
<string name="qr_scanner_warning_unknown_parameters_message">This QR code contains parameters that are not recognized: %s. Some payment details may be lost if you continue.</string>
<string name="qr_scanner_warning_unknown_parameters_title">Unknown Parameters</string>
<string name="quick_action_buy_description">Credit card or bank account</string>
<string name="quick_action_receive_description">Share your address or QR-code</string>
<string name="quick_action_swap_description">Between your portfolios</string>
<string name="receive_bottom_sheet_no_memo_required_message">No memo required</string>
<string name="receive_bottom_sheet_warning_message">%1$s (%2$s) on %3$s network</string>
<string name="receive_bottom_sheet_warning_message_compact">%1$s on %2$s network</string>

View file

@ -30,7 +30,7 @@ import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
enum class AccountIconSize {
Default, Large, Medium, Small, ExtraSmall
Default, Large, Medium, Small, ExtraSmall, RedesignedDefault
}
/**
@ -128,6 +128,7 @@ fun AccountCharIcon(char: Char, color: Color, size: AccountIconSize, modifier: M
AccountIconSize.Medium -> TangemTheme.typography.subtitle1
AccountIconSize.Small -> TangemTheme.typography.subtitle2
AccountIconSize.ExtraSmall -> TangemTheme.typography.caption1
AccountIconSize.RedesignedDefault -> TangemTheme.typography2.headingSemibold28
}
val textSize by animateFloatAsState(
@ -159,6 +160,7 @@ private fun AccountIconSize.iconSizeInDp(): Dp = when (this) {
AccountIconSize.Medium -> 16.dp
AccountIconSize.Small -> 12.dp
AccountIconSize.ExtraSmall -> 8.dp
AccountIconSize.RedesignedDefault -> 20.dp
}
private fun AccountIconSize.boxSizeInDp(): Dp = when (this) {
@ -167,6 +169,7 @@ private fun AccountIconSize.boxSizeInDp(): Dp = when (this) {
AccountIconSize.Medium -> 28.dp
AccountIconSize.Small -> 20.dp
AccountIconSize.ExtraSmall -> 14.dp
AccountIconSize.RedesignedDefault -> 40.dp
}
private fun AccountIconSize.boxShapeSizeInDp(): Dp = when (this) {
@ -175,6 +178,7 @@ private fun AccountIconSize.boxShapeSizeInDp(): Dp = when (this) {
AccountIconSize.Medium -> 8.dp
AccountIconSize.Small -> 6.dp
AccountIconSize.ExtraSmall -> 4.dp
AccountIconSize.RedesignedDefault -> 12.dp
}
@Preview(showBackground = true)
@ -203,6 +207,7 @@ private fun Sample() {
AccountIconSize.Medium -> AccountIconSize.Small
AccountIconSize.Small -> AccountIconSize.ExtraSmall
AccountIconSize.ExtraSmall -> AccountIconSize.Default
AccountIconSize.RedesignedDefault -> AccountIconSize.Large
}
}) { Text("Change") }

View file

@ -1,11 +1,7 @@
package com.tangem.core.ui.components.token.internal
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.*
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@ -13,11 +9,11 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.testTag
import com.tangem.core.ui.test.TokenElementsTestTags
import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.testTag
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastForEach
@ -25,7 +21,10 @@ import com.tangem.core.ui.components.RectangleShimmer
import com.tangem.core.ui.components.icons.IconTint
import com.tangem.core.ui.components.text.applyBladeBrush
import com.tangem.core.ui.extensions.orMaskWithStars
import com.tangem.core.ui.extensions.resolveAnnotatedReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.test.TokenElementsTestTags
import kotlinx.collections.immutable.ImmutableList
import com.tangem.core.ui.components.token.state.TokenItemState.FiatAmountState as TokenFiatAmountState
@Composable
@ -60,6 +59,11 @@ internal fun TokenFiatAmount(state: TokenFiatAmountState?, isBalanceHidden: Bool
// Empty box for proper measurements
Box(modifier)
}
is TokenFiatAmountState.AnnotatedContent -> FiatAmountAnnotatedText(
text = state.text.orMaskWithStars(isBalanceHidden).resolveAnnotatedReference(),
modifier = modifier,
isFlickering = state.isFlickering,
)
null -> Unit
}
}
@ -77,7 +81,7 @@ private fun IconAmount(state: TokenFiatAmountState.Icon, modifier: Modifier = Mo
@Composable
private fun ContentFiatAmount(
text: String,
icons: List<TokenFiatAmountState.Content.IconUM>,
icons: ImmutableList<TokenFiatAmountState.Content.IconUM>,
isAmountFlickering: Boolean,
modifier: Modifier = Modifier,
) {
@ -135,6 +139,25 @@ private fun FiatAmountText(
)
}
@Composable
private fun FiatAmountAnnotatedText(
text: AnnotatedString,
modifier: Modifier = Modifier,
isAvailable: Boolean = true,
isFlickering: Boolean = false,
) {
Text(
modifier = modifier,
text = text,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
style = TangemTheme.typography.body2.applyBladeBrush(
isEnabled = isFlickering,
textColor = if (isAvailable) TangemTheme.colors.text.primary1 else TangemTheme.colors.text.tertiary,
),
)
}
private fun Modifier.placeholderSize(): Modifier = composed {
return@composed this
.padding(vertical = 4.dp)

View file

@ -230,6 +230,11 @@ sealed class TokenItemState {
val tint: IconTint = IconTint.Inactive,
) : FiatAmountState()
data class AnnotatedContent(
val text: TextReference,
val isFlickering: Boolean = false,
) : FiatAmountState()
data object Loading : FiatAmountState()
data object Locked : FiatAmountState()

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
android:viewportWidth="20"
android:viewportHeight="20">
<path
android:pathData="M13.832,3.945C14.803,3.945 15.289,3.946 15.686,4.061C16.648,4.339 17.4,5.091 17.678,6.053C17.792,6.449 17.793,6.935 17.793,7.906V11.354L17.788,12.435C17.773,13.382 17.714,13.959 17.476,14.427L17.362,14.628C17.082,15.085 16.68,15.458 16.2,15.702L16.081,15.758C15.634,15.95 15.077,16.001 14.208,16.015L13.126,16.02H6.873C5.342,16.02 4.529,16.02 3.919,15.758L3.8,15.702C3.32,15.458 2.918,15.085 2.638,14.628L2.525,14.427C2.287,13.959 2.227,13.382 2.212,12.435L2.207,11.354V7.906C2.207,7.057 2.207,6.579 2.283,6.207L2.321,6.053C2.582,5.151 3.26,4.433 4.137,4.118L4.313,4.061C4.71,3.946 5.197,3.945 6.168,3.945H13.832ZM3.457,11.354C3.457,12.19 3.458,12.743 3.492,13.167C3.526,13.575 3.585,13.753 3.639,13.859C3.798,14.173 4.053,14.428 4.366,14.588C4.473,14.642 4.651,14.701 5.06,14.734C5.483,14.769 6.036,14.769 6.873,14.769H13.126C13.963,14.769 14.517,14.769 14.94,14.734C15.349,14.701 15.526,14.642 15.633,14.588C15.946,14.428 16.201,14.173 16.361,13.859C16.416,13.753 16.473,13.575 16.507,13.167C16.541,12.743 16.543,12.19 16.543,11.354V8.531H3.457V11.354ZM10.136,12.227C10.481,12.228 10.761,12.508 10.761,12.852C10.761,13.198 10.481,13.477 10.136,13.477H9.081C8.736,13.477 8.456,13.198 8.456,12.852C8.456,12.507 8.736,12.227 9.081,12.227H10.136ZM14.589,12.227C14.934,12.228 15.214,12.508 15.214,12.852C15.214,13.198 14.934,13.477 14.589,13.477H12.164C11.819,13.477 11.539,13.198 11.539,12.852C11.539,12.508 11.819,12.228 12.164,12.227H14.589ZM6.168,5.196C5.111,5.196 4.845,5.208 4.661,5.261C4.111,5.42 3.68,5.851 3.521,6.4C3.481,6.541 3.465,6.73 3.459,7.281H16.54C16.534,6.729 16.518,6.541 16.478,6.4C16.319,5.851 15.889,5.42 15.339,5.261C15.155,5.208 14.889,5.196 13.832,5.196H6.168Z"
android:fillColor="#0099FF"/>
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
android:viewportWidth="20"
android:viewportHeight="20">
<path
android:pathData="M13.46,11.793C13.85,11.402 14.483,11.402 14.874,11.793C15.264,12.183 15.264,12.816 14.874,13.207L10.708,17.374C10.317,17.764 9.684,17.764 9.294,17.374L5.127,13.207C4.736,12.816 4.736,12.183 5.127,11.793C5.517,11.402 6.15,11.402 6.541,11.793L10,15.252L13.46,11.793ZM9.294,2.626C9.684,2.236 10.317,2.235 10.708,2.626L14.874,6.793C15.264,7.183 15.264,7.816 14.874,8.207C14.483,8.597 13.85,8.597 13.46,8.207L10,4.748L6.541,8.207C6.15,8.597 5.517,8.597 5.127,8.207C4.736,7.816 4.736,7.183 5.127,6.793L9.294,2.626Z"
android:fillColor="#B0B0B0"/>
</vector>

View file

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

View file

@ -15,10 +15,12 @@ import com.tangem.core.decompose.factory.ComponentFactory
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.domain.models.TokenReceiveConfig
import com.tangem.features.commonfeatures.impl.addtoportfolio.analytics.PortfolioAnalyticsEvent
import com.tangem.features.commonfeatures.impl.addtoportfolio.model.TokenActionsModel
import com.tangem.features.commonfeatures.impl.addtoportfolio.ui.TokenActionsContent
import com.tangem.features.commonfeatures.impl.addtoportfolio.ui.TokenActionsContentV2
import com.tangem.features.tokenreceive.TokenReceiveComponent
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
@ -44,10 +46,17 @@ internal class TokenActionsComponent @AssistedInject constructor(
val state = model.uiState.collectAsStateWithLifecycle()
val bottomSheet by bottomSheetSlot.subscribeAsState()
val tokenActionsUM = state.value ?: return
TokenActionsContent(
modifier = modifier,
state = tokenActionsUM,
)
if (LocalRedesignEnabled.current) {
TokenActionsContentV2(
modifier = modifier,
state = tokenActionsUM,
)
} else {
TokenActionsContent(
modifier = modifier,
state = tokenActionsUM,
)
}
bottomSheet.child?.instance?.BottomSheet()
}

View file

@ -51,9 +51,9 @@ private const val TOKEN_ACTIONS_DELAY = 500L
@Suppress("LongParameterList", "LargeClass")
internal class AddToPortfolioModel @Inject constructor(
paramsContainer: ParamsContainer,
designFeatureToggles: DesignFeatureToggles,
override val dispatchers: CoroutineDispatcherProvider,
val portfolioSelectorController: PortfolioSelectorController,
private val designFeatureToggles: DesignFeatureToggles,
private val callbackDelegate: AddToPortfolioCallbackDelegate,
private val getCryptoCurrencyActionsUseCase: GetCryptoCurrencyActionsUseCaseV2,
private val getTokenMarketCryptoCurrency: GetTokenMarketCryptoCurrency,
@ -441,7 +441,7 @@ internal class AddToPortfolioModel @Inject constructor(
currency = addedToken.currency,
accountId = selectedPortfolio.account.account.account.accountId,
).onEach { state ->
val requestedQuickActions = toQuickActions(state.states)
val requestedQuickActions = toQuickActions(state.states, designFeatureToggles.isRedesignEnabled)
when {
requestedQuickActions.isNotEmpty() -> {
timerJob.cancel()
@ -458,6 +458,8 @@ internal class AddToPortfolioModel @Inject constructor(
userWallet = selectedPortfolio.userWallet,
status = actionsState.cryptoCurrencyStatus,
actions = actionsState.states,
isAccountMode = selectedPortfolio.isAccountMode,
account = selectedPortfolio.account.account,
)
}
}

View file

@ -10,17 +10,15 @@ import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
import com.tangem.domain.models.TokenReceiveConfig
import com.tangem.domain.transaction.usecase.ReceiveAddressesFactory
import com.tangem.features.commonfeatures.impl.addtoportfolio.TokenActionsComponent
import com.tangem.features.commonfeatures.impl.addtoportfolio.ui.state.TokenActionsUM
import com.tangem.utils.Provider
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import javax.inject.Inject
@ -29,6 +27,7 @@ import javax.inject.Inject
internal class TokenActionsModel @Inject constructor(
paramsContainer: ParamsContainer,
getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase,
tokenActionsIntentsFactory: TokenActionsHandler.Factory,
override val dispatchers: CoroutineDispatcherProvider,
private val uiBuilder: TokenActionsUiBuilder,
@ -52,13 +51,29 @@ internal class TokenActionsModel @Inject constructor(
)
val bottomSheetNavigation: SlotNavigation<TokenReceiveConfig> = SlotNavigation()
val uiState: StateFlow<TokenActionsUM?> = params.data
.mapLatest { uiBuilder.build(it, tokenActionsHandler, analyticsEventBuilder.first()) }
.stateIn(
scope = modelScope,
started = SharingStarted.Eagerly,
initialValue = null,
)
@OptIn(ExperimentalCoroutinesApi::class)
val uiState: StateFlow<TokenActionsUM?> =
combine(
params.data,
getBalanceHidingSettingsUseCase.isBalanceHidden(),
) { cryptoCurrencyData, isBalanceHidden ->
cryptoCurrencyData to isBalanceHidden
}
.mapLatest { (cryptoCurrencyData, isBalanceHidden) ->
uiBuilder.build(
cryptoCurrencyData = cryptoCurrencyData,
tokenActionsHandler = tokenActionsHandler,
eventBuilder = analyticsEventBuilder.first(),
appCurrency = currentAppCurrency.value,
isBalanceHidden = isBalanceHidden,
)
}
.stateIn(
scope = modelScope,
started = SharingStarted.Eagerly,
initialValue = null,
)
private fun handledQuickAction(handledAction: TokenActionsHandler.HandledQuickAction) = modelScope.launch {
val event = analyticsEventBuilder.first().getTokenActionClick(actionUM = handledAction.action)

View file

@ -1,32 +1,73 @@
package com.tangem.features.commonfeatures.impl.addtoportfolio.model
import androidx.compose.ui.text.SpanStyle
import com.tangem.common.getTotalCryptoAmount
import com.tangem.common.getTotalFiatAmount
import com.tangem.common.ui.account.*
import com.tangem.common.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter
import com.tangem.common.ui.markets.action.CryptoCurrencyData
import com.tangem.common.ui.markets.action.QuickActionsConverter.quickActions
import com.tangem.common.ui.markets.action.TokenActionsHandler
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.common.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter
import com.tangem.core.ui.DesignFeatureToggles
import com.tangem.core.ui.R
import com.tangem.core.ui.components.token.state.TokenItemState
import com.tangem.core.ui.ds.badge.TangemBadgeIconPosition
import com.tangem.core.ui.ds.badge.TangemBadgeShape
import com.tangem.core.ui.ds.badge.TangemBadgeSize
import com.tangem.core.ui.ds.badge.TangemBadgeUM
import com.tangem.core.ui.ds.image.TangemIconUM
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.format.bigdecimal.*
import com.tangem.core.ui.res.TangemTheme
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.features.commonfeatures.impl.addtoportfolio.TokenActionsComponent
import com.tangem.features.commonfeatures.impl.addtoportfolio.analytics.PortfolioAnalyticsEvent
import com.tangem.features.commonfeatures.impl.addtoportfolio.ui.state.TokenActionsUM
import java.math.BigDecimal
import javax.inject.Inject
@ModelScoped
internal class TokenActionsUiBuilder @Inject constructor(
paramsContainer: ParamsContainer,
private val analyticsEventHandler: AnalyticsEventHandler,
private val designFeatureToggles: DesignFeatureToggles,
) {
private val params = paramsContainer.require<TokenActionsComponent.Params>()
fun build(
data: CryptoCurrencyData,
cryptoCurrencyData: CryptoCurrencyData,
tokenActionsHandler: TokenActionsHandler,
eventBuilder: PortfolioAnalyticsEvent.EventBuilder,
appCurrency: AppCurrency,
isBalanceHidden: Boolean,
): TokenActionsUM {
return if (designFeatureToggles.isRedesignEnabled) {
buildV2(
cryptoCurrencyData = cryptoCurrencyData,
tokenActionsHandler = tokenActionsHandler,
eventBuilder = eventBuilder,
appCurrency = appCurrency,
isBalanceHidden = isBalanceHidden,
)
} else {
buildV1(
cryptoCurrencyData = cryptoCurrencyData,
tokenActionsHandler = tokenActionsHandler,
eventBuilder = eventBuilder,
)
}
}
private fun buildV1(
cryptoCurrencyData: CryptoCurrencyData,
tokenActionsHandler: TokenActionsHandler,
eventBuilder: PortfolioAnalyticsEvent.EventBuilder,
): TokenActionsUM {
val status = data.status
val status = cryptoCurrencyData.status
val tokenUM = TokenItemState.Content(
id = status.currency.id.value,
iconState = CryptoCurrencyToIconStateConverter().convert(status.currency),
@ -39,11 +80,147 @@ internal class TokenActionsUiBuilder @Inject constructor(
)
return TokenActionsUM(
token = tokenUM,
quickActions = quickActions(
cryptoData = cryptoCurrencyData,
tokenActionsHandler = tokenActionsHandler,
isRedesignEnabled = false,
),
onLaterClick = {
analyticsEventHandler.send(eventBuilder.getTokenLater())
params.callbacks.onLaterClick()
},
quickActions = quickActions(data, tokenActionsHandler),
)
}
private fun buildV2(
cryptoCurrencyData: CryptoCurrencyData,
tokenActionsHandler: TokenActionsHandler,
eventBuilder: PortfolioAnalyticsEvent.EventBuilder,
appCurrency: AppCurrency,
isBalanceHidden: Boolean,
): 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 = createFiatAmountState(status, appCurrency),
subtitle2State = createSubtitle2State(status),
subtitleState = TokenItemState.SubtitleState.TextContent(stringReference(status.currency.symbol)),
onItemClick = null,
onItemLongClick = null,
)
return TokenActionsUM(
token = tokenUM,
quickActions = quickActions(
cryptoData = cryptoCurrencyData,
tokenActionsHandler = tokenActionsHandler,
isRedesignEnabled = true,
),
onLaterClick = {
analyticsEventHandler.send(eventBuilder.getTokenLater())
params.callbacks.onLaterClick()
},
isBalancesHidden = isBalanceHidden,
portfolioBadge = createPortfolioBadge(cryptoCurrencyData),
)
}
private fun createPortfolioBadge(cryptoCurrencyData: CryptoCurrencyData): TangemBadgeUM {
val icon: AccountIconUM?
val name = if (cryptoCurrencyData.isAccountMode) {
icon = CryptoPortfolioIconConverter.convert(cryptoCurrencyData.account.account.icon)
cryptoCurrencyData
.account
.account
.accountName
.toUM()
.value
} else {
icon = null
stringReference(cryptoCurrencyData.userWallet.name)
}
return TangemBadgeUM(
text = name,
tangemIconUM = if (icon == null) {
TangemIconUM.Icon(
iconRes = R.drawable.ic_key_card_20,
tintReference = { TangemTheme.colors2.graphic.neutral.tertiary },
)
} else {
TangemIconUM.Icon(
iconRes = icon.value.getResId(),
tintReference = { icon.color.getUiColor() },
)
},
size = TangemBadgeSize.X6,
shape = TangemBadgeShape.Rounded,
iconPosition = if (cryptoCurrencyData.isAccountMode) {
TangemBadgeIconPosition.Start
} else {
TangemBadgeIconPosition.End
},
)
}
private fun createSubtitle2State(status: CryptoCurrencyStatus): TokenItemState.Subtitle2State? {
return when (status.value) {
is CryptoCurrencyStatus.Loaded,
is CryptoCurrencyStatus.Custom,
is CryptoCurrencyStatus.NoQuote,
is CryptoCurrencyStatus.NoAccount,
-> {
TokenItemState.Subtitle2State.TextContent(
text = status.getTotalCryptoAmount().format {
crypto(status.currency)
},
)
}
is CryptoCurrencyStatus.Loading,
is CryptoCurrencyStatus.MissedDerivation,
is CryptoCurrencyStatus.Unreachable,
is CryptoCurrencyStatus.NoAmount,
-> TokenItemState.Subtitle2State.TextContent(
text = BigDecimal.ZERO.format {
crypto(status.currency)
},
)
}
}
private fun createFiatAmountState(
status: CryptoCurrencyStatus,
appCurrency: AppCurrency,
): TokenItemState.FiatAmountState? {
return when (status.value) {
is CryptoCurrencyStatus.Loaded,
is CryptoCurrencyStatus.Custom,
is CryptoCurrencyStatus.NoQuote,
is CryptoCurrencyStatus.NoAccount,
-> {
TokenItemState.FiatAmountState.AnnotatedContent(
text = status.getTotalFiatAmount().formatStyled {
fiat(
fiatCurrencyCode = appCurrency.code,
fiatCurrencySymbol = appCurrency.symbol,
spanStyleReference = { SpanStyle(color = TangemTheme.colors2.text.neutral.secondary) },
).price()
},
)
}
is CryptoCurrencyStatus.Unreachable,
is CryptoCurrencyStatus.NoAmount,
is CryptoCurrencyStatus.MissedDerivation,
is CryptoCurrencyStatus.Loading,
-> TokenItemState.FiatAmountState.AnnotatedContent(
text = BigDecimal.ZERO.formatStyled {
fiat(
fiatCurrencyCode = appCurrency.code,
fiatCurrencySymbol = appCurrency.symbol,
spanStyleReference = { SpanStyle(color = TangemTheme.colors2.text.neutral.secondary) },
).price()
},
)
}
}
}

View file

@ -1,31 +1,47 @@
package com.tangem.features.commonfeatures.impl.addtoportfolio.ui
import android.content.res.Configuration
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.key
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clip
import androidx.compose.ui.layout.layoutId
import androidx.compose.ui.res.painterResource
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.util.fastForEachIndexed
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastForEach
import com.tangem.core.ui.components.label.Label
import com.tangem.core.ui.components.label.entity.LabelStyle
import com.tangem.core.ui.components.label.entity.LabelUM
import com.tangem.core.ui.components.rows.BlockchainRow
import com.tangem.core.ui.components.rows.model.BlockchainRowUM
import com.tangem.core.ui.ds.badge.TangemBadge
import com.tangem.core.ui.ds.badge.TangemBadgeShape
import com.tangem.core.ui.ds.badge.TangemBadgeSize
import com.tangem.core.ui.ds.row.TangemRowContainer
import com.tangem.core.ui.ds.row.TangemRowLayoutId
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.features.commonfeatures.impl.addtoportfolio.ui.state.ChooseNetworkUM
import com.tangem.core.ui.res.TangemThemePreviewRedesign
import com.tangem.features.commonfeatures.impl.R
import com.tangem.features.commonfeatures.impl.addtoportfolio.ui.state.ChooseNetworkUM
import kotlinx.collections.immutable.persistentListOf
import java.util.UUID
@ -33,13 +49,28 @@ private const val DISABLED_ALPHA = 0.4f
@Composable
internal fun ChooseNetworkContent(state: ChooseNetworkUM, modifier: Modifier = Modifier) {
if (LocalRedesignEnabled.current) {
ChooseNetworkContentV2(
state = state,
modifier = modifier,
)
} else {
ChooseNetworkContentV1(
state = state,
modifier = modifier,
)
}
}
@Composable
internal fun ChooseNetworkContentV1(state: ChooseNetworkUM, modifier: Modifier = Modifier) {
Column(
modifier = modifier
.fillMaxWidth()
.clip(RoundedCornerShape(TangemTheme.dimens.radius14))
.background(TangemTheme.colors.background.action),
) {
state.networks.fastForEachIndexed { index, model ->
state.networks.fastForEach { model ->
key(model.id) {
BlockchainRow(
model = model,
@ -66,6 +97,120 @@ internal fun ChooseNetworkContent(state: ChooseNetworkUM, modifier: Modifier = M
}
}
@Composable
internal fun ChooseNetworkContentV2(state: ChooseNetworkUM, modifier: Modifier = Modifier) {
Column(
modifier = modifier
.fillMaxWidth()
.background(
color = TangemTheme.colors2.surface.level3,
shape = RoundedCornerShape(34.dp),
)
.padding(horizontal = TangemTheme.dimens2.x4, vertical = TangemTheme.dimens2.x2),
) {
Text(
modifier = Modifier.padding(top = TangemTheme.dimens2.x4, bottom = TangemTheme.dimens2.x2),
text = stringResourceSafe(R.string.common_choose_network),
style = TangemTheme.typography2.subheadlineMedium14,
color = TangemTheme.colors2.text.neutral.tertiary,
)
state.networks.fastForEach { model ->
key(model.id) {
TangemRowContainer(
modifier = Modifier.clickable(
enabled = model.isEnabled,
onClick = { state.onNetworkClick(model) },
),
contentPadding = PaddingValues(vertical = TangemTheme.dimens2.x3),
content = {
NetworkIcon(
modifier = Modifier
.layoutId(layoutId = TangemRowLayoutId.HEAD)
.padding(end = TangemTheme.dimens2.x3),
model = model,
)
NetworkText(
modifier = Modifier.layoutId(layoutId = TangemRowLayoutId.START_TOP),
model = model,
)
if (!model.isEnabled) {
TangemBadge(
modifier = Modifier
.layoutId(layoutId = TangemRowLayoutId.TAIL)
.padding(start = TangemTheme.dimens2.x2),
text = resourceReference(R.string.common_added),
size = TangemBadgeSize.X6,
shape = TangemBadgeShape.Rounded,
)
}
},
)
}
}
}
}
@Composable
private fun NetworkIcon(model: BlockchainRowUM, modifier: Modifier = Modifier) {
if (model.isSelected && model.isEnabled) {
Image(
modifier = modifier
.size(TangemTheme.dimens2.x10),
painter = painterResource(id = model.iconResId),
contentDescription = null,
)
} else {
Icon(
modifier = modifier
.background(
color = TangemTheme.colors2.button.backgroundSecondary,
shape = CircleShape,
)
.size(TangemTheme.dimens2.x10),
painter = painterResource(id = model.iconResId),
tint = TangemTheme.colors2.graphic.neutral.tertiary,
contentDescription = null,
)
}
}
@Composable
private fun NetworkText(model: BlockchainRowUM, modifier: Modifier = Modifier) {
Row(
modifier = modifier,
verticalAlignment = Alignment.Bottom,
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x1),
) {
Text(
modifier = Modifier.weight(weight = 10f, fill = false),
text = model.name,
style = TangemTheme.typography2.bodyMedium16,
color = when {
model.isEnabled && model.isMainNetwork -> TangemTheme.colors2.text.neutral.primary
model.isEnabled -> TangemTheme.colors2.text.neutral.secondary
else -> TangemTheme.colors2.text.status.disabled
},
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
Text(
modifier = Modifier.weight(weight = 5f, fill = false),
text = model.type,
style = TangemTheme.typography2.captionMedium12,
color = if (model.isEnabled) {
TangemTheme.colors2.text.neutral.tertiary
} else {
TangemTheme.colors2.text.status.disabled
},
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}
}
@Composable
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
@ -77,6 +222,19 @@ private fun Preview(@PreviewParameter(ChooseNetworkContentProvider::class) conte
}
}
@Composable
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
private fun PreviewV2(@PreviewParameter(ChooseNetworkContentProvider::class) content: ChooseNetworkUM) {
TangemThemePreviewRedesign {
CompositionLocalProvider(LocalRedesignEnabled provides true) {
ChooseNetworkContent(
state = content,
)
}
}
}
internal class ChooseNetworkContentProvider : PreviewParameterProvider<ChooseNetworkUM> {
private val blockchainRow = BlockchainRowUM(

View file

@ -123,7 +123,7 @@ private fun ActionRow(
.size(36.dp)
.drawWithContent {
drawContent()
if (state is QuickActionUM.Exchange && state.shouldShowBadge) {
if (state is QuickActionUM.V1.Exchange && state.shouldShowBadge) {
drawBadge(containerColor = containerColor, offset = 4.dp)
}
},
@ -190,9 +190,9 @@ private class TokenActionsContentPreviewProvider : PreviewParameterProvider<Toke
TokenActionsUM(
quickActions = QuickActions(
actions = persistentListOf(
QuickActionUM.Buy,
QuickActionUM.Exchange(shouldShowBadge = true),
QuickActionUM.Receive,
QuickActionUM.V1.Buy,
QuickActionUM.V1.Exchange(shouldShowBadge = true),
QuickActionUM.V1.Receive,
),
onQuickActionClick = {},
onQuickActionLongClick = {},

View file

@ -0,0 +1,292 @@
package com.tangem.features.commonfeatures.impl.addtoportfolio.ui
import android.content.res.Configuration
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.key
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.layout.layoutId
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.SpanStyle
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.util.fastForEach
import com.tangem.common.ui.markets.action.QuickActionUM
import com.tangem.common.ui.markets.action.QuickActions
import com.tangem.core.ui.components.SpacerH
import com.tangem.core.ui.components.currency.icon.CurrencyIcon
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.core.ui.components.token.state.TokenItemState
import com.tangem.core.ui.ds.badge.*
import com.tangem.core.ui.ds.button.SecondaryTangemButton
import com.tangem.core.ui.ds.button.TangemButtonShape
import com.tangem.core.ui.ds.button.TangemButtonSize
import com.tangem.core.ui.ds.image.TangemIconUM
import com.tangem.core.ui.ds.row.TangemRowContainer
import com.tangem.core.ui.ds.row.TangemRowLayoutId
import com.tangem.core.ui.extensions.*
import com.tangem.core.ui.format.bigdecimal.fiat
import com.tangem.core.ui.format.bigdecimal.formatStyled
import com.tangem.core.ui.format.bigdecimal.price
import com.tangem.core.ui.haptic.TangemHapticEffect
import com.tangem.core.ui.res.*
import com.tangem.features.commonfeatures.impl.R
import com.tangem.features.commonfeatures.impl.addtoportfolio.ui.state.TokenActionsUM
import kotlinx.collections.immutable.persistentListOf
import java.math.BigDecimal
import java.util.UUID
private const val ACTION_BACKGROUND_ALPHA = .1f
@Composable
internal fun TokenActionsContentV2(state: TokenActionsUM, modifier: Modifier = Modifier) {
Column(
modifier = modifier.fillMaxWidth(),
) {
TokenHeader(
addedToken = state.token,
portfolioBadge = state.portfolioBadge,
isBalanceHidden = state.isBalancesHidden,
)
SpacerH(TangemTheme.dimens2.x2)
Column(
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x2),
) {
state.quickActions.actions.fastForEach { actionUM ->
key(actionUM.title) {
ActionRow(
state = actionUM,
onClick = { state.quickActions.onQuickActionClick(actionUM) },
onLongClick = { state.quickActions.onQuickActionLongClick(actionUM) },
)
}
}
}
SpacerH(TangemTheme.dimens2.x2)
SecondaryTangemButton(
modifier = Modifier.fillMaxWidth(),
onClick = state.onLaterClick,
text = resourceReference(R.string.common_later),
size = TangemButtonSize.X12,
shape = TangemButtonShape.Rounded,
)
}
}
@OptIn(ExperimentalFoundationApi::class)
@Composable
private fun ActionRow(
state: QuickActionUM,
onClick: () -> Unit,
onLongClick: (() -> Unit),
modifier: Modifier = Modifier,
) {
val hapticManager = LocalHapticManager.current
val onLongClickInternal = {
hapticManager.perform(TangemHapticEffect.View.LongPress)
onLongClick()
}
TangemRowContainer(
modifier = modifier
.combinedClickable(
onLongClick = onLongClickInternal.takeIf { state.isLongClickAvailable },
onClick = {
hapticManager.perform(TangemHapticEffect.View.SegmentTick)
onClick()
},
)
.background(
color = TangemTheme.colors2.surface.level3,
shape = RoundedCornerShape(TangemTheme.dimens2.x5),
),
) {
Box(
modifier = Modifier
.layoutId(TangemRowLayoutId.HEAD)
.padding(end = TangemTheme.dimens2.x3)
.size(40.dp)
.background(
color = TangemTheme.colors2.graphic.status.accent.copy(alpha = ACTION_BACKGROUND_ALPHA),
shape = CircleShape,
),
contentAlignment = Alignment.Center,
) {
Icon(
modifier = Modifier.size(20.dp),
imageVector = ImageVector.vectorResource(id = state.icon),
contentDescription = null,
tint = TangemTheme.colors2.graphic.status.accent,
)
}
Text(
modifier = Modifier.layoutId(TangemRowLayoutId.START_TOP),
text = state.title.resolveReference(),
style = TangemTheme.typography2.bodyMedium16,
color = TangemTheme.colors2.text.neutral.primary,
)
Text(
modifier = Modifier.layoutId(TangemRowLayoutId.START_BOTTOM),
text = state.description.resolveReference(),
style = TangemTheme.typography2.captionMedium12,
color = TangemTheme.colors2.text.neutral.secondary,
)
Icon(
modifier = Modifier
.layoutId(TangemRowLayoutId.TAIL)
.padding(start = TangemTheme.dimens2.x2)
.size(24.dp),
imageVector = ImageVector.vectorResource(R.drawable.ic_chevron_small_right_24),
tint = TangemTheme.colors2.graphic.neutral.tertiary,
contentDescription = null,
)
}
}
@Composable
private fun TokenHeader(
addedToken: TokenItemState,
isBalanceHidden: Boolean,
portfolioBadge: TangemBadgeUM?,
modifier: Modifier = Modifier,
) {
Column(
modifier = modifier
.fillMaxWidth()
.padding(vertical = TangemTheme.dimens2.x8),
horizontalAlignment = Alignment.CenterHorizontally,
) {
CurrencyIcon(
state = addedToken.iconState,
iconSize = 70.dp,
networkBadgeSize = 24.dp,
)
SpacerH(TangemTheme.dimens2.x5)
when (val fiat = addedToken.fiatAmountState) {
is TokenItemState.FiatAmountState.AnnotatedContent -> {
Text(
text = fiat.text.orMaskWithStars(isBalanceHidden).resolveAnnotatedReference(),
style = TangemTheme.typography2.titleRegular44,
color = TangemTheme.colors2.text.neutral.primary,
)
SpacerH(TangemTheme.dimens2.x2)
}
else -> Unit
}
when (val cryptoAmount = addedToken.subtitle2State) {
is TokenItemState.Subtitle2State.TextContent -> {
Text(
text = cryptoAmount.text.orMaskWithStars(isBalanceHidden),
style = TangemTheme.typography2.bodyMedium16,
color = TangemTheme.colors2.text.neutral.secondary,
)
SpacerH(TangemTheme.dimens2.x2)
}
else -> Unit
}
SpacerH(TangemTheme.dimens2.x7)
if (portfolioBadge == null) return
TangemBadge(portfolioBadge)
}
}
@Preview(widthDp = 360, showBackground = true)
@Preview(widthDp = 360, showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun Preview(@PreviewParameter(TokenActionsContentPreviewProviderV2::class) state: TokenActionsUM) {
TangemThemePreviewRedesign {
CompositionLocalProvider(LocalRedesignEnabled provides true) {
Box(
modifier = Modifier
.fillMaxWidth()
.background(TangemTheme.colors2.surface.level2)
.padding(horizontal = 16.dp),
) {
TokenActionsContentV2(
state = state,
)
}
}
}
}
private class TokenActionsContentPreviewProviderV2 : PreviewParameterProvider<TokenActionsUM> {
private val tokenState
get() = TokenItemState.Content(
id = UUID.randomUUID().toString(),
iconState = CurrencyIconState.TokenIcon(
url = null,
topBadgeIconResId = R.drawable.img_eth_22,
fallbackTint = TangemColorPalette.Black,
fallbackBackground = TangemColorPalette.Meadow,
isGrayscale = false,
shouldShowCustomBadge = false,
),
titleState = TokenItemState.TitleState.Content(
text = stringReference(value = "Tether"),
),
fiatAmountState = TokenItemState.FiatAmountState.AnnotatedContent(
text = BigDecimal.ONE.formatStyled {
fiat(
fiatCurrencyCode = "USD",
fiatCurrencySymbol = "$",
spanStyleReference = { SpanStyle(color = TangemTheme.colors2.text.neutral.secondary) },
).price()
},
),
subtitle2State = TokenItemState.Subtitle2State.TextContent(
"1.01 USDT",
),
subtitleState = TokenItemState.SubtitleState.TextContent(value = stringReference("USDT")),
onItemClick = {},
onItemLongClick = {},
)
override val values: Sequence<TokenActionsUM>
get() = sequenceOf(
TokenActionsUM(
quickActions = QuickActions(
actions = persistentListOf(
QuickActionUM.V2.Buy,
QuickActionUM.V2.Exchange(shouldShowBadge = true),
QuickActionUM.V2.Receive,
),
onQuickActionClick = {},
onQuickActionLongClick = {},
),
token = tokenState,
onLaterClick = {},
portfolioBadge = TangemBadgeUM(
text = stringReference("Wallet 2"),
tangemIconUM = TangemIconUM.Icon(
iconRes = R.drawable.ic_key_card_20,
tintReference = { TangemTheme.colors2.graphic.neutral.tertiary },
),
size = TangemBadgeSize.X6,
shape = TangemBadgeShape.Rounded,
iconPosition = TangemBadgeIconPosition.End,
),
),
)
}

View file

@ -2,9 +2,12 @@ package com.tangem.features.commonfeatures.impl.addtoportfolio.ui.state
import com.tangem.common.ui.markets.action.QuickActions
import com.tangem.core.ui.components.token.state.TokenItemState
import com.tangem.core.ui.ds.badge.TangemBadgeUM
internal data class TokenActionsUM(
val token: TokenItemState,
val quickActions: QuickActions,
val onLaterClick: () -> Unit,
val isBalancesHidden: Boolean = false,
val portfolioBadge: TangemBadgeUM? = null,
)

View file

@ -7,9 +7,11 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.model.getOrCreateModel
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.impl.portfolioselector.ui.PortfolioSelectorBS
import com.tangem.features.commonfeatures.impl.portfolioselector.ui.PortfolioSelectorContent
import com.tangem.features.commonfeatures.impl.portfolioselector.ui.PortfolioSelectorContentV2
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
@ -43,10 +45,17 @@ internal class DefaultPortfolioSelectorComponent @AssistedInject constructor(
@Composable
override fun Content(modifier: Modifier) {
val state by model.state.collectAsStateWithLifecycle()
PortfolioSelectorContent(
state = state,
modifier = modifier,
)
if (LocalRedesignEnabled.current) {
PortfolioSelectorContentV2(
state = state,
modifier = modifier,
)
} else {
PortfolioSelectorContent(
state = state,
modifier = modifier,
)
}
}
@AssistedFactory

View file

@ -4,6 +4,7 @@ import android.content.res.Configuration
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
@ -12,8 +13,10 @@ 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.res.LocalRedesignEnabled
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.res.TangemThemePreviewRedesign
import com.tangem.features.commonfeatures.impl.R
import com.tangem.features.commonfeatures.impl.portfolioselector.entity.PortfolioSelectorUM
@ -41,11 +44,19 @@ internal fun PortfolioSelectorBS(
)
},
content = {
PortfolioSelectorContent(
state = state,
contentPadding = PaddingValues(bottom = 16.dp),
modifier = modifier.padding(horizontal = 16.dp),
)
if (LocalRedesignEnabled.current) {
PortfolioSelectorContentV2(
state = state,
contentPadding = PaddingValues(bottom = 16.dp),
modifier = modifier.padding(horizontal = 16.dp),
)
} else {
PortfolioSelectorContent(
state = state,
contentPadding = PaddingValues(bottom = 16.dp),
modifier = modifier.padding(horizontal = 16.dp),
)
}
},
)
}
@ -62,4 +73,20 @@ private fun Preview(@PreviewParameter(PortfolioSelectorPreviewStateProvider::cla
onBack = {},
)
}
}
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun PreviewV2(@PreviewParameter(PortfolioSelectorPreviewStateProvider::class) params: PortfolioSelectorUM) {
TangemThemePreviewRedesign {
CompositionLocalProvider(LocalRedesignEnabled provides true) {
PortfolioSelectorBS(
state = params,
onDismiss = {},
modifier = Modifier,
onBack = {},
)
}
}
}

View file

@ -0,0 +1,228 @@
package com.tangem.features.commonfeatures.impl.portfolioselector.ui
import android.content.res.Configuration
import androidx.compose.animation.AnimatedContent
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.layout.layoutId
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import com.tangem.common.ui.userwallet.CardImage
import com.tangem.common.ui.userwallet.getBalanceValueAndFlickerState
import com.tangem.common.ui.userwallet.getInformationValue
import com.tangem.common.ui.userwallet.state.UserWalletItemUM
import com.tangem.core.ui.R
import com.tangem.core.ui.components.TextShimmer
import com.tangem.core.ui.components.text.applyBladeBrush
import com.tangem.core.ui.decorations.roundedShapeItemDecoration
import com.tangem.core.ui.ds.row.TangemRowContainer
import com.tangem.core.ui.ds.row.TangemRowLayoutId
import com.tangem.core.ui.extensions.conditional
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreviewRedesign
import com.tangem.features.commonfeatures.impl.portfolioselector.entity.PortfolioSelectorItemUM
import com.tangem.features.commonfeatures.impl.portfolioselector.entity.PortfolioSelectorUM
import com.tangem.utils.StringsSigns.DOT
private const val DISABLED_WALLET_ALPHA = 0.5f
@Composable
internal fun PortfolioSelectorContentV2(
state: PortfolioSelectorUM,
modifier: Modifier = Modifier,
contentPadding: PaddingValues = PaddingValues(),
) {
LazyColumn(
modifier = modifier,
contentPadding = contentPadding,
) {
val items = state.items
itemsIndexed(
items = items,
key = { _, item -> item.id },
) { index, item ->
when (item) {
is PortfolioSelectorItemUM.Portfolio ->
PortfolioSelectorItem(
state = item.item,
modifier = Modifier
.roundedShapeItemDecoration(
currentIndex = index,
lastIndex = state.items.lastIndex,
backgroundColor = TangemTheme.colors2.surface.level3,
radius = TangemTheme.dimens2.x5,
addDefaultPadding = false,
)
.clickable(enabled = item.item.isEnabled, onClick = item.item.onClick)
.conditional(!item.item.isEnabled) { alpha(DISABLED_WALLET_ALPHA) },
)
is PortfolioSelectorItemUM.GroupTitle -> WalletNameRow(
model = item,
modifier = Modifier
.fillMaxWidth()
.roundedShapeItemDecoration(
currentIndex = index,
lastIndex = state.items.lastIndex,
backgroundColor = TangemTheme.colors2.surface.level3,
radius = TangemTheme.dimens2.x5,
addDefaultPadding = false,
)
.padding(horizontal = TangemTheme.dimens.spacing16),
)
}
}
}
}
@Composable
private fun PortfolioSelectorItem(state: UserWalletItemUM, modifier: Modifier = Modifier) {
TangemRowContainer(modifier = modifier) {
Box(
modifier = Modifier
.layoutId(TangemRowLayoutId.HEAD)
.padding(end = TangemTheme.dimens2.x3),
contentAlignment = Alignment.Center,
) {
CardImage(
modifier = Modifier.size(40.dp),
imageState = state.imageState,
)
}
Text(
modifier = Modifier.layoutId(layoutId = TangemRowLayoutId.START_TOP),
text = state.name.resolveReference(),
style = TangemTheme.typography2.bodyMedium16,
color = TangemTheme.colors2.text.neutral.primary,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
InfoRow(
modifier = Modifier.layoutId(layoutId = TangemRowLayoutId.START_BOTTOM),
information = state.information,
balance = state.balance,
)
}
}
@Composable
private fun InfoRow(
information: UserWalletItemUM.Information,
balance: UserWalletItemUM.Balance,
modifier: Modifier = Modifier,
) {
Row(
modifier = modifier,
verticalAlignment = Alignment.CenterVertically,
) {
AnimatedContent(
targetState = information,
label = "Information content",
) { information ->
val informationValue = getInformationValue(information)
if (informationValue == null) {
TextShimmer(
style = TangemTheme.typography2.captionMedium12,
text = "aaaaa",
)
} else {
Text(
text = informationValue,
style = TangemTheme.typography2.captionMedium12,
color = TangemTheme.colors2.text.neutral.secondary,
maxLines = 1,
)
}
}
Row {
Text(
text = " $DOT ",
style = TangemTheme.typography2.captionMedium12,
color = TangemTheme.colors2.text.neutral.secondary,
maxLines = 1,
)
val (balanceValue, isFlickering) = getBalanceValueAndFlickerState(balance)
if (balanceValue == null) {
TextShimmer(
style = TangemTheme.typography2.captionMedium12,
text = "aaaaa",
)
} else {
Text(
text = balanceValue,
style = TangemTheme.typography2.captionMedium12.applyBladeBrush(
isEnabled = isFlickering,
textColor = TangemTheme.colors2.text.neutral.secondary,
),
maxLines = 1,
)
}
}
}
}
@Composable
private fun WalletNameRow(model: PortfolioSelectorItemUM.GroupTitle, modifier: Modifier = Modifier) {
Row(
modifier = modifier.padding(top = TangemTheme.dimens2.x4, bottom = TangemTheme.dimens2.x2),
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x1),
) {
Text(
modifier = Modifier.align(Alignment.CenterVertically),
text = model.name.resolveReference(),
style = TangemTheme.typography2.subheadlineMedium14,
color = TangemTheme.colors2.text.neutral.secondary,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
Icon(
imageVector = ImageVector.vectorResource(R.drawable.ic_key_card_20),
modifier = Modifier
.align(Alignment.Bottom)
.size(TangemTheme.dimens2.x5),
tint = TangemTheme.colors2.graphic.neutral.tertiary,
contentDescription = null,
)
}
}
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun Preview(@PreviewParameter(PortfolioSelectorPreviewStateProvider::class) params: PortfolioSelectorUM) {
TangemThemePreviewRedesign {
CompositionLocalProvider(LocalRedesignEnabled provides true) {
Box(
modifier = Modifier.fillMaxWidth(),
contentAlignment = Alignment.Center,
) {
PortfolioSelectorContentV2(
state = params,
modifier = Modifier.background(color = TangemTheme.colors.background.tertiary),
)
}
}
}
}

View file

@ -7,6 +7,7 @@ import com.tangem.common.ui.account.CryptoPortfolioIconConverter
import com.tangem.common.ui.account.toUM
import com.tangem.common.ui.markets.action.CryptoCurrencyData
import com.tangem.common.ui.markets.action.TokenActionsHandler
import com.tangem.core.ui.DesignFeatureToggles
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.domain.account.models.AccountStatusList
@ -53,6 +54,7 @@ internal class MarketsPortfolioDelegate @AssistedInject constructor(
private val getCryptoCurrencyActionsUseCase: GetCryptoCurrencyActionsUseCaseV2,
private val getUserWalletUseCase: GetUserWalletUseCase,
private val yieldSupplyGetAvailabilityUseCase: YieldSupplyGetAvailabilityUseCase,
private val designFeatureToggles: DesignFeatureToggles,
isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase,
@Assisted private val scope: CoroutineScope,
@Assisted private val token: TokenMarketParams,
@ -229,6 +231,7 @@ internal class MarketsPortfolioDelegate @AssistedInject constructor(
isBalanceHidden = isBalanceHidden,
onTokenItemClick = { },
tokenActionsHandler = tokenActionsHandler,
isRedesignEnabled = designFeatureToggles.isRedesignEnabled,
)
portfolio.portfolios.forEach { portfolioItem ->
@ -253,6 +256,8 @@ internal class MarketsPortfolioDelegate @AssistedInject constructor(
userWallet = userWallet,
status = currencyStatus,
actions = actions,
isAccountMode = isAccountMode,
account = accountWithAdded.accountStatus,
)
val expandedKey = portfolioItem.userWallet.walletId to currencyStatus.currency.id
val isExpand = expanded.contains(expandedKey)

View file

@ -22,6 +22,7 @@ internal class PortfolioTokenUMConverter(
private val isBalanceHidden: Boolean,
private val onTokenItemClick: (CryptoCurrencyStatus) -> Unit,
private val tokenActionsHandler: TokenActionsHandler,
private val isRedesignEnabled: Boolean,
) : Converter<CryptoCurrencyData, PortfolioTokenUM> {
fun convertV2(
@ -38,7 +39,11 @@ internal class PortfolioTokenUMConverter(
walletId = value.userWallet.walletId,
isBalanceHidden = isBalanceHidden,
isQuickActionsShown = isQuickActionsShown,
quickActions = quickActions(cryptoData = value, tokenActionsHandler = tokenActionsHandler),
quickActions = quickActions(
cryptoData = value,
tokenActionsHandler = tokenActionsHandler,
isRedesignEnabled = isRedesignEnabled,
),
)
}
@ -57,7 +62,11 @@ internal class PortfolioTokenUMConverter(
walletId = value.userWallet.walletId,
isBalanceHidden = isBalanceHidden,
isQuickActionsShown = false,
quickActions = quickActions(cryptoData = value, tokenActionsHandler = tokenActionsHandler),
quickActions = quickActions(
cryptoData = value,
tokenActionsHandler = tokenActionsHandler,
isRedesignEnabled = isRedesignEnabled,
),
)
}
}

View file

@ -180,7 +180,7 @@ private fun AnimatedVisibilityScope.QuickActionIcon(state: QuickActionUM) {
)
.size(TangemTheme.dimens.size32)
.semantics {
contentDescription = if (state is QuickActionUM.Exchange && state.shouldShowBadge) {
contentDescription = if (state is QuickActionUM.V1.Exchange && state.shouldShowBadge) {
"Badge shown"
} else {
"Badge hidden"
@ -188,7 +188,7 @@ private fun AnimatedVisibilityScope.QuickActionIcon(state: QuickActionUM) {
}
.drawWithContent {
drawContent()
if (state is QuickActionUM.Exchange && state.shouldShowBadge) {
if (state is QuickActionUM.V1.Exchange && state.shouldShowBadge) {
drawBadge(containerColor = containerColor, offset = 4.dp)
}
},
@ -229,9 +229,9 @@ private fun Preview() {
) {
PortfolioQuickActions(
actions = persistentListOf(
QuickActionUM.Buy,
QuickActionUM.Exchange(shouldShowBadge = true),
QuickActionUM.Receive,
QuickActionUM.V1.Buy,
QuickActionUM.V1.Exchange(shouldShowBadge = true),
QuickActionUM.V1.Receive,
),
isVisible = isVisible,
onActionClick = {},
@ -250,9 +250,9 @@ private fun PreviewRtl() {
Box(modifier = Modifier.background(color = TangemTheme.colors.background.action)) {
PortfolioQuickActions(
actions = persistentListOf(
QuickActionUM.Buy,
QuickActionUM.Exchange(shouldShowBadge = true),
QuickActionUM.Receive,
QuickActionUM.V1.Buy,
QuickActionUM.V1.Exchange(shouldShowBadge = true),
QuickActionUM.V1.Receive,
),
isVisible = true,
onActionClick = {},

View file

@ -129,9 +129,9 @@ internal class PreviewMyPortfolioUMProvider : PreviewParameterProvider<MyPortfol
isQuickActionsShown = false,
quickActions = QuickActions(
actions = persistentListOf(
QuickActionUM.Buy,
QuickActionUM.Exchange(shouldShowBadge = true),
QuickActionUM.Receive,
QuickActionUM.V1.Buy,
QuickActionUM.V1.Exchange(shouldShowBadge = true),
QuickActionUM.V1.Receive,
),
onQuickActionClick = {},
onQuickActionLongClick = {},