Updated on 2026-08-14

This commit is contained in:
Tangem 2024-09-03 12:36:09 +03:00
commit d617c9c30d
491 changed files with 15019 additions and 5050 deletions

View file

@ -5,13 +5,15 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import com.arkivanov.decompose.defaultComponentContext
import com.tangem.common.routing.AppRouter
import com.tangem.common.routing.utils.asRouter
import com.tangem.core.decompose.context.DefaultAppComponentContext
import com.tangem.core.decompose.di.DecomposeComponent
import com.tangem.core.ui.UiDependencies
import com.tangem.core.ui.screen.ComposeFragment
import com.tangem.feature.wallet.presentation.router.InnerWalletRouter
import com.tangem.features.markets.MarketsFeatureToggles
import com.tangem.features.markets.component.MarketsEntryComponent
import com.tangem.features.markets.entry.MarketsEntryComponent
import com.tangem.features.wallet.navigation.WalletRouter
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.hilt.android.AndroidEntryPoint
@ -44,6 +46,9 @@ internal class WalletFragment : ComposeFragment() {
@Inject
internal lateinit var marketsFeatureToggles: MarketsFeatureToggles
@Inject
internal lateinit var appRouter: AppRouter
private var marketsEntryComponent: MarketsEntryComponent? = null
private val _walletRouter: InnerWalletRouter
@ -60,6 +65,7 @@ internal class WalletFragment : ComposeFragment() {
messageHandler = uiDependencies.eventMessageHandler,
dispatchers = coroutineDispatcherProvider,
hiltComponentBuilder = componentBuilder,
replaceRouter = appRouter.asRouter(),
)
marketsEntryComponent = marketsEntryComponentFactory.create(appContext)

View file

@ -2,13 +2,12 @@ package com.tangem.feature.wallet.presentation.common
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.core.ui.components.marketprice.PriceChangeType
import com.tangem.core.ui.components.token.state.TokenItemState
import com.tangem.core.ui.event.consumedEvent
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.res.TangemColorPalette
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.feature.wallet.impl.R
import com.tangem.feature.wallet.presentation.common.state.TokenItemState
import com.tangem.feature.wallet.presentation.organizetokens.model.DraggableItem
import com.tangem.feature.wallet.presentation.organizetokens.model.OrganizeTokensListState
import com.tangem.feature.wallet.presentation.organizetokens.model.OrganizeTokensState
@ -66,122 +65,22 @@ internal object WalletPreviewData {
)
}
val coinIconState
get() = CurrencyIconState.CoinIcon(
url = null,
fallbackResId = R.drawable.img_polygon_22,
isGrayscale = false,
showCustomBadge = false,
)
private val tokenIconState
get() = CurrencyIconState.TokenIcon(
url = null,
topBadgeIconResId = R.drawable.img_polygon_22,
fallbackTint = TangemColorPalette.Black,
fallbackBackground = TangemColorPalette.Meadow,
isGrayscale = false,
showCustomBadge = false,
)
private val customTokenIconState
get() = CurrencyIconState.CustomTokenIcon(
tint = TangemColorPalette.Black,
background = TangemColorPalette.Meadow,
topBadgeIconResId = R.drawable.img_polygon_22,
isGrayscale = false,
)
val tokenItemVisibleState by lazy {
TokenItemState.Content(
id = UUID.randomUUID().toString(),
iconState = coinIconState,
titleState = TokenItemState.TitleState.Content(text = "Polygon", hasPending = true),
fiatAmountState = TokenItemState.FiatAmountState.Content(text = "321 $", hasStaked = true),
cryptoAmountState = TokenItemState.CryptoAmountState.Content(text = "5,412 MATIC"),
cryptoPriceState = TokenItemState.CryptoPriceState.Unknown,
onItemClick = {},
onItemLongClick = {},
)
}
val testnetTokenItemVisibleState by lazy {
tokenItemVisibleState.copy(
titleState = TokenItemState.TitleState.Content(text = "Polygon testnet"),
iconState = tokenIconState.copy(isGrayscale = true),
)
}
val tokenItemHiddenState by lazy {
TokenItemState.Content(
id = UUID.randomUUID().toString(),
iconState = tokenIconState,
titleState = TokenItemState.TitleState.Content(text = "Polygon"),
fiatAmountState = TokenItemState.FiatAmountState.Content(text = "321 $", hasStaked = false),
cryptoAmountState = TokenItemState.CryptoAmountState.Content(text = "5,412 MATIC"),
cryptoPriceState = TokenItemState.CryptoPriceState.Content(
price = "312 USD",
priceChangePercent = "2.0%",
type = PriceChangeType.UP,
),
onItemClick = {},
onItemLongClick = {},
)
}
val tokenItemDragState by lazy {
private val tokenItemDragState by lazy {
TokenItemState.Draggable(
id = UUID.randomUUID().toString(),
iconState = tokenIconState,
iconState = CurrencyIconState.TokenIcon(
url = null,
topBadgeIconResId = R.drawable.img_polygon_22,
fallbackTint = TangemColorPalette.Black,
fallbackBackground = TangemColorPalette.Meadow,
isGrayscale = false,
showCustomBadge = false,
),
titleState = TokenItemState.TitleState.Content(text = "Polygon"),
cryptoAmountState = TokenItemState.CryptoAmountState.Content(text = "3 172,14 $"),
)
}
val tokenItemUnreachableState by lazy {
TokenItemState.Unreachable(
id = UUID.randomUUID().toString(),
iconState = tokenIconState,
titleState = TokenItemState.TitleState.Content(text = "Polygon"),
onItemClick = {},
onItemLongClick = {},
)
}
val tokenItemNoAddressState by lazy {
TokenItemState.NoAddress(
id = UUID.randomUUID().toString(),
iconState = tokenIconState,
titleState = TokenItemState.TitleState.Content(text = "Polygon"),
onItemLongClick = {},
)
}
val customTokenItemVisibleState by lazy {
tokenItemVisibleState.copy(
titleState = TokenItemState.TitleState.Content(text = "Polygon"),
iconState = customTokenIconState.copy(
tint = TangemColorPalette.White,
background = TangemColorPalette.Black,
),
)
}
val customTestnetTokenItemVisibleState by lazy {
tokenItemVisibleState.copy(
titleState = TokenItemState.TitleState.Content(text = "Polygon"),
iconState = customTokenIconState.copy(isGrayscale = true),
)
}
val loadingTokenItemState by lazy {
TokenItemState.Loading(
id = "Loading#1",
iconState = customTokenIconState.copy(isGrayscale = true),
titleState = TokenItemState.TitleState.Content(text = "Polygon"),
)
}
private const val networksSize = 10
private const val tokensSize = 3
private val draggableItems by lazy {

View file

@ -56,30 +56,46 @@ internal fun DraggableNetworkGroupItem(
)
}
// TODO: [REDACTED_JIRA]
@Composable
private fun InternalNetworkGroupItem(
networkName: String,
modifier: Modifier = Modifier,
endIcon: @Composable RowScope.() -> Unit = {},
endIcon: @Composable (RowScope.() -> Unit)? = null,
) {
Column(modifier = modifier) {
Row(
modifier = Modifier
.background(TangemTheme.colors.background.primary)
.padding(horizontal = TangemTheme.dimens.spacing12)
.fillMaxWidth()
.heightIn(min = TangemTheme.dimens.size40),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween,
) {
Text(
text = stringResource(id = R.string.wallet_network_group_title, networkName),
modifier = Modifier.padding(top = TangemTheme.dimens.spacing12, bottom = TangemTheme.dimens.spacing8),
style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.tertiary,
)
endIcon()
}
val minHeight = if (endIcon == null) TangemTheme.dimens.size36 else TangemTheme.dimens.size40
val padding = if (endIcon == null) {
PaddingValues(
start = TangemTheme.dimens.spacing12,
top = TangemTheme.dimens.spacing12,
end = TangemTheme.dimens.spacing12,
bottom = TangemTheme.dimens.spacing4,
)
} else {
PaddingValues(
start = TangemTheme.dimens.spacing12,
top = TangemTheme.dimens.spacing11,
end = TangemTheme.dimens.spacing12,
bottom = TangemTheme.dimens.spacing5,
)
}
Row(
modifier = modifier
.background(TangemTheme.colors.background.primary)
.fillMaxWidth()
.heightIn(min = minHeight)
.padding(paddingValues = padding),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween,
) {
Text(
text = stringResource(id = R.string.wallet_network_group_title, networkName),
style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.tertiary,
)
if (endIcon != null) endIcon()
}
}

View file

@ -1,426 +0,0 @@
package com.tangem.feature.wallet.presentation.common.component
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.layout.Measurable
import androidx.compose.ui.layout.Placeable
import androidx.compose.ui.layout.layoutId
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
import androidx.compose.ui.unit.Constraints
import com.tangem.core.ui.components.currency.icon.CurrencyIcon
import com.tangem.core.ui.components.marketprice.PriceChangeType
import com.tangem.core.ui.extensions.rememberHapticFeedback
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.feature.wallet.presentation.common.WalletPreviewData
import com.tangem.feature.wallet.presentation.common.component.token.*
import com.tangem.feature.wallet.presentation.common.state.TokenItemState
import org.burnoutcrew.reorderable.ReorderableLazyListState
import kotlin.math.max
private const val TITLE_MIN_WIDTH_COEFFICIENT = 0.3
private const val PRICE_MIN_WIDTH_COEFFICIENT = 0.32
private enum class LayoutId {
ICON, TITLE, FIAT_AMOUNT, CRYPTO_AMOUNT, CRYPTO_PRICE, NON_FIAT_CONTENT
}
@Composable
internal fun TokenItem(
state: TokenItemState,
isBalanceHidden: Boolean,
modifier: Modifier = Modifier,
reorderableTokenListState: ReorderableLazyListState? = null,
) {
val betweenRowsMargin = TangemTheme.dimens.spacing2
CustomContainer(
state = state,
modifier = modifier
.tokenClickable(state = state)
.background(color = TangemTheme.colors.background.primary),
) {
CurrencyIcon(
state = state.iconState,
modifier = Modifier
.layoutId(layoutId = LayoutId.ICON)
.padding(end = TangemTheme.dimens.spacing8),
)
TokenTitle(
state = state.titleState,
modifier = Modifier
.layoutId(layoutId = LayoutId.TITLE)
.padding(end = TangemTheme.dimens.spacing8)
.padding(bottom = betweenRowsMargin),
)
TokenFiatAmount(
state = state.fiatAmountState,
isBalanceHidden = isBalanceHidden,
modifier = Modifier
.layoutId(layoutId = LayoutId.FIAT_AMOUNT)
.padding(bottom = betweenRowsMargin),
)
TokenPrice(
state = state.cryptoPriceState,
modifier = Modifier
.layoutId(layoutId = LayoutId.CRYPTO_PRICE)
.padding(end = TangemTheme.dimens.spacing8),
)
TokenCryptoAmount(
state = state.cryptoAmountState,
isBalanceHidden = isBalanceHidden,
modifier = Modifier.layoutId(layoutId = LayoutId.CRYPTO_AMOUNT),
)
NonFiatContentBlock(
state = state,
reorderableTokenListState = reorderableTokenListState,
modifier = Modifier.layoutId(layoutId = LayoutId.NON_FIAT_CONTENT),
)
}
}
@OptIn(ExperimentalFoundationApi::class)
private fun Modifier.tokenClickable(state: TokenItemState): Modifier = composed {
when (state) {
is TokenItemState.Content -> {
val onLongClick = rememberHapticFeedback(state = state, onAction = state.onItemLongClick)
combinedClickable(onClick = state.onItemClick, onLongClick = onLongClick)
}
is TokenItemState.Unreachable -> {
val onLongClick = rememberHapticFeedback(state = state, onAction = state.onItemLongClick)
combinedClickable(onClick = state.onItemClick, onLongClick = onLongClick)
}
is TokenItemState.NoAddress -> {
val onLongClick = rememberHapticFeedback(state = state, onAction = state.onItemLongClick)
combinedClickable(onClick = {}, onLongClick = onLongClick)
}
is TokenItemState.Draggable,
is TokenItemState.Loading,
is TokenItemState.Locked,
-> this
}
}
/**
* IMPORTANT! All margins that used between children setup like as children paddings.
*/
@Suppress("LongMethod")
@Composable
private fun CustomContainer(state: TokenItemState, modifier: Modifier = Modifier, content: @Composable () -> Unit) {
val density = LocalDensity.current
val dimens = TangemTheme.dimens
Layout(content = content, modifier = modifier) { measurables, constraints ->
val layoutWidth = constraints.maxWidth
val horizontalPadding = with(density) { dimens.size12.roundToPx() }
val verticalPadding = with(density) { dimens.size15.roundToPx() }
val layoutWidthWithoutPaddings = layoutWidth - 2 * horizontalPadding
val titleMinWidth = (layoutWidth * TITLE_MIN_WIDTH_COEFFICIENT).toInt()
val priceChangeMinWidth = (layoutWidth * PRICE_MIN_WIDTH_COEFFICIENT).toInt()
val icon = measurables.measure(layoutId = LayoutId.ICON, constraints = constraints)
/*
* Title width take the whole REMAINING space.
* If FiatAmount took the whole free space, then Title will have min width.
*/
val title: Placeable
// FiatAmount width must take the whole free space but is not greater the Title min size
var fiatAmount: Placeable? = null
// CryptoAmount width must take the whole free space but is not greater the PriceChange min size
var cryptoAmount: Placeable? = null
/*
* PriceChange width take the whole REMAINING space.
* If CryptoAmount took the whole free space, then PriceChange will have min width.
*/
val priceChange: Placeable?
val nonFiatContent = measurables.measure(layoutId = LayoutId.NON_FIAT_CONTENT, constraints = constraints)
var firstRowRemainingFreeSpace: Int? = null
var secondRowRemainingFreeSpace: Int? = null
when (state) {
is TokenItemState.Content,
is TokenItemState.Loading,
is TokenItemState.Locked,
-> {
fiatAmount = measurables.measureFiatAmount(
state = state,
maxWidth = layoutWidthWithoutPaddings - icon.width - titleMinWidth,
defaultConstraints = constraints,
)
cryptoAmount = measurables.measureCryptoAmount(
state = state,
maxWidth = layoutWidthWithoutPaddings - icon.width - priceChangeMinWidth,
defaultConstraints = constraints,
)
firstRowRemainingFreeSpace = layoutWidthWithoutPaddings - icon.width - fiatAmount.width
secondRowRemainingFreeSpace = layoutWidthWithoutPaddings - icon.width - cryptoAmount.width
}
is TokenItemState.Draggable -> {
cryptoAmount = measurables.measureCryptoAmount(
state = state,
maxWidth = layoutWidthWithoutPaddings - icon.width - nonFiatContent.width,
defaultConstraints = constraints,
)
firstRowRemainingFreeSpace = layoutWidthWithoutPaddings - icon.width - nonFiatContent.width
}
is TokenItemState.NoAddress,
is TokenItemState.Unreachable,
-> {
firstRowRemainingFreeSpace = layoutWidthWithoutPaddings - icon.width - nonFiatContent.width
}
}
title = measurables.measureTitle(
state = state,
minWidth = titleMinWidth,
remainingFreeSpace = firstRowRemainingFreeSpace,
defaultConstraints = constraints,
)
priceChange = secondRowRemainingFreeSpace?.let {
measurables.measurePriceChange(
state = state,
minWidth = priceChangeMinWidth,
remainingFreeSpace = secondRowRemainingFreeSpace,
defaultConstraints = constraints,
)
}
val layoutHeight = calculateLayoutHeight(
state = state,
minLayoutHeight = with(density) { dimens.size68.roundToPx() },
layoutPadding = verticalPadding,
title = title,
fiatAmount = fiatAmount,
cryptoAmount = cryptoAmount,
priceChange = priceChange,
)
layout(width = constraints.maxWidth, height = layoutHeight) {
icon.placeRelative(x = horizontalPadding, y = (layoutHeight - icon.height).div(other = 2))
title.placeRelative(
x = horizontalPadding + icon.width,
y = when (state) {
is TokenItemState.NoAddress,
is TokenItemState.Unreachable,
-> (layoutHeight - title.height).div(other = 2)
else -> verticalPadding
},
)
fiatAmount?.placeRelative(x = layoutWidth - fiatAmount.width - horizontalPadding, y = verticalPadding)
priceChange?.placeRelative(
x = horizontalPadding + icon.width,
y = layoutHeight - priceChange.height - verticalPadding,
)
cryptoAmount?.placeRelative(
x = when (state) {
is TokenItemState.Draggable -> horizontalPadding + icon.width
else -> layoutWidth - cryptoAmount.width - horizontalPadding
},
y = layoutHeight - cryptoAmount.height - verticalPadding,
)
nonFiatContent.placeRelative(
x = layoutWidth - nonFiatContent.width - horizontalPadding,
y = (layoutHeight - nonFiatContent.height).div(other = 2),
)
}
}
}
private fun List<Measurable>.measureFiatAmount(
state: TokenItemState,
maxWidth: Int,
defaultConstraints: Constraints,
): Placeable {
return measure(
layoutId = LayoutId.FIAT_AMOUNT,
constraints = when (state) {
is TokenItemState.Content,
is TokenItemState.Draggable,
-> createConstrainsSafely(maxWidth = maxWidth)
else -> defaultConstraints
},
)
}
private fun List<Measurable>.measureCryptoAmount(
state: TokenItemState,
maxWidth: Int,
defaultConstraints: Constraints,
): Placeable {
return measure(
layoutId = LayoutId.CRYPTO_AMOUNT,
constraints = when (state) {
is TokenItemState.Content,
is TokenItemState.Draggable,
-> createConstrainsSafely(maxWidth = maxWidth)
else -> defaultConstraints
},
)
}
private fun List<Measurable>.measureTitle(
state: TokenItemState,
minWidth: Int,
remainingFreeSpace: Int,
defaultConstraints: Constraints,
): Placeable {
return measure(
layoutId = LayoutId.TITLE,
constraints = when (state) {
is TokenItemState.Content,
is TokenItemState.Draggable,
is TokenItemState.NoAddress,
is TokenItemState.Unreachable,
-> createDynamicConstrains(minWidth = minWidth, remainingFreeSpace = remainingFreeSpace)
else -> defaultConstraints
},
)
}
private fun List<Measurable>.measurePriceChange(
state: TokenItemState,
minWidth: Int,
remainingFreeSpace: Int,
defaultConstraints: Constraints,
): Placeable {
return measure(
layoutId = LayoutId.CRYPTO_PRICE,
constraints = when (state) {
is TokenItemState.Content,
-> createDynamicConstrains(minWidth = minWidth, remainingFreeSpace = remainingFreeSpace)
else -> defaultConstraints
},
)
}
private fun List<Measurable>.measure(layoutId: LayoutId, constraints: Constraints): Placeable {
return requireNotNull(
value = firstOrNull { it.layoutId == layoutId },
lazyMessage = { "Measurables[$layoutId] is null" },
).measure(constraints)
}
private fun createDynamicConstrains(minWidth: Int, remainingFreeSpace: Int): Constraints {
return createConstrainsSafely(
minWidth = minWidth,
maxWidth = max(a = minWidth, b = remainingFreeSpace),
)
}
private fun createConstrainsSafely(
minWidth: Int = 0,
maxWidth: Int = Constraints.Infinity,
minHeight: Int = 0,
maxHeight: Int = Constraints.Infinity,
): Constraints {
return Constraints(
minWidth = minWidth.makeNotLessZero(),
maxWidth = maxWidth.makeNotLessZero(),
minHeight = minHeight.makeNotLessZero(),
maxHeight = maxHeight.makeNotLessZero(),
)
}
private fun Int.makeNotLessZero(): Int = max(a = 0, b = this)
@Suppress("LongParameterList")
private fun calculateLayoutHeight(
state: TokenItemState,
minLayoutHeight: Int,
layoutPadding: Int,
title: Placeable,
fiatAmount: Placeable?,
cryptoAmount: Placeable?,
priceChange: Placeable?,
): Int {
val firstColumnHeight: Int
val secondColumnHeight: Int
when (state) {
is TokenItemState.Content,
is TokenItemState.Loading,
is TokenItemState.Locked,
-> {
firstColumnHeight = 2 * layoutPadding + title.height + (cryptoAmount?.height ?: 0)
secondColumnHeight = 2 * layoutPadding + (fiatAmount?.height ?: 0) + (priceChange?.height ?: 0)
}
is TokenItemState.Draggable,
is TokenItemState.NoAddress,
is TokenItemState.Unreachable,
-> {
firstColumnHeight = minLayoutHeight
secondColumnHeight = minLayoutHeight
}
}
return max(firstColumnHeight, secondColumnHeight).coerceAtLeast(minLayoutHeight)
}
@Preview(widthDp = 360)
@Composable
private fun Preview_TokenItem_InLight(@PreviewParameter(TokenItemStateProvider::class) state: TokenItemState) {
TangemThemePreview(isDark = false) {
TokenItem(state = state, isBalanceHidden = false)
}
}
private class TokenItemStateProvider : CollectionPreviewParameterProvider<TokenItemState>(
collection = listOf(
WalletPreviewData.tokenItemVisibleState.copy(
iconState = WalletPreviewData.coinIconState.copy(showCustomBadge = true),
titleState = TokenItemState.TitleState.Content(
text = "PolygonPolygonPolygonPolygonPolygonPolygon",
hasPending = true,
),
fiatAmountState = TokenItemState.FiatAmountState.Content(
text = "3213123123321312312312312312 $",
hasStaked = true,
),
cryptoAmountState = TokenItemState.CryptoAmountState.Content(text = "5,4123123213123123123123123123 MATIC"),
cryptoPriceState = TokenItemState.CryptoPriceState.Content(
price = "312 USD",
priceChangePercent = "42.0%",
type = PriceChangeType.UP,
),
),
WalletPreviewData.tokenItemUnreachableState,
WalletPreviewData.tokenItemNoAddressState,
WalletPreviewData.tokenItemDragState,
WalletPreviewData.tokenItemHiddenState,
WalletPreviewData.loadingTokenItemState,
WalletPreviewData.testnetTokenItemVisibleState,
WalletPreviewData.customTokenItemVisibleState,
WalletPreviewData.customTestnetTokenItemVisibleState,
),
)

View file

@ -1,18 +0,0 @@
package com.tangem.feature.wallet.presentation.common.component.token
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.tangem.core.ui.res.TangemTheme
@Composable
internal fun LockedRectangle(modifier: Modifier = Modifier) {
Box(
modifier = modifier.background(
color = TangemTheme.colors.field.primary,
shape = RoundedCornerShape(TangemTheme.dimens.radius4),
),
)
}

View file

@ -1,76 +0,0 @@
package com.tangem.feature.wallet.presentation.common.component.token
import androidx.annotation.StringRes
import androidx.compose.animation.AnimatedContent
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.size
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.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import com.tangem.core.ui.res.TangemTheme
import com.tangem.feature.wallet.impl.R
import com.tangem.feature.wallet.presentation.common.state.TokenItemState
import org.burnoutcrew.reorderable.ReorderableLazyListState
import org.burnoutcrew.reorderable.detectReorder
@Composable
internal fun NonFiatContentBlock(
state: TokenItemState,
reorderableTokenListState: ReorderableLazyListState?,
modifier: Modifier = Modifier,
) {
AnimatedContent(
targetState = state,
label = "Update non content fiat block",
modifier = modifier,
contentKey = { it::class.java },
) { animatedState ->
when (animatedState) {
is TokenItemState.Draggable -> DraggableImage(reorderableTokenListState = reorderableTokenListState)
is TokenItemState.Unreachable -> NonFiatContentText(text = R.string.common_unreachable)
is TokenItemState.NoAddress -> NonFiatContentText(text = R.string.common_no_address)
is TokenItemState.Content,
is TokenItemState.Loading,
is TokenItemState.Locked,
-> Unit
}
}
}
@Composable
private fun DraggableImage(reorderableTokenListState: ReorderableLazyListState?) {
Box(
modifier = Modifier
.size(size = TangemTheme.dimens.size32)
.then(
other = if (reorderableTokenListState != null) {
Modifier.detectReorder(reorderableTokenListState)
} else {
Modifier
},
),
contentAlignment = Alignment.Center,
) {
Icon(
painter = painterResource(id = R.drawable.ic_drag_24),
tint = TangemTheme.colors.icon.informative,
contentDescription = null,
)
}
}
@Composable
private fun NonFiatContentText(@StringRes text: Int) {
Text(
text = stringResource(id = text),
color = TangemTheme.colors.text.tertiary,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
style = TangemTheme.typography.body2,
)
}

View file

@ -1,59 +0,0 @@
package com.tangem.feature.wallet.presentation.common.component.token
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.Modifier
import androidx.compose.ui.composed
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import com.tangem.core.ui.components.RectangleShimmer
import com.tangem.core.ui.res.TangemTheme
import com.tangem.feature.wallet.impl.R
import com.tangem.utils.StringsSigns
import com.tangem.feature.wallet.presentation.common.state.TokenItemState.CryptoAmountState as TokenCryptoAmountState
@Composable
internal fun TokenCryptoAmount(
state: TokenCryptoAmountState?,
isBalanceHidden: Boolean,
modifier: Modifier = Modifier,
) {
when (state) {
is TokenCryptoAmountState.Content -> {
CryptoAmountText(
amount = if (isBalanceHidden) StringsSigns.STARS else state.text,
modifier = modifier,
)
}
is TokenCryptoAmountState.Unreachable -> {
CryptoAmountText(amount = stringResource(id = R.string.common_unreachable), modifier = modifier)
}
is TokenCryptoAmountState.Loading -> {
RectangleShimmer(modifier = modifier.placeholderSize(), radius = TangemTheme.dimens.radius4)
}
is TokenCryptoAmountState.Locked -> {
LockedRectangle(modifier = modifier.placeholderSize())
}
null -> Unit
}
}
@Composable
private fun CryptoAmountText(amount: String, modifier: Modifier = Modifier) {
Text(
text = amount,
modifier = modifier,
color = TangemTheme.colors.text.tertiary,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
style = TangemTheme.typography.caption2,
)
}
private fun Modifier.placeholderSize(): Modifier = composed {
return@composed this
.padding(vertical = TangemTheme.dimens.spacing2)
.size(width = TangemTheme.dimens.size40, height = TangemTheme.dimens.size12)
}

View file

@ -1,75 +0,0 @@
package com.tangem.feature.wallet.presentation.common.component.token
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
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.composed
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.style.TextOverflow
import com.tangem.core.ui.components.RectangleShimmer
import com.tangem.core.ui.res.TangemTheme
import com.tangem.feature.wallet.impl.R
import com.tangem.utils.StringsSigns
import com.tangem.feature.wallet.presentation.common.state.TokenItemState.FiatAmountState as TokenFiatAmountState
@Composable
internal fun TokenFiatAmount(state: TokenFiatAmountState?, isBalanceHidden: Boolean, modifier: Modifier = Modifier) {
when (state) {
is TokenFiatAmountState.Content -> {
FiatAmountText(
text = if (isBalanceHidden) StringsSigns.STARS else state.text,
hasStaked = state.hasStaked,
modifier = modifier,
)
}
is TokenFiatAmountState.Loading -> {
RectangleShimmer(modifier = modifier.placeholderSize(), radius = TangemTheme.dimens.radius4)
}
is TokenFiatAmountState.Locked -> {
LockedRectangle(modifier = modifier.placeholderSize())
}
null -> Unit
}
}
@Composable
private fun FiatAmountText(text: String, hasStaked: Boolean, modifier: Modifier = Modifier) {
Row(
modifier = modifier,
verticalAlignment = Alignment.CenterVertically,
) {
AnimatedVisibility(
visible = hasStaked,
) {
Icon(
painter = rememberVectorPainter(image = ImageVector.vectorResource(R.drawable.ic_staking_24)),
contentDescription = null,
tint = TangemTheme.colors.icon.accent,
modifier = Modifier
.padding(horizontal = TangemTheme.dimens.spacing4)
.size(TangemTheme.dimens.size12),
)
}
Text(
text = text,
color = TangemTheme.colors.text.primary1,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
style = TangemTheme.typography.body2,
)
}
}
private fun Modifier.placeholderSize(): Modifier = composed {
return@composed this
.padding(vertical = TangemTheme.dimens.spacing4)
.size(width = TangemTheme.dimens.size40, height = TangemTheme.dimens.size12)
}

View file

@ -1,125 +0,0 @@
package com.tangem.feature.wallet.presentation.common.component.token
import androidx.compose.animation.AnimatedContent
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
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.composed
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextOverflow
import com.tangem.core.ui.components.RectangleShimmer
import com.tangem.core.ui.components.SpacerW4
import com.tangem.core.ui.components.SpacerW6
import com.tangem.core.ui.components.marketprice.PriceChangeType
import com.tangem.core.ui.res.TangemTheme
import com.tangem.feature.wallet.impl.R
import com.tangem.utils.StringsSigns.DASH_SIGN
import com.tangem.feature.wallet.presentation.common.state.TokenItemState.CryptoPriceState as TokenPriceChangeState
@Composable
internal fun TokenPrice(state: TokenPriceChangeState?, modifier: Modifier = Modifier) {
when (state) {
is TokenPriceChangeState.Content -> {
PriceBlock(
modifier = modifier,
price = state.price,
type = state.type,
priceChangePercent = state.priceChangePercent,
)
}
is TokenPriceChangeState.Unknown -> {
PriceText(text = DASH_SIGN, modifier = modifier)
}
is TokenPriceChangeState.Loading -> {
RectangleShimmer(modifier = modifier.placeholderSize(), radius = TangemTheme.dimens.radius4)
}
is TokenPriceChangeState.Locked -> {
LockedRectangle(modifier = modifier.placeholderSize())
}
null -> Unit
}
}
@Composable
private fun PriceBlock(
price: String,
modifier: Modifier = Modifier,
type: PriceChangeType? = null,
priceChangePercent: String? = null,
) {
Row(modifier = modifier, verticalAlignment = Alignment.CenterVertically) {
PriceText(text = price, modifier = Modifier.weight(weight = 1f, fill = false))
SpacerW6()
if (type != null) {
PriceChangeIcon(type = type)
SpacerW4()
}
PriceChangeText(type = type, text = priceChangePercent)
}
}
@Composable
private fun PriceText(text: String, modifier: Modifier = Modifier) {
AnimatedContent(targetState = text, label = "Update the price text", modifier = modifier) { animatedText ->
Text(
text = animatedText,
color = TangemTheme.colors.text.tertiary,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
style = TangemTheme.typography.caption2,
)
}
}
@Composable
private fun PriceChangeIcon(type: PriceChangeType) {
AnimatedContent(targetState = type, label = "Update the price change's arrow") { animatedType ->
Icon(
painter = painterResource(
id = when (animatedType) {
PriceChangeType.UP -> R.drawable.ic_arrow_up_8
PriceChangeType.DOWN -> R.drawable.ic_arrow_down_8
PriceChangeType.NEUTRAL -> R.drawable.ic_elipse_8
},
),
tint = when (animatedType) {
PriceChangeType.UP -> TangemTheme.colors.icon.accent
PriceChangeType.DOWN -> TangemTheme.colors.icon.warning
PriceChangeType.NEUTRAL -> TangemTheme.colors.icon.inactive
},
contentDescription = null,
)
}
}
@Composable
private fun PriceChangeText(type: PriceChangeType?, text: String?, modifier: Modifier = Modifier) {
AnimatedContent(targetState = text, modifier = modifier, label = "Update the price change's text") { animatedText ->
Text(
text = animatedText ?: DASH_SIGN,
color = when (type) {
PriceChangeType.UP -> TangemTheme.colors.text.accent
PriceChangeType.DOWN -> TangemTheme.colors.text.warning
PriceChangeType.NEUTRAL -> TangemTheme.colors.text.disabled
null -> TangemTheme.colors.text.tertiary
},
overflow = TextOverflow.Ellipsis,
maxLines = 1,
style = TangemTheme.typography.caption2,
)
}
}
private fun Modifier.placeholderSize(): Modifier = composed {
return@composed this
.padding(vertical = TangemTheme.dimens.spacing2)
.size(width = TangemTheme.dimens.size52, height = TangemTheme.dimens.size12)
}

View file

@ -1,83 +0,0 @@
package com.tangem.feature.wallet.presentation.common.component.token
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
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.composed
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextOverflow
import com.tangem.core.ui.components.RectangleShimmer
import com.tangem.core.ui.res.TangemTheme
import com.tangem.feature.wallet.impl.R
import com.tangem.feature.wallet.presentation.common.state.TokenItemState.TitleState as TokenTitleState
@Composable
internal fun TokenTitle(state: TokenTitleState?, modifier: Modifier = Modifier) {
when (state) {
is TokenTitleState.Content -> {
ContentTitle(name = state.text, hasPending = state.hasPending, modifier = modifier)
}
is TokenTitleState.Loading -> {
RectangleShimmer(modifier = modifier.placeholderSize(), radius = TangemTheme.dimens.radius4)
}
is TokenTitleState.Locked -> {
LockedRectangle(modifier = modifier.placeholderSize())
}
null -> Unit
}
}
@Composable
private fun ContentTitle(name: String, hasPending: Boolean, modifier: Modifier = Modifier) {
Row(
modifier = modifier,
horizontalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing6),
verticalAlignment = Alignment.CenterVertically,
) {
/*
* If currency name has a long width, then it will completely displace the image.
* So we need to use [weight] to avoid displacement.
*/
CurrencyNameText(name = name, modifier = Modifier.weight(weight = 1f, fill = false))
PendingTransactionImage(
hasPending = hasPending,
modifier = Modifier.align(alignment = Alignment.CenterVertically),
)
}
}
@Composable
private fun CurrencyNameText(name: String, modifier: Modifier = Modifier) {
Text(
text = name,
modifier = modifier,
color = TangemTheme.colors.text.primary1,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
style = TangemTheme.typography.subtitle2,
)
}
@Composable
private fun PendingTransactionImage(hasPending: Boolean, modifier: Modifier = Modifier) {
AnimatedVisibility(visible = hasPending, modifier = modifier) {
Image(
painter = painterResource(id = R.drawable.img_loader_15),
contentDescription = null,
)
}
}
private fun Modifier.placeholderSize(): Modifier = composed {
return@composed this
.padding(vertical = TangemTheme.dimens.spacing4)
.size(width = TangemTheme.dimens.size70, height = TangemTheme.dimens.size12)
}

View file

@ -2,6 +2,7 @@ package com.tangem.feature.wallet.presentation.common.preview
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.core.ui.components.marketprice.PriceChangeType
import com.tangem.core.ui.components.token.state.TokenItemState
import com.tangem.core.ui.event.consumedEvent
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.stringReference
@ -9,7 +10,6 @@ import com.tangem.core.ui.utils.BigDecimalFormatter
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.feature.wallet.impl.R
import com.tangem.feature.wallet.presentation.common.WalletPreviewData.topBarConfig
import com.tangem.feature.wallet.presentation.common.state.TokenItemState
import com.tangem.feature.wallet.presentation.wallet.state.model.*
import kotlinx.collections.immutable.persistentListOf
@ -20,7 +20,7 @@ internal object WalletScreenPreviewData {
titleState = TokenItemState.TitleState.Content(text = "Bitcoin"),
fiatAmountState = TokenItemState.FiatAmountState.Content(text = "12 368,14 \$"),
cryptoAmountState = TokenItemState.CryptoAmountState.Content(text = "0,35853044 BTC"),
cryptoPriceState = TokenItemState.CryptoPriceState.Content(
subtitleState = TokenItemState.SubtitleState.CryptoPriceContent(
price = "34 496,75 \$",
priceChangePercent = "0,43 %",
type = PriceChangeType.DOWN,
@ -46,7 +46,7 @@ internal object WalletScreenPreviewData {
titleState = TokenItemState.TitleState.Content(text = "Ethereum"),
fiatAmountState = TokenItemState.FiatAmountState.Content(text = "3 340,79 \$"),
cryptoAmountState = TokenItemState.CryptoAmountState.Content(text = "1,856660295 ETH"),
cryptoPriceState = TokenItemState.CryptoPriceState.Content(
subtitleState = TokenItemState.SubtitleState.CryptoPriceContent(
price = "1 799,41 \$",
priceChangePercent = "5,16 %",
type = PriceChangeType.UP,
@ -68,7 +68,7 @@ internal object WalletScreenPreviewData {
titleState = TokenItemState.TitleState.Content(text = "Shiba Inu"),
fiatAmountState = TokenItemState.FiatAmountState.Content(text = "48,64 \$"),
cryptoAmountState = TokenItemState.CryptoAmountState.Content(text = "6 200 220,00 SHIB"),
cryptoPriceState = TokenItemState.CryptoPriceState.Content(
subtitleState = TokenItemState.SubtitleState.CryptoPriceContent(
price = "0.01 \$",
priceChangePercent = "1,34 %",
type = PriceChangeType.DOWN,

View file

@ -1,167 +0,0 @@
package com.tangem.feature.wallet.presentation.common.state
import androidx.compose.runtime.Immutable
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.core.ui.components.marketprice.PriceChangeType
/** Token item state */
@Immutable
internal sealed class TokenItemState {
abstract val id: String
abstract val iconState: CurrencyIconState
abstract val titleState: TitleState
abstract val fiatAmountState: FiatAmountState?
abstract val cryptoAmountState: CryptoAmountState?
abstract val cryptoPriceState: CryptoPriceState?
/** Loading token state */
data class Loading(
override val id: String,
override val iconState: CurrencyIconState,
override val titleState: TitleState.Content,
) : TokenItemState() {
override val fiatAmountState: FiatAmountState = FiatAmountState.Loading
override val cryptoAmountState: CryptoAmountState = CryptoAmountState.Loading
override val cryptoPriceState: CryptoPriceState = CryptoPriceState.Loading
}
/** Locked token state */
data class Locked(override val id: String) : TokenItemState() {
override val iconState: CurrencyIconState = CurrencyIconState.Locked
override val titleState: TitleState = TitleState.Locked
override val fiatAmountState: FiatAmountState = FiatAmountState.Locked
override val cryptoAmountState: CryptoAmountState = CryptoAmountState.Locked
override val cryptoPriceState: CryptoPriceState = CryptoPriceState.Locked
}
/**
* Content token state
*
* @property id unique id
* @property iconState token icon state
* @property titleState token name
* @property onItemClick callback which will be called when an item is clicked
* @property onItemLongClick callback which will be called when an item is long clicked
*/
data class Content(
override val id: String,
override val iconState: CurrencyIconState,
override val titleState: TitleState,
override val fiatAmountState: FiatAmountState,
override val cryptoAmountState: CryptoAmountState.Content,
override val cryptoPriceState: CryptoPriceState,
val onItemClick: () -> Unit,
val onItemLongClick: () -> Unit,
) : TokenItemState()
/**
* Draggable token state
*
* @property id unique id
* @property iconState token icon state
* @property titleState token name
*/
data class Draggable(
override val id: String,
override val iconState: CurrencyIconState,
override val titleState: TitleState,
override val cryptoAmountState: CryptoAmountState,
) : TokenItemState() {
override val fiatAmountState: FiatAmountState? = null
override val cryptoPriceState: CryptoPriceState? = null
}
/**
* Unreachable token state
*
* @property id token id
* @property iconState token icon state
* @property titleState token name
* @property onItemClick callback which will be called when an item is clicked
* @property onItemLongClick callback which will be called when an item is long clicked
*/
data class Unreachable(
override val id: String,
override val iconState: CurrencyIconState,
override val titleState: TitleState,
val onItemClick: () -> Unit,
val onItemLongClick: () -> Unit,
) : TokenItemState() {
override val fiatAmountState: FiatAmountState? = null
override val cryptoAmountState: CryptoAmountState? = null
override val cryptoPriceState: CryptoPriceState? = null
}
/**
* No derivation address state
*
* @property id token id
* @property iconState token icon state
* @property titleState token name
* @property onItemLongClick callback which will be called when an item is long clicked
*/
data class NoAddress(
override val id: String,
override val iconState: CurrencyIconState,
override val titleState: TitleState,
val onItemLongClick: () -> Unit,
) : TokenItemState() {
override val fiatAmountState: FiatAmountState? = null
override val cryptoAmountState: CryptoAmountState? = null
override val cryptoPriceState: CryptoPriceState? = null
}
@Immutable
sealed class TitleState {
data class Content(val text: String, val hasPending: Boolean = false) : TitleState()
object Loading : TitleState()
object Locked : TitleState()
}
@Immutable
sealed class FiatAmountState {
data class Content(
val text: String,
val hasStaked: Boolean = false,
) : FiatAmountState()
object Loading : FiatAmountState()
object Locked : FiatAmountState()
}
@Immutable
sealed class CryptoAmountState {
data class Content(val text: String) : CryptoAmountState()
object Unreachable : CryptoAmountState()
object Loading : CryptoAmountState()
object Locked : CryptoAmountState()
}
sealed class CryptoPriceState {
data class Content(
val price: String,
val priceChangePercent: String?,
val type: PriceChangeType?,
) : CryptoPriceState()
object Unknown : CryptoPriceState()
object Loading : CryptoPriceState()
object Locked : CryptoPriceState()
}
}

View file

@ -7,7 +7,6 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.AppBarDefaults
import androidx.compose.material3.FabPosition
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
@ -32,6 +31,7 @@ import com.tangem.core.ui.components.PrimaryButton
import com.tangem.core.ui.components.SecondaryButton
import com.tangem.core.ui.components.buttons.actions.ActionButtonConfig
import com.tangem.core.ui.components.buttons.actions.RoundedActionButton
import com.tangem.core.ui.components.token.TokenItem
import com.tangem.core.ui.event.EventEffect
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.res.TangemTheme
@ -40,7 +40,6 @@ import com.tangem.core.ui.utils.WindowInsetsZero
import com.tangem.feature.wallet.impl.R
import com.tangem.feature.wallet.presentation.common.WalletPreviewData
import com.tangem.feature.wallet.presentation.common.component.DraggableNetworkGroupItem
import com.tangem.feature.wallet.presentation.common.component.TokenItem
import com.tangem.feature.wallet.presentation.organizetokens.model.DraggableItem
import com.tangem.feature.wallet.presentation.organizetokens.model.OrganizeTokensListState
import com.tangem.feature.wallet.presentation.organizetokens.model.OrganizeTokensState
@ -181,7 +180,7 @@ private fun LazyItemScope.DraggableItem(
reorderableTokenListState = reorderableState,
)
is DraggableItem.Token -> TokenItem(
modifier = itemModifier,
modifier = itemModifier.background(color = TangemTheme.colors.background.primary),
state = item.tokenItemState,
reorderableTokenListState = reorderableState,
isBalanceHidden = isBalanceHidden,
@ -212,7 +211,7 @@ private fun TopBar(
}
}
val elevation by animateDpAsState(
targetValue = if (isElevationEnabled) AppBarDefaults.TopAppBarElevation else TangemTheme.dimens.elevation0,
targetValue = if (isElevationEnabled) TangemTheme.dimens.elevation4 else TangemTheme.dimens.elevation0,
label = "top_bar_shadow_elevation",
)

View file

@ -1,7 +1,7 @@
package com.tangem.feature.wallet.presentation.organizetokens.model
import androidx.compose.runtime.Immutable
import com.tangem.feature.wallet.presentation.common.state.TokenItemState
import com.tangem.core.ui.components.token.state.TokenItemState
import com.tangem.feature.wallet.presentation.organizetokens.model.DraggableItem.RoundingMode
/**

View file

@ -1,10 +1,10 @@
package com.tangem.feature.wallet.presentation.organizetokens.utils.converter.items
import com.tangem.core.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter
import com.tangem.core.ui.components.token.state.TokenItemState
import com.tangem.core.ui.utils.BigDecimalFormatter
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.feature.wallet.presentation.common.state.TokenItemState
import com.tangem.feature.wallet.presentation.organizetokens.model.DraggableItem
import com.tangem.feature.wallet.presentation.organizetokens.utils.common.getGroupHeaderId
import com.tangem.feature.wallet.presentation.organizetokens.utils.common.getTokenItemId

View file

@ -25,7 +25,7 @@ import com.tangem.feature.wallet.presentation.organizetokens.OrganizeTokensScree
import com.tangem.feature.wallet.presentation.organizetokens.OrganizeTokensViewModel
import com.tangem.feature.wallet.presentation.wallet.ui.WalletScreen
import com.tangem.feature.wallet.presentation.wallet.viewmodels.WalletViewModel
import com.tangem.features.markets.component.MarketsEntryComponent
import com.tangem.features.markets.entry.MarketsEntryComponent
import kotlin.properties.Delegates
/** Default implementation of wallet feature router */
@ -139,8 +139,8 @@ internal class DefaultWalletRouter(
return router.stack.lastOrNull() is AppRoute.Wallet
}
override fun openManageTokensScreen() {
router.push(AppRoute.ManageTokens(readOnlyContent = false))
override fun openManageTokensScreen(userWalletId: UserWalletId) {
router.push(AppRoute.ManageTokens(userWalletId = userWalletId))
}
override fun openScanFailedDialog(onTryAgain: () -> Unit) {

View file

@ -4,7 +4,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.features.markets.component.MarketsEntryComponent
import com.tangem.features.markets.entry.MarketsEntryComponent
import com.tangem.features.wallet.navigation.WalletRouter
/**
@ -54,7 +54,7 @@ internal interface InnerWalletRouter : WalletRouter {
fun isWalletLastScreen(): Boolean
/** Open manage tokens screen */
fun openManageTokensScreen()
fun openManageTokensScreen(userWalletId: UserWalletId)
/** Open scan failed dialog */
fun openScanFailedDialog(onTryAgain: () -> Unit)

View file

@ -0,0 +1,31 @@
package com.tangem.feature.wallet.presentation.wallet.domain
import androidx.annotation.DrawableRes
import com.tangem.blockchain.common.Blockchain
import com.tangem.feature.wallet.impl.R
/**
* Note image model
*
* @property blockchain blockchain
* @property imageResId image res id
*
[REDACTED_AUTHOR]
*/
internal enum class NoteImage(
val blockchain: Blockchain,
@DrawableRes val imageResId: Int,
) {
Bitcoin(blockchain = Blockchain.Bitcoin, imageResId = R.drawable.ill_note_btc_120_106),
Ethereum(blockchain = Blockchain.Ethereum, imageResId = R.drawable.ill_note_ethereum_120_106),
Binance(blockchain = Blockchain.BSC, imageResId = R.drawable.ill_note_binance_120_106),
Dogecoin(blockchain = Blockchain.Dogecoin, imageResId = R.drawable.ill_note_doge_120_106),
Cardano(blockchain = Blockchain.Cardano, imageResId = R.drawable.ill_note_cardano_120_106),
XRP(blockchain = Blockchain.XRP, imageResId = R.drawable.ill_note_xrp_120_106),
}

View file

@ -1,18 +0,0 @@
package com.tangem.feature.wallet.presentation.wallet.domain
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.wallets.models.UserWallet
fun UserWallet.getCardsCount(): Int? {
return if (isMultiCurrency) {
when (val status = scanResponse.card.backupStatus) {
is CardDTO.BackupStatus.Active -> status.cardCount + 1
is CardDTO.BackupStatus.NoBackup,
is CardDTO.BackupStatus.CardLinked,
-> 1
null -> 1 // Multi-currency wallet without backup function. Example, 4.12
}
} else {
null
}
}

View file

@ -0,0 +1,161 @@
package com.tangem.feature.wallet.presentation.wallet.domain
import androidx.annotation.DrawableRes
import com.tangem.feature.wallet.impl.R
/**
* Wallet2 cobrand image.
* To integrate a new cobrand, just implement new enum object and that all.
*
* @property cards2ResId image resource id for wallet set with 2 cards
* @property cards3ResId image resource id for wallet set with 3 cards
* @property batchIds set of unique batch ids for this cobrand
*
[REDACTED_AUTHOR]
*/
internal enum class Wallet2CobrandImage(
@DrawableRes val cards2ResId: Int,
@DrawableRes val cards3ResId: Int,
val batchIds: Set<String>,
) {
Avrora(
cards2ResId = R.drawable.ill_avrora_card2_120_106,
cards3ResId = R.drawable.ill_avrora_card3_120_106,
batchIds = setOf("AF18"),
),
BabyDoge(
cards2ResId = R.drawable.ill_baby_doge_card2_120_106,
cards3ResId = R.drawable.ill_baby_doge_card3_120_106,
batchIds = setOf("AF51"),
),
Bad(
cards2ResId = R.drawable.ill_bad_card2_120_106,
cards3ResId = R.drawable.ill_bad_card3_120_106,
batchIds = setOf("AF09"),
),
BitcoinPizzaDay(
cards2ResId = R.drawable.ill_pizza_day_card2_120_106,
cards3ResId = R.drawable.ill_pizza_day_card3_120_106,
batchIds = setOf("AF33"),
),
CoinMetrica(
cards2ResId = R.drawable.ill_coin_metrica_card2_120_106,
cards3ResId = R.drawable.ill_coin_metrica_card3_120_106,
batchIds = setOf("AF27"),
),
COQ(
cards2ResId = R.drawable.ill_coq_card2_120_106,
cards3ResId = R.drawable.ill_coq_card3_120_106,
batchIds = setOf("AF28"),
),
CryptoSeth(
cards2ResId = R.drawable.ill_crypto_seth_card2_120_106,
cards3ResId = R.drawable.ill_crypto_seth_card3_120_106,
batchIds = setOf("AF32"),
),
Grim(
cards2ResId = R.drawable.ill_grim_card2_120_106,
cards3ResId = R.drawable.ill_grim_card3_120_106,
batchIds = setOf("AF13"),
),
Jr(
cards2ResId = R.drawable.ill_jr_card2_120_106,
cards3ResId = R.drawable.ill_jr_card3_120_106,
batchIds = setOf("AF14"),
),
Kaspa(
cards2ResId = R.drawable.ill_kaspa_card2_120_106,
cards3ResId = R.drawable.ill_kaspa_card3_120_106,
batchIds = setOf("AF08"),
),
Kaspa2(
cards2ResId = R.drawable.ill_kaspa2_card2_120_106,
cards3ResId = R.drawable.ill_kaspa2_card3_120_106,
batchIds = setOf("AF25"),
),
KaspaReseller(
cards2ResId = R.drawable.ill_kaspa_reseller_card2_120_106,
cards3ResId = R.drawable.ill_kaspa_reseller_card3_120_106,
batchIds = setOf("AF31"),
),
KishuInu(
cards2ResId = R.drawable.ill_kishu_inu_card2_120_106,
cards3ResId = R.drawable.ill_kishu_inu_card3_120_106,
batchIds = setOf("AF52"),
),
NewWorldElite(
cards2ResId = R.drawable.ill_nwe_card2_120_106,
cards3ResId = R.drawable.ill_nwe_card3_120_106,
batchIds = setOf("AF26"),
),
// for multicolored cards use image of 3 cards in all cases
Pastel(
cards2ResId = R.drawable.ill_pastel_cards3_120_106,
cards3ResId = R.drawable.ill_pastel_cards3_120_106,
batchIds = setOf("AF43", "AF44", "AF45"),
),
RedPanda(
cards2ResId = R.drawable.ill_red_panda_card2_120_106,
cards3ResId = R.drawable.ill_red_panda_card3_120_106,
batchIds = setOf("AF34"),
),
SatoshiFriends(
cards2ResId = R.drawable.ill_satoshi_card2_120_106,
cards3ResId = R.drawable.ill_satoshi_card3_120_106,
batchIds = setOf("AF19"),
),
Trillant(
cards2ResId = R.drawable.ill_trillant_card2_120_106,
cards3ResId = R.drawable.ill_trillant_card3_120_106,
batchIds = setOf("AF16"),
),
Tron(
cards2ResId = R.drawable.ill_tron_card2_120_106,
cards3ResId = R.drawable.ill_tron_card3_120_106,
batchIds = setOf("AF07"),
),
VeChain(
cards2ResId = R.drawable.ill_vechain_card2_120_106,
cards3ResId = R.drawable.ill_vechain_card3_120_106,
batchIds = setOf("AF29"),
),
// for multicolored cards use image of 3 cards in all cases
Vivid(
cards2ResId = R.drawable.ill_vivid_cards3_120_106,
cards3ResId = R.drawable.ill_vivid_cards3_120_106,
batchIds = setOf("AF40", "AF41", "AF42"),
),
VoltInu(
cards2ResId = R.drawable.ill_volt_inu_card2_120_106,
cards3ResId = R.drawable.ill_volt_inu_card3_120_106,
batchIds = setOf("AF35"),
),
WhiteTangem(
cards2ResId = R.drawable.ill_white_card2_120_106,
cards3ResId = R.drawable.ill_white_card3_120_106,
batchIds = setOf("AF15"),
),
}

View file

@ -6,6 +6,7 @@ import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.core.ui.utils.BigDecimalFormatter
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.common.util.getCardsCount
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.feature.wallet.impl.R
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletAdditionalInfo

View file

@ -1,8 +1,8 @@
package com.tangem.feature.wallet.presentation.wallet.domain
import androidx.annotation.DrawableRes
import com.tangem.blockchain.common.Blockchain
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.common.util.getCardsCount
import com.tangem.domain.demo.DemoConfig
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.feature.wallet.impl.R
@ -12,7 +12,6 @@ import com.tangem.feature.wallet.impl.R
*
[REDACTED_AUTHOR]
*/
// TODO: make flexible to integrate cobrands ([REDACTED_JIRA])
internal object WalletImageResolver {
private const val WALLET_WITHOUT_BACKUP_COUNT = 1
@ -24,42 +23,42 @@ internal object WalletImageResolver {
@DrawableRes
fun resolve(userWallet: UserWallet): Int? {
val cardTypesResolver = userWallet.scanResponse.cardTypesResolver
val cobrandImage = Wallet2CobrandImage.entries.firstOrNull {
it.batchIds.contains(userWallet.scanResponse.card.batchId)
}
val noteImage by lazy {
val noteBlockchain = cardTypesResolver.getBlockchain()
NoteImage.entries.firstOrNull { it.blockchain == noteBlockchain }
}
return when {
cardTypesResolver.isDevKit() -> R.drawable.ill_dev_120_106
cardTypesResolver.isWhiteWallet2() -> userWallet.resolveWhiteWallet2()
cardTypesResolver.isAvroraWallet() -> userWallet.resolveAvroraWallet()
cardTypesResolver.isTraillantWallet() -> userWallet.resolveTraillantWallet()
cardTypesResolver.isTronWallet() -> userWallet.resolveTronWallet()
cardTypesResolver.isKaspaWallet() -> userWallet.resolveKaspaWallet()
cardTypesResolver.isKaspa2Wallet() -> userWallet.resolveKaspa2Wallet()
cardTypesResolver.isKaspaResellerWallet() -> userWallet.resolveKaspaResellerWallet()
cardTypesResolver.isBadWallet() -> userWallet.resolveBadWallet()
cardTypesResolver.isJrWallet() -> userWallet.resolveJrWallet()
cardTypesResolver.isGrimWallet() -> userWallet.resolveGrimWallet()
cardTypesResolver.isSatoshiFriendsWallet() -> userWallet.resolveSatoshiWallet()
cardTypesResolver.isBitcoinPizzaDayWallet() -> userWallet.resolveBitcoinPizzaDayWallet()
cardTypesResolver.isVeChainWallet() -> userWallet.resolveVeChainWallet()
cardTypesResolver.isNewWorldEliteWallet() -> userWallet.resolveNewWorldEliteWallet()
cardTypesResolver.isRedPandaWallet() -> userWallet.resolveRedPandaWallet()
cardTypesResolver.isCryptoSethWallet() -> userWallet.resolveCryptoSethWallet()
cardTypesResolver.isKishuInuWallet() -> userWallet.resolveKishuInuWallet()
cardTypesResolver.isBabyDogeWallet() -> userWallet.resolveBabyDogeWallet()
cardTypesResolver.isCOQWallet() -> userWallet.resolveCOQWallet()
cardTypesResolver.isCoinMetricaWallet() -> userWallet.resolveCoinMetricaWallet()
cardTypesResolver.isVoltInuWallet() -> userWallet.resolveVoltInuWallet()
cardTypesResolver.isVividWallet() -> userWallet.resolveVividWallet()
cardTypesResolver.isPastelWallet() -> userWallet.resolvePastelWallet()
cobrandImage != null -> userWallet.resolveWallet2Cobrand(image = cobrandImage)
cardTypesResolver.isWallet2() -> userWallet.resolveWallet2()
cardTypesResolver.isShibaWallet() -> userWallet.resolveShibaWallet()
cardTypesResolver.isTangemWallet() -> userWallet.resolveWallet1()
cardTypesResolver.isWhiteWallet() -> R.drawable.ill_wallet_old_white_120_106
cardTypesResolver.isTangemTwins() -> R.drawable.ill_twins_120_106
cardTypesResolver.isStart2Coin() -> R.drawable.ill_start2coin_120_106
cardTypesResolver.isTangemNote() -> resolveNote(blockchain = cardTypesResolver.getBlockchain())
cardTypesResolver.isTangemNote() -> noteImage?.imageResId
else -> null
}
}
private fun UserWallet.resolveWallet2Cobrand(image: Wallet2CobrandImage): Int? {
return resolveWallet2(oneBackupResId = image.cards2ResId, twoBackupResId = image.cards3ResId)
}
private fun UserWallet.resolveShibaWallet(): Int? {
return resolveWallet2(
oneBackupResId = R.drawable.ill_shiba_card2_120_106,
twoBackupResId = R.drawable.ill_shiba_card3_120_106,
)
}
private fun UserWallet.resolveWallet2(
@DrawableRes oneBackupResId: Int = R.drawable.ill_wallet2_cards2_120_106,
@DrawableRes twoBackupResId: Int = R.drawable.ill_wallet2_cards3_120_106,
@ -75,176 +74,6 @@ internal object WalletImageResolver {
}
}
private fun UserWallet.resolveTronWallet(): Int? {
return resolveWallet2(
oneBackupResId = R.drawable.ill_tron_card2_120_106,
twoBackupResId = R.drawable.ill_tron_card3_120_106,
)
}
private fun UserWallet.resolveKaspaWallet(): Int? {
return resolveWallet2(
oneBackupResId = R.drawable.ill_kaspa_card2_120_106,
twoBackupResId = R.drawable.ill_kaspa_card3_120_106,
)
}
private fun UserWallet.resolveKaspa2Wallet(): Int? {
return resolveWallet2(
oneBackupResId = R.drawable.ill_kaspa2_card2_120_106,
twoBackupResId = R.drawable.ill_kaspa2_card3_120_106,
)
}
private fun UserWallet.resolveKaspaResellerWallet(): Int? {
return resolveWallet2(
oneBackupResId = R.drawable.ill_kaspa_reseller_card2_120_106,
twoBackupResId = R.drawable.ill_kaspa_reseller_card3_120_106,
)
}
private fun UserWallet.resolveBadWallet(): Int? {
return resolveWallet2(
oneBackupResId = R.drawable.ill_bad_card2_120_106,
twoBackupResId = R.drawable.ill_bad_card3_120_106,
)
}
private fun UserWallet.resolveJrWallet(): Int? {
return resolveWallet2(
oneBackupResId = R.drawable.ill_jr_card2_120_106,
twoBackupResId = R.drawable.ill_jr_card3_120_106,
)
}
private fun UserWallet.resolveGrimWallet(): Int? {
return resolveWallet2(
oneBackupResId = R.drawable.ill_grim_card2_120_106,
twoBackupResId = R.drawable.ill_grim_card3_120_106,
)
}
private fun UserWallet.resolveSatoshiWallet(): Int? {
return resolveWallet2(
oneBackupResId = R.drawable.ill_satoshi_card2_120_106,
twoBackupResId = R.drawable.ill_satoshi_card3_120_106,
)
}
private fun UserWallet.resolveShibaWallet(): Int? {
return resolveWallet2(
oneBackupResId = R.drawable.ill_shiba_card2_120_106,
twoBackupResId = R.drawable.ill_shiba_card3_120_106,
)
}
private fun UserWallet.resolveWhiteWallet2(): Int? {
return resolveWallet2(
oneBackupResId = R.drawable.ill_white_card2_120_106,
twoBackupResId = R.drawable.ill_white_card3_120_106,
)
}
private fun UserWallet.resolveAvroraWallet(): Int? {
return resolveWallet2(
oneBackupResId = R.drawable.ill_avrora_card2_120_106,
twoBackupResId = R.drawable.ill_avrora_card3_120_106,
)
}
private fun UserWallet.resolveTraillantWallet(): Int? {
return resolveWallet2(
oneBackupResId = R.drawable.ill_traillant_card2_120_106,
twoBackupResId = R.drawable.ill_traillant_card3_120_106,
)
}
private fun UserWallet.resolveBitcoinPizzaDayWallet(): Int? {
return resolveWallet2(
oneBackupResId = R.drawable.ill_pizza_day_card2_120_106,
twoBackupResId = R.drawable.ill_pizza_day_card3_120_106,
)
}
private fun UserWallet.resolveVeChainWallet(): Int? {
return resolveWallet2(
oneBackupResId = R.drawable.ill_vechain_card2_120_106,
twoBackupResId = R.drawable.ill_vechain_card3_120_106,
)
}
private fun UserWallet.resolveNewWorldEliteWallet(): Int? {
return resolveWallet2(
oneBackupResId = R.drawable.ill_nwe_card2_120_106,
twoBackupResId = R.drawable.ill_nwe_card3_120_106,
)
}
private fun UserWallet.resolveRedPandaWallet(): Int? {
return resolveWallet2(
oneBackupResId = R.drawable.ill_red_panda_card2_120_106,
twoBackupResId = R.drawable.ill_red_panda_card3_120_106,
)
}
private fun UserWallet.resolveCryptoSethWallet(): Int? {
return resolveWallet2(
oneBackupResId = R.drawable.ill_crypto_seth_card2_120_106,
twoBackupResId = R.drawable.ill_crypto_seth_card3_120_106,
)
}
private fun UserWallet.resolveKishuInuWallet(): Int? {
return resolveWallet2(
oneBackupResId = R.drawable.ill_kishu_inu_card2_120_106,
twoBackupResId = R.drawable.ill_kishu_inu_card3_120_106,
)
}
private fun UserWallet.resolveBabyDogeWallet(): Int? {
return resolveWallet2(
oneBackupResId = R.drawable.ill_baby_doge_card2_120_106,
twoBackupResId = R.drawable.ill_baby_doge_card3_120_106,
)
}
private fun UserWallet.resolveCOQWallet(): Int? {
return resolveWallet2(
oneBackupResId = R.drawable.ill_coq_card2_120_106,
twoBackupResId = R.drawable.ill_coq_card3_120_106,
)
}
private fun UserWallet.resolveCoinMetricaWallet(): Int? {
return resolveWallet2(
oneBackupResId = R.drawable.ill_coin_metrica_card2_120_106,
twoBackupResId = R.drawable.ill_coin_metrica_card3_120_106,
)
}
private fun UserWallet.resolveVoltInuWallet(): Int? {
return resolveWallet2(
oneBackupResId = R.drawable.ill_volt_inu_card2_120_106,
twoBackupResId = R.drawable.ill_volt_inu_card3_120_106,
)
}
private fun UserWallet.resolveVividWallet(): Int? {
// for multicolored cards use image of 3 cards in all cases
return resolveWallet2(
oneBackupResId = R.drawable.ill_vivid_cards3_120_106,
twoBackupResId = R.drawable.ill_vivid_cards3_120_106,
)
}
private fun UserWallet.resolvePastelWallet(): Int? {
// for multicolored cards use image of 3 cards in all cases
return resolveWallet2(
oneBackupResId = R.drawable.ill_pastel_cards3_120_106,
twoBackupResId = R.drawable.ill_pastel_cards3_120_106,
)
}
private fun UserWallet.resolveWallet1(): Int? {
return resolveWalletWithBackups { count ->
when (count) {
@ -261,16 +90,4 @@ internal object WalletImageResolver {
return if (count != null) resolve(count) else null
}
private fun resolveNote(blockchain: Blockchain): Int? {
return when (blockchain) {
Blockchain.Bitcoin -> R.drawable.ill_note_btc_120_106
Blockchain.Ethereum -> R.drawable.ill_note_ethereum_120_106
Blockchain.BSC -> R.drawable.ill_note_binance_120_106
Blockchain.Dogecoin -> R.drawable.ill_note_doge_120_106
Blockchain.Cardano -> R.drawable.ill_note_cardano_120_106
Blockchain.XRP -> R.drawable.ill_note_xrp_120_106
else -> null
}
}
}

View file

@ -1,9 +1,9 @@
package com.tangem.feature.wallet.presentation.wallet.state.model
import androidx.compose.runtime.Immutable
import com.tangem.core.ui.components.token.state.TokenItemState
import com.tangem.core.ui.extensions.TextReference
import com.tangem.feature.wallet.impl.R
import com.tangem.feature.wallet.presentation.common.state.TokenItemState
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf

View file

@ -4,9 +4,9 @@ import arrow.core.Either
import arrow.core.getOrElse
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.utils.BigDecimalFormatter
import com.tangem.domain.common.util.getCardsCount
import com.tangem.domain.visa.model.VisaCurrency
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.feature.wallet.presentation.wallet.domain.getCardsCount
import com.tangem.feature.wallet.presentation.wallet.state.model.BalancesAndLimitsBlockState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletAdditionalInfo
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletCardState

View file

@ -2,10 +2,10 @@ package com.tangem.feature.wallet.presentation.wallet.state.transformers
import com.tangem.core.ui.utils.BigDecimalFormatter
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.common.util.getCardsCount
import com.tangem.domain.tokens.error.TokenListError
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.feature.wallet.presentation.wallet.domain.WalletAdditionalInfoFactory
import com.tangem.feature.wallet.presentation.wallet.domain.getCardsCount
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletCardState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTokensListState

View file

@ -1,9 +1,9 @@
package com.tangem.feature.wallet.presentation.wallet.state.transformers
import com.tangem.domain.common.util.getCardsCount
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.feature.wallet.presentation.wallet.domain.WalletAdditionalInfoFactory
import com.tangem.feature.wallet.presentation.wallet.domain.WalletImageResolver
import com.tangem.feature.wallet.presentation.wallet.domain.getCardsCount
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletCardState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
import timber.log.Timber

View file

@ -2,10 +2,10 @@ package com.tangem.feature.wallet.presentation.wallet.state.transformers.convert
import com.tangem.core.ui.utils.BigDecimalFormatter
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.common.util.getCardsCount
import com.tangem.domain.tokens.model.TotalFiatBalance
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.feature.wallet.presentation.wallet.domain.WalletAdditionalInfoFactory
import com.tangem.feature.wallet.presentation.wallet.domain.getCardsCount
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletCardState
import com.tangem.utils.converter.Converter

View file

@ -62,7 +62,7 @@ internal class MultiWalletCurrencyActionsConverter(
is TokenActionsState.ActionState.Stake -> {
title = resourceReference(R.string.common_stake)
icon = R.drawable.ic_staking_24
action = { clickIntents.onStakeClick(cryptoCurrencyStatus) }
action = { clickIntents.onStakeClick(cryptoCurrencyStatus, actionsState.yield) }
}
is TokenActionsState.ActionState.Sell -> {
title = resourceReference(R.string.common_sell)
@ -77,7 +77,13 @@ internal class MultiWalletCurrencyActionsConverter(
is TokenActionsState.ActionState.Swap -> {
title = resourceReference(R.string.swapping_swap_action)
icon = R.drawable.ic_exchange_horizontal_24
action = { clickIntents.onSwapClick(cryptoCurrencyStatus, ScenarioUnavailabilityReason.None) }
action = {
clickIntents.onSwapClick(
cryptoCurrencyStatus = cryptoCurrencyStatus,
userWalletId = userWallet.walletId,
unavailabilityReason = ScenarioUnavailabilityReason.None,
)
}
}
is TokenActionsState.ActionState.CopyAddress -> {
title = resourceReference(R.string.common_copy_address)

View file

@ -2,10 +2,10 @@ package com.tangem.feature.wallet.presentation.wallet.state.transformers.convert
import com.tangem.core.ui.utils.BigDecimalFormatter
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.common.util.getCardsCount
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.feature.wallet.presentation.wallet.domain.WalletAdditionalInfoFactory
import com.tangem.feature.wallet.presentation.wallet.domain.getCardsCount
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletCardState
import com.tangem.utils.converter.Converter

View file

@ -1,130 +0,0 @@
package com.tangem.feature.wallet.presentation.wallet.state.transformers.converter
import com.tangem.common.extensions.isZero
import com.tangem.core.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter
import com.tangem.core.ui.components.marketprice.PriceChangeType
import com.tangem.core.ui.components.marketprice.utils.PriceChangeConverter
import com.tangem.core.ui.utils.BigDecimalFormatter
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.staking.model.stakekit.YieldBalance
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.feature.wallet.presentation.common.state.TokenItemState
import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents
import com.tangem.utils.Provider
import com.tangem.utils.StringsSigns.DASH_SIGN
import com.tangem.utils.converter.Converter
import com.tangem.utils.extensions.orZero
import java.math.BigDecimal
internal class TokenItemStateConverter(
private val appCurrencyProvider: Provider<AppCurrency>,
private val clickIntents: WalletClickIntents,
) : Converter<CryptoCurrencyStatus, TokenItemState> {
private val iconStateConverter by lazy(::CryptoCurrencyToIconStateConverter)
override fun convert(value: CryptoCurrencyStatus): TokenItemState {
return when (value.value) {
is CryptoCurrencyStatus.Loading -> value.mapToLoadingState()
is CryptoCurrencyStatus.Loaded,
is CryptoCurrencyStatus.Custom,
is CryptoCurrencyStatus.NoQuote,
is CryptoCurrencyStatus.NoAccount,
-> value.mapToTokenItemState()
is CryptoCurrencyStatus.MissedDerivation -> value.mapToNoAddressTokenItemState()
is CryptoCurrencyStatus.Unreachable,
is CryptoCurrencyStatus.NoAmount,
-> value.mapToUnreachableTokenItemState()
}
}
private fun CryptoCurrencyStatus.mapToLoadingState(): TokenItemState.Loading {
return TokenItemState.Loading(
id = currency.id.value,
iconState = iconStateConverter.convert(value = this),
titleState = TokenItemState.TitleState.Content(text = currency.name),
)
}
private fun CryptoCurrencyStatus.mapToTokenItemState(): TokenItemState.Content {
return TokenItemState.Content(
id = currency.id.value,
iconState = iconStateConverter.convert(value = this),
titleState = TokenItemState.TitleState.Content(
text = currency.name,
hasPending = value.hasCurrentNetworkTransactions,
),
fiatAmountState = TokenItemState.FiatAmountState.Content(
text = getFormattedFiatAmount(),
hasStaked = !getStakedBalance().isZero(),
),
cryptoAmountState = TokenItemState.CryptoAmountState.Content(text = getFormattedAmount()),
cryptoPriceState = getCryptoPriceState(),
onItemClick = { clickIntents.onTokenItemClick(this) },
onItemLongClick = { clickIntents.onTokenItemLongClick(cryptoCurrencyStatus = this) },
)
}
private fun CryptoCurrencyStatus.getFormattedAmount(): String {
val amount = value.amount?.plus(getStakedBalance()) ?: return DASH_SIGN
return BigDecimalFormatter.formatCryptoAmount(amount, currency.symbol, currency.decimals)
}
private fun CryptoCurrencyStatus.getFormattedFiatAmount(): String {
val fiatYieldBalance = value.fiatRate?.times(getStakedBalance()).orZero()
val fiatAmount = value.fiatAmount?.plus(fiatYieldBalance) ?: return DASH_SIGN
val appCurrency = appCurrencyProvider()
return BigDecimalFormatter.formatFiatAmount(fiatAmount, appCurrency.code, appCurrency.symbol)
}
private fun CryptoCurrencyStatus.getStakedBalance() =
(value.yieldBalance as? YieldBalance.Data)?.getTotalStakingBalance().orZero()
private fun CryptoCurrencyStatus.mapToUnreachableTokenItemState() = TokenItemState.Unreachable(
id = currency.id.value,
iconState = iconStateConverter.convert(value = this),
titleState = TokenItemState.TitleState.Content(text = currency.name),
onItemClick = { clickIntents.onTokenItemClick(this) },
onItemLongClick = { clickIntents.onTokenItemLongClick(cryptoCurrencyStatus = this) },
)
private fun CryptoCurrencyStatus.mapToNoAddressTokenItemState() = TokenItemState.NoAddress(
id = currency.id.value,
iconState = iconStateConverter.convert(this),
titleState = TokenItemState.TitleState.Content(text = currency.name),
onItemLongClick = { clickIntents.onTokenItemLongClick(cryptoCurrencyStatus = this) },
)
private fun CryptoCurrencyStatus.getCryptoPriceState(): TokenItemState.CryptoPriceState {
val fiatRate = value.fiatRate
val priceChange = value.priceChange
return if (fiatRate != null && priceChange != null) {
TokenItemState.CryptoPriceState.Content(
price = fiatRate.getFormattedCryptoPrice(),
priceChangePercent = BigDecimalFormatter.formatPercent(
percent = priceChange,
useAbsoluteValue = true,
),
type = priceChange.getPriceChangeType(),
)
} else {
TokenItemState.CryptoPriceState.Unknown
}
}
private fun BigDecimal.getFormattedCryptoPrice(): String {
val appCurrency = appCurrencyProvider()
return BigDecimalFormatter.formatFiatAmountUncapped(
fiatAmount = this,
fiatCurrencyCode = appCurrency.code,
fiatCurrencySymbol = appCurrency.symbol,
)
}
private fun BigDecimal.getPriceChangeType(): PriceChangeType {
return PriceChangeConverter.fromBigDecimal(value = this)
}
}

View file

@ -1,5 +1,6 @@
package com.tangem.feature.wallet.presentation.wallet.state.transformers.converter
import com.tangem.common.ui.tokens.TokenItemStateConverter
import com.tangem.core.ui.extensions.stringReference
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.common.util.cardTypesResolver
@ -11,7 +12,6 @@ import com.tangem.domain.wallets.models.UserWallet
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTokensListState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTokensListState.TokensListItemState
import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents
import com.tangem.utils.Provider
import com.tangem.utils.converter.Converter
import kotlinx.collections.immutable.PersistentList
import kotlinx.collections.immutable.mutate
@ -19,15 +19,16 @@ import kotlinx.collections.immutable.persistentListOf
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTokensListState.OrganizeTokensButtonConfig as WalletOrganizeTokensButtonConfig
internal class TokenListStateConverter(
appCurrency: AppCurrency,
private val tokenList: TokenList,
private val selectedWallet: UserWallet,
private val appCurrency: AppCurrency,
private val clickIntents: WalletClickIntents,
) : Converter<WalletTokensListState, WalletTokensListState> {
private val tokenStatusConverter = TokenItemStateConverter(
appCurrencyProvider = Provider { appCurrency },
clickIntents = clickIntents,
appCurrency = appCurrency,
onItemClick = clickIntents::onTokenItemClick,
onItemLongClick = clickIntents::onTokenItemLongClick,
)
override fun convert(value: WalletTokensListState): WalletTokensListState {

View file

@ -3,9 +3,9 @@ package com.tangem.feature.wallet.presentation.wallet.ui
import androidx.compose.runtime.*
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.TextFieldValue
import com.tangem.core.ui.components.AdditionalTextInputDialogParams
import com.tangem.core.ui.components.AdditionalTextInputDialogUM
import com.tangem.core.ui.components.BasicDialog
import com.tangem.core.ui.components.DialogButton
import com.tangem.core.ui.components.DialogButtonUM
import com.tangem.core.ui.components.TextInputDialog
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.feature.wallet.impl.R
@ -21,12 +21,12 @@ internal fun WalletAlert(state: WalletAlertState, onDismiss: () -> Unit) {
@Composable
private fun BasicAlert(state: WalletAlertState.Basic, onDismiss: () -> Unit) {
val confirmButton: DialogButton
val dismissButton: DialogButton?
val confirmButton: DialogButtonUM
val dismissButton: DialogButtonUM?
val onActionClick = state.onConfirmClick
if (onActionClick != null) {
confirmButton = DialogButton(
confirmButton = DialogButtonUM(
title = state.confirmButtonText.resolveReference(),
warning = state.isWarningConfirmButton,
onClick = {
@ -34,12 +34,12 @@ private fun BasicAlert(state: WalletAlertState.Basic, onDismiss: () -> Unit) {
onDismiss()
},
)
dismissButton = DialogButton(
dismissButton = DialogButtonUM(
title = stringResource(id = R.string.common_cancel),
onClick = onDismiss,
)
} else {
confirmButton = DialogButton(
confirmButton = DialogButtonUM(
title = state.confirmButtonText.resolveReference(),
warning = state.isWarningConfirmButton,
onClick = onDismiss,
@ -62,7 +62,7 @@ private fun TextInputAlert(state: WalletAlertState.TextInput, onDismiss: () -> U
TextInputDialog(
fieldValue = value,
confirmButton = DialogButton(
confirmButton = DialogButtonUM(
title = state.confirmButtonText.resolveReference(),
enabled = value.text.isNotEmpty() &&
value.text != state.text &&
@ -75,8 +75,8 @@ private fun TextInputAlert(state: WalletAlertState.TextInput, onDismiss: () -> U
onDismissDialog = onDismiss,
onValueChange = { value = it },
title = state.title.resolveReference(),
dismissButton = DialogButton(title = stringResource(id = R.string.common_cancel), onClick = onDismiss),
textFieldParams = AdditionalTextInputDialogParams(
dismissButton = DialogButtonUM(title = stringResource(id = R.string.common_cancel), onClick = onDismiss),
textFieldParams = AdditionalTextInputDialogUM(
label = state.label.resolveReference(),
isError = state.errorTextProvider(value.text) != null,
caption = state.errorTextProvider(value.text)?.resolveReference(),

View file

@ -74,10 +74,9 @@ import com.tangem.feature.wallet.presentation.wallet.ui.components.visa.VisaTxDe
import com.tangem.feature.wallet.presentation.wallet.ui.components.visa.balancesAndLimitsBlock
import com.tangem.feature.wallet.presentation.wallet.ui.components.visa.depositButton
import com.tangem.feature.wallet.presentation.wallet.ui.utils.changeWalletAnimator
import com.tangem.features.markets.component.BottomSheetState
import com.tangem.features.markets.component.BottomSheetState.COLLAPSED
import com.tangem.features.markets.component.BottomSheetState.EXPANDED
import com.tangem.features.markets.component.MarketsEntryComponent
import com.tangem.features.markets.entry.BottomSheetState
import com.tangem.features.markets.entry.BottomSheetState.*
import com.tangem.features.markets.entry.MarketsEntryComponent
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.launch

View file

@ -1,10 +1,12 @@
package com.tangem.feature.wallet.presentation.wallet.ui.components.multicurrency
import androidx.compose.foundation.background
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.tangem.core.ui.components.token.TokenItem
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.feature.wallet.presentation.common.component.NetworkGroupItem
import com.tangem.feature.wallet.presentation.common.component.TokenItem
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTokensListState.TokensListItemState
/**
@ -26,7 +28,11 @@ internal fun MultiCurrencyContentItem(
NetworkGroupItem(networkName = state.name.resolveReference(), modifier = modifier)
}
is TokensListItemState.Token -> {
TokenItem(state = state.state, isBalanceHidden = isBalanceHidden, modifier = modifier)
TokenItem(
state = state.state,
isBalanceHidden = isBalanceHidden,
modifier = modifier.background(color = TangemTheme.colors.background.primary),
)
}
}
}

View file

@ -1,10 +1,10 @@
package com.tangem.feature.wallet.presentation.wallet.viewmodels
import arrow.core.getOrElse
import com.tangem.domain.common.util.getCardsCount
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
import com.tangem.feature.wallet.presentation.wallet.domain.getCardsCount
import com.tangem.feature.wallet.presentation.wallet.state.model.NOT_INITIALIZED_WALLET_INDEX
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletCardState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletScreenState

View file

@ -90,7 +90,7 @@ internal class WalletContentClickIntentsImplementor @Inject constructor(
override fun onManageTokensClick() {
analyticsEventHandler.send(PortfolioEvent.ButtonManageTokens)
reduxStateHolder.dispatch(action = TokensAction.SetArgs.ManageAccess)
router.openManageTokensScreen()
router.openManageTokensScreen(userWalletId = stateHolder.getSelectedWalletId())
}
override fun onOrganizeTokensClick() {

View file

@ -1,7 +1,9 @@
package com.tangem.feature.wallet.presentation.wallet.viewmodels.intents
import arrow.core.getOrElse
import com.tangem.blockchain.common.address.AddressType
import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.AppRouter
import com.tangem.common.ui.tokens.getUnavailabilityReasonText
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.ui.clipboard.ClipboardManager
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
@ -12,7 +14,6 @@ import com.tangem.core.ui.components.bottomsheets.tokenreceive.mapToAddressModel
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.WrappedList
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.core.ui.haptic.TangemHapticEffect
import com.tangem.core.ui.haptic.VibratorHapticManager
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
@ -20,7 +21,7 @@ import com.tangem.domain.appcurrency.extenstions.unwrap
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.demo.IsDemoCardUseCase
import com.tangem.domain.redux.ReduxStateHolder
import com.tangem.domain.staking.GetYieldUseCase
import com.tangem.domain.staking.model.stakekit.Yield
import com.tangem.domain.tokens.*
import com.tangem.domain.tokens.legacy.TradeCryptoAction
import com.tangem.domain.tokens.model.CryptoCurrency
@ -57,11 +58,15 @@ interface WalletCurrencyActionsClickIntents {
fun onBuyClick(cryptoCurrencyStatus: CryptoCurrencyStatus, unavailabilityReason: ScenarioUnavailabilityReason)
fun onSwapClick(cryptoCurrencyStatus: CryptoCurrencyStatus, unavailabilityReason: ScenarioUnavailabilityReason)
fun onSwapClick(
cryptoCurrencyStatus: CryptoCurrencyStatus,
userWalletId: UserWalletId,
unavailabilityReason: ScenarioUnavailabilityReason,
)
fun onReceiveClick(cryptoCurrencyStatus: CryptoCurrencyStatus)
fun onStakeClick(cryptoCurrencyStatus: CryptoCurrencyStatus)
fun onStakeClick(cryptoCurrencyStatus: CryptoCurrencyStatus, yield: Yield?)
fun onCopyAddressLongClick(cryptoCurrencyStatus: CryptoCurrencyStatus): TextReference?
@ -94,7 +99,7 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
private val reduxStateHolder: ReduxStateHolder,
private val vibratorHapticManager: VibratorHapticManager,
private val clipboardManager: ClipboardManager,
private val getYieldUseCase: GetYieldUseCase,
private val appRouter: AppRouter,
) : BaseWalletClickIntents(), WalletCurrencyActionsClickIntents {
override fun onSendClick(
@ -223,19 +228,16 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
)
viewModelScope.launch(dispatchers.main) {
walletManagersFacade.getAddress(
walletManagersFacade.getDefaultAddress(
userWalletId = stateHolder.getSelectedWalletId(),
network = cryptoCurrencyStatus.currency.network,
)
.find { it.type == AddressType.Default }
?.value
?.let {
stateHolder.update(CloseBottomSheetTransformer(userWalletId = stateHolder.getSelectedWalletId()))
)?.let {
stateHolder.update(CloseBottomSheetTransformer(userWalletId = stateHolder.getSelectedWalletId()))
walletEventSender.send(
event = WalletEvent.CopyAddress(address = it),
)
}
walletEventSender.send(
event = WalletEvent.CopyAddress(address = it),
)
}
}
}
@ -355,6 +357,7 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
override fun onSwapClick(
cryptoCurrencyStatus: CryptoCurrencyStatus,
userWalletId: UserWalletId,
unavailabilityReason: ScenarioUnavailabilityReason,
) {
analyticsEventHandler.send(
@ -363,32 +366,31 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
if (handleUnavailabilityReason(unavailabilityReason)) return
reduxStateHolder.dispatch(TradeCryptoAction.Swap(cryptoCurrencyStatus.currency))
appRouter.push(
AppRoute.Swap(
currency = cryptoCurrencyStatus.currency,
userWalletId = userWalletId,
),
)
}
override fun onExploreClick() {
showErrorIfDemoModeOrElse(action = ::openExplorer)
}
override fun onStakeClick(cryptoCurrencyStatus: CryptoCurrencyStatus) {
override fun onStakeClick(cryptoCurrencyStatus: CryptoCurrencyStatus, yield: Yield?) {
val userWallet = getSelectedWalletSyncUseCase.unwrap() ?: return
stateHolder.update(CloseBottomSheetTransformer(userWalletId = userWallet.walletId))
viewModelScope.launch {
val userWalletId = stateHolder.getSelectedWalletId()
val cryptoCurrency = cryptoCurrencyStatus.currency
val yield = getYieldUseCase.invoke(
cryptoCurrencyId = cryptoCurrency.id,
symbol = cryptoCurrency.symbol,
).getOrElse {
error("Staking is unavailable for ${cryptoCurrency.name}")
}
reduxStateHolder.dispatch(
TradeCryptoAction.Stake(
appRouter.push(
AppRoute.Staking(
userWalletId = userWalletId,
cryptoCurrencyId = cryptoCurrency.id,
yield = yield,
yield = yield ?: return@launch,
),
)
}
@ -477,14 +479,12 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
private fun handleUnavailabilityReason(unavailabilityReason: ScenarioUnavailabilityReason): Boolean {
if (unavailabilityReason == ScenarioUnavailabilityReason.None) return false
val unavailabilityReasonText = getUnavailabilityReasonText(unavailabilityReason)
viewModelScope.launch(dispatchers.main) {
walletEventSender.send(
event = WalletEvent.ShowAlert(
state = WalletAlertState.DefaultAlert(
title = null,
message = unavailabilityReasonText,
message = unavailabilityReason.getUnavailabilityReasonText(),
onConfirmClick = null,
),
),
@ -493,66 +493,4 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
return true
}
private fun getUnavailabilityReasonText(unavailabilityReason: ScenarioUnavailabilityReason): TextReference {
return when (unavailabilityReason) {
is ScenarioUnavailabilityReason.StakingUnavailable -> {
resourceReference(
id = R.string.token_button_unavailability_reason_staking_unavailable,
formatArgs = wrappedList(unavailabilityReason.cryptoCurrencyName),
)
}
is ScenarioUnavailabilityReason.PendingTransaction -> {
when (unavailabilityReason.withdrawalScenario) {
ScenarioUnavailabilityReason.WithdrawalScenario.SEND -> resourceReference(
id = R.string.token_button_unavailability_reason_pending_transaction_send,
formatArgs = wrappedList(unavailabilityReason.networkName),
)
ScenarioUnavailabilityReason.WithdrawalScenario.SELL -> resourceReference(
id = R.string.token_button_unavailability_reason_pending_transaction_sell,
formatArgs = wrappedList(unavailabilityReason.networkName),
)
}
}
is ScenarioUnavailabilityReason.EmptyBalance -> {
when (unavailabilityReason.withdrawalScenario) {
ScenarioUnavailabilityReason.WithdrawalScenario.SEND -> resourceReference(
id = R.string.token_button_unavailability_reason_empty_balance_send,
)
ScenarioUnavailabilityReason.WithdrawalScenario.SELL -> resourceReference(
id = R.string.token_button_unavailability_reason_empty_balance_sell,
)
}
}
is ScenarioUnavailabilityReason.BuyUnavailable -> {
resourceReference(
id = R.string.token_button_unavailability_reason_buy_unavailable,
formatArgs = wrappedList(unavailabilityReason.cryptoCurrencyName),
)
}
is ScenarioUnavailabilityReason.NotExchangeable -> {
resourceReference(
id = R.string.token_button_unavailability_reason_not_exchangeable,
formatArgs = wrappedList(unavailabilityReason.cryptoCurrencyName),
)
}
is ScenarioUnavailabilityReason.NotSupportedBySellService -> {
resourceReference(
id = R.string.token_button_unavailability_reason_sell_unavailable,
formatArgs = wrappedList(unavailabilityReason.cryptoCurrencyName),
)
}
ScenarioUnavailabilityReason.Unreachable -> {
resourceReference(
id = R.string.token_button_unavailability_generic_description,
)
}
ScenarioUnavailabilityReason.UnassociatedAsset -> resourceReference(
id = R.string.warning_receive_blocked_hedera_token_association_required_message,
)
ScenarioUnavailabilityReason.None -> {
throw IllegalArgumentException("The unavailability reason must be other than None")
}
}
}
}

View file

@ -0,0 +1,26 @@
package com.tangem.feature.wallet.presentation.wallet.domain
import com.google.common.truth.Truth
import com.tangem.blockchain.common.Blockchain
import org.junit.Test
/**
[REDACTED_AUTHOR]
*/
internal class NoteImageTest {
@Test
fun check() {
// check uniqueness
Truth.assertThat(NoteImage.entries.map { it.blockchain }).containsNoDuplicates()
Truth.assertThat(NoteImage.entries.map { it.imageResId }).containsNoDuplicates()
// check blockchain matching
Truth.assertThat(NoteImage.Bitcoin.blockchain).isEqualTo(Blockchain.Bitcoin)
Truth.assertThat(NoteImage.Ethereum.blockchain).isEqualTo(Blockchain.Ethereum)
Truth.assertThat(NoteImage.Binance.blockchain).isEqualTo(Blockchain.BSC)
Truth.assertThat(NoteImage.Dogecoin.blockchain).isEqualTo(Blockchain.Dogecoin)
Truth.assertThat(NoteImage.Cardano.blockchain).isEqualTo(Blockchain.Cardano)
Truth.assertThat(NoteImage.XRP.blockchain).isEqualTo(Blockchain.XRP)
}
}

View file

@ -0,0 +1,22 @@
package com.tangem.feature.wallet.presentation.wallet.domain
import com.google.common.truth.Truth
import org.junit.Test
/**
[REDACTED_AUTHOR]
*/
internal class Wallet2CobrandImageTest {
@Test
fun checkUniqueness() {
val excluded = listOf(Wallet2CobrandImage.Pastel, Wallet2CobrandImage.Vivid)
(Wallet2CobrandImage.entries - excluded).forEach {
Truth.assertThat(it.cards2ResId != it.cards3ResId).isTrue()
}
Truth.assertThat(Wallet2CobrandImage.entries.map { it.cards2ResId }).containsNoDuplicates()
Truth.assertThat(Wallet2CobrandImage.entries.map { it.cards3ResId }).containsNoDuplicates()
Truth.assertThat(Wallet2CobrandImage.entries.flatMap { it.batchIds }).containsNoDuplicates()
}
}