Updated on 2026-08-14

This commit is contained in:
Tangem 2023-09-15 15:59:45 +08:00
parent ae2edc7fbf
commit 7cdf385873
16 changed files with 143 additions and 102 deletions

View file

@ -48,6 +48,12 @@ sealed interface TextReference {
* @see [TextReference.plus] method
*/
data class Combined(val refs: WrappedList<TextReference>) : TextReference
companion object {
/** Empty string as [TextReference] */
val EMPTY: TextReference by lazy(mode = LazyThreadSafetyMode.NONE) { Str(value = "") }
}
}
/**

View file

@ -25,8 +25,6 @@ interface CardTypesResolver {
fun getPrimaryToken(): Token?
fun getBackupCardsCount(): Int
fun isReleaseFirmwareType(): Boolean
fun getRemainingSignatures(): Int?

View file

@ -37,7 +37,7 @@ internal class TangemCardTypesResolver(
override fun isStart2Coin(): Boolean = card.isStart2Coin
override fun isDev(): Boolean = card.isTestCard
override fun isDev(): Boolean = card.firmwareVersion.type != FirmwareVersion.FirmwareType.Release
override fun isMultiwalletAllowed(): Boolean {
return !isTangemTwins() && !card.isStart2Coin && !isTangemNote() &&
@ -71,8 +71,6 @@ internal class TangemCardTypesResolver(
)
}
override fun getBackupCardsCount(): Int = card.wallets.size
override fun isReleaseFirmwareType(): Boolean = card.firmwareVersion.type == FirmwareVersion.FirmwareType.Release
override fun getRemainingSignatures(): Int? = card.wallets.firstOrNull()?.remainingSignatures

View file

@ -27,6 +27,10 @@ class UserWalletBuilder(
}
}
/**
* DANGEROUS!!!
* [backupCardsIds] will be non-empty list if card is backed up on current device.
*/
fun backupCardsIds(backupCardsIds: Set<String>?) = this.apply {
if (backupCardsIds != null) {
this.backupCardsIds = backupCardsIds

View file

@ -46,7 +46,7 @@ data class CardDTO(
isAccessCodeSet = card.isAccessCodeSet,
isPasscodeSet = card.isPasscodeSet,
supportedCurves = card.supportedCurves,
wallets = card.wallets.map { Wallet(it) },
wallets = card.wallets.map(::Wallet),
attestation = card.attestation,
backupStatus = BackupStatus.fromSdkStatus(card.backupStatus),
)
@ -234,6 +234,7 @@ data class CardDTO(
val hasBackup: Boolean,
val derivedKeys: Map<DerivationPath, ExtendedPublicKey>,
val extendedPublicKey: ExtendedPublicKey?,
val isImported: Boolean,
) {
constructor(wallet: CardWallet) : this(
publicKey = wallet.publicKey,
@ -246,8 +247,10 @@ data class CardDTO(
hasBackup = wallet.hasBackup,
derivedKeys = wallet.derivedKeys,
extendedPublicKey = wallet.extendedPublicKey,
isImported = wallet.isImported,
)
@Suppress("CyclomaticComplexMethod")
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is Wallet) return false
@ -256,13 +259,16 @@ data class CardDTO(
if (chainCode != null) {
if (other.chainCode == null) return false
if (!chainCode.contentEquals(other.chainCode)) return false
} else if (other.chainCode != null) return false
} else {
if (other.chainCode != null) return false
}
if (curve != other.curve) return false
if (settings != other.settings) return false
if (totalSignedHashes != other.totalSignedHashes) return false
if (remainingSignatures != other.remainingSignatures) return false
if (index != other.index) return false
if (hasBackup != other.hasBackup) return false
if (isImported != other.isImported) return false
return true
}
@ -276,6 +282,7 @@ data class CardDTO(
result = 31 * result + (remainingSignatures ?: 0)
result = 31 * result + index
result = 31 * result + hasBackup.hashCode()
result = 31 * result + isImported.hashCode()
return result
}
}

View file

@ -1,34 +1,37 @@
package com.tangem.domain.wallets.models
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.models.scan.ScanResponse
/**
* Represents user's wallet which stored in app persistence
* @param name User wallet name
* @param walletId User wallet [UserWalletId]
* @param artworkUrl User wallet card artwork URL
* @param cardsInWallet List of cards IDs assigned with this user's wallet
* @param isMultiCurrency Indicates whether this user wallet can work with more than one currency
* @param scanResponse [ScanResponse] of primary user's wallet card.
* TODO: Replace with [com.tangem.domain.common.CardDTO]
* @property cardId ID of user's wallet primary card
* @property hasAccessCode Indicates if the user's wallet primary card has access code
* @property isLocked Indicates if this primary card has no currency wallets
* */
*
* @property name User wallet name
* @property walletId User wallet [UserWalletId]
* @property artworkUrl User wallet card artwork URL
* @property cardsInWallet List of cards IDs assigned with this user's wallet. The list will be empty if the wallet
* has been backed up on another device.
* @property isMultiCurrency Indicates whether this user wallet can work with more than one currency
* @property scanResponse [ScanResponse] of primary user's wallet card.
*/
data class UserWallet(
val name: String,
val walletId: UserWalletId,
val artworkUrl: String,
val cardsInWallet: Set<String>,
val isMultiCurrency: Boolean,
val scanResponse: ScanResponse,
val scanResponse: ScanResponse, // TODO: Replace with [com.tangem.domain.models.scan.CardDTO]
) {
val cardId: String
get() = scanResponse.card.cardId
val hasAccessCode: Boolean
get() = scanResponse.card.isAccessCodeSet
/** ID of user's wallet primary card */
val cardId: String get() = scanResponse.card.cardId
val isLocked: Boolean
get() = scanResponse.card.wallets.isEmpty()
/** Indicates if the user's wallet primary card has access code */
val hasAccessCode: Boolean get() = scanResponse.card.isAccessCodeSet
/** Indicates if this primary card has no currency wallets */
val isLocked: Boolean get() = scanResponse.card.wallets.isEmpty()
/** Indicated if this primary card is imported */
val isImported: Boolean get() = scanResponse.card.wallets.any(CardDTO.Wallet::isImported)
}

View file

@ -0,0 +1,17 @@
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.CardLinked -> status.cardCount + 1
is CardDTO.BackupStatus.NoBackup -> 1
null -> 1 // Multi-currency wallet without backup function. Example, 4.12
}
} else {
null
}
}

View file

@ -2,9 +2,10 @@ package com.tangem.feature.wallet.presentation.wallet.domain
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.plus
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.CardTypesResolver
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.feature.wallet.impl.R
import java.math.BigDecimal
@ -16,55 +17,73 @@ import java.math.BigDecimal
*/
internal object WalletAdditionalInfoFactory {
private val DIVIDER_RES by lazy { TextReference.Str(value = "") }
private val DIVIDER by lazy(mode = LazyThreadSafetyMode.NONE) { stringReference(value = "") }
/**
* Get additional info
*
* @param cardTypesResolver card type resolver
* @param wallet current wallet
* @param currencyAmount amount of currency
*/
fun resolve(
cardTypesResolver: CardTypesResolver,
wallet: UserWallet,
currencyAmount: BigDecimal? = null,
): TextReference {
return if (cardTypesResolver.isMultiwalletAllowed()) {
resolveMultiCurrencyInfo(cardTypesResolver, wallet)
fun resolve(wallet: UserWallet, currencyAmount: BigDecimal? = null): TextReference {
return if (wallet.isMultiCurrency) {
wallet.resolveMultiCurrencyInfo()
} else {
resolveSingleCurrencyInfo(cardTypesResolver, wallet, currencyAmount)
wallet.resolveSingleCurrencyInfo(currencyAmount)
}
}
private fun resolveMultiCurrencyInfo(cardTypeResolver: CardTypesResolver, wallet: UserWallet): TextReference {
val backupCardsCount = wallet.cardsInWallet.size + 1
val backupInfoRes = TextReference.PluralRes(
id = R.plurals.card_label_card_count,
count = backupCardsCount,
formatArgs = wrappedList(backupCardsCount),
)
return if (wallet.isLocked) {
backupInfoRes + DIVIDER_RES + TextReference.Res(R.string.common_locked)
private fun UserWallet.resolveMultiCurrencyInfo(): TextReference {
return if (isLocked) {
getBackupInfoWithDivider(backupCardsCount = getCardsCount()) + TextReference.Res(R.string.common_locked)
} else {
val cardTypeResolver = scanResponse.cardTypesResolver
if (cardTypeResolver.isWallet2()) {
backupInfoRes + DIVIDER_RES + TextReference.Res(id = R.string.common_seed_phrase)
resolveWallet2Info()
} else {
backupInfoRes
getBackupInfo(backupCardsCount = getCardsCount())
}
}
}
private fun resolveSingleCurrencyInfo(
cardTypeResolver: CardTypesResolver,
wallet: UserWallet,
currencyAmount: BigDecimal?,
): TextReference {
return if (wallet.isLocked) {
private fun UserWallet.resolveWallet2Info(): TextReference {
return if (isImported) {
getBackupInfoWithDivider(backupCardsCount = getCardsCount()) +
TextReference.Res(id = R.string.common_seed_phrase)
} else {
getBackupInfo(backupCardsCount = getCardsCount())
}
}
private fun getBackupInfoWithDivider(backupCardsCount: Int?): TextReference {
return if (backupCardsCount != null) {
getBackupInfoTextReference(count = backupCardsCount) + DIVIDER
} else {
TextReference.EMPTY
}
}
private fun getBackupInfo(backupCardsCount: Int?): TextReference {
return if (backupCardsCount != null) {
getBackupInfoTextReference(count = backupCardsCount)
} else {
TextReference.EMPTY
}
}
private fun getBackupInfoTextReference(count: Int): TextReference {
return TextReference.PluralRes(
id = R.plurals.card_label_card_count,
count = count,
formatArgs = wrappedList(count),
)
}
private fun UserWallet.resolveSingleCurrencyInfo(currencyAmount: BigDecimal?): TextReference {
return if (isLocked) {
TextReference.Res(R.string.common_locked)
} else {
val blockchain = cardTypeResolver.getBlockchain()
val blockchain = scanResponse.cardTypesResolver.getBlockchain()
val amount = currencyAmount?.let {
BigDecimalFormatter.formatCryptoAmount(
cryptoAmount = it,

View file

@ -2,7 +2,8 @@ package com.tangem.feature.wallet.presentation.wallet.domain
import androidx.annotation.DrawableRes
import com.tangem.blockchain.common.Blockchain
import com.tangem.domain.common.CardTypesResolver
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.feature.wallet.impl.R
/**
@ -12,34 +13,41 @@ import com.tangem.feature.wallet.impl.R
*/
internal object WalletImageResolver {
private const val DOUBLE_WALLET_SET_BACKUP_COUNT = 1
private const val TRIPLE_WALLET_SET_BACKUP_COUNT = 2
private const val DOUBLE_WALLET_SET_BACKUP_COUNT = 2
private const val TRIPLE_WALLET_SET_BACKUP_COUNT = 3
/** Get image by [cardTypesResolver] */
/** Get a specified wallet [userWallet] image */
@DrawableRes
fun resolve(cardTypesResolver: CardTypesResolver): Int? {
fun resolve(userWallet: UserWallet): Int? {
val cardTypesResolver = userWallet.scanResponse.cardTypesResolver
return when {
cardTypesResolver.isWallet2() -> resolveWallet2(cardTypesResolver)
cardTypesResolver.isDev() -> R.drawable.ill_dev_120_106
cardTypesResolver.isWallet2() -> userWallet.resolveWallet2()
cardTypesResolver.isTangemWallet() -> R.drawable.ill_wallet_120_106
cardTypesResolver.isWhiteWallet() -> R.drawable.ill_old_wallet_120_106
cardTypesResolver.isTangemTwins() -> R.drawable.ill_twin_120_106
cardTypesResolver.isStart2Coin() -> R.drawable.ill_start2coin_120_106
cardTypesResolver.isTangemNote() -> resolveNote(cardTypesResolver)
cardTypesResolver.isDev() -> R.drawable.ill_dev_120_106
cardTypesResolver.isTangemNote() -> resolveNote(blockchain = cardTypesResolver.getBlockchain())
else -> null
}
}
private fun resolveWallet2(cardTypesResolver: CardTypesResolver): Int? {
return when (cardTypesResolver.getBackupCardsCount()) {
DOUBLE_WALLET_SET_BACKUP_COUNT -> R.drawable.ill_wallet2_cards2_120_106
TRIPLE_WALLET_SET_BACKUP_COUNT -> R.drawable.ill_wallet2_cards3_120_106
else -> null
private fun UserWallet.resolveWallet2(): Int? {
val count = getCardsCount()
return if (count != null) {
when (count) {
DOUBLE_WALLET_SET_BACKUP_COUNT -> R.drawable.ill_wallet2_cards2_120_106
TRIPLE_WALLET_SET_BACKUP_COUNT -> R.drawable.ill_wallet2_cards3_120_106
else -> null
}
} else {
null
}
}
private fun resolveNote(cardTypesResolver: CardTypesResolver): Int? {
return when (cardTypesResolver.getBlockchain()) {
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

View file

@ -3,7 +3,6 @@ package com.tangem.feature.wallet.presentation.wallet.state.factory
import arrow.core.Either
import com.tangem.common.Provider
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.common.CardTypesResolver
import com.tangem.domain.tokens.error.TokenListError
import com.tangem.domain.tokens.model.TokenList
import com.tangem.domain.wallets.models.UserWallet
@ -17,10 +16,11 @@ import com.tangem.utils.converter.Converter
/**
* Converter from loaded [TokenListError] or [TokenList] to [WalletMultiCurrencyState]
*
* @property currentStateProvider current ui state provider
* @param cardTypeResolverProvider card type resolver
* @param currentWalletProvider current wallet provider
* @param clickIntents screen click intents
* @property currentStateProvider current ui state provider
* @property tokenListErrorConverter converter of tokens list
* @param appCurrencyProvider app currency provider
* @param currentWalletProvider current wallet provider
* @param clickIntents screen click intents
*
[REDACTED_AUTHOR]
*/
@ -28,14 +28,12 @@ internal class WalletLoadedTokensListConverter(
private val currentStateProvider: Provider<WalletState>,
private val tokenListErrorConverter: TokenListErrorConverter,
appCurrencyProvider: Provider<AppCurrency>,
cardTypeResolverProvider: Provider<CardTypesResolver>,
currentWalletProvider: Provider<UserWallet>,
clickIntents: WalletClickIntents,
) : Converter<Either<TokenListError, TokenList>, WalletState> {
private val tokenListStateConverter = TokenListToWalletStateConverter(
currentStateProvider = currentStateProvider,
cardTypeResolverProvider = cardTypeResolverProvider,
currentWalletProvider = currentWalletProvider,
appCurrencyProvider = appCurrencyProvider,
isWalletContentHidden = false, // TODO: [REDACTED_JIRA]

View file

@ -6,7 +6,6 @@ import com.tangem.core.ui.components.marketprice.MarketPriceBlockState
import com.tangem.core.ui.components.marketprice.PriceChangeConfig
import com.tangem.core.ui.utils.BigDecimalFormatter
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.common.CardTypesResolver
import com.tangem.domain.tokens.error.CurrencyStatusError
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.wallets.models.UserWallet
@ -23,7 +22,6 @@ import java.math.BigDecimal
internal class WalletSingleCurrencyLoadedBalanceConverter(
private val currentStateProvider: Provider<WalletState>,
private val cardTypeResolverProvider: Provider<CardTypesResolver>,
private val appCurrencyProvider: Provider<AppCurrency>,
private val currentWalletProvider: Provider<UserWallet>,
private val currencyStatusErrorConverter: CurrencyStatusErrorConverter,
@ -90,7 +88,6 @@ internal class WalletSingleCurrencyLoadedBalanceConverter(
id = selectedWallet.id,
title = selectedWallet.title,
additionalInfo = WalletAdditionalInfoFactory.resolve(
cardTypesResolver = cardTypeResolverProvider(),
wallet = currentWalletProvider(),
currencyAmount = status.amount,
),

View file

@ -136,7 +136,7 @@ internal class WalletSkeletonStateConverter(
private fun UserWallet.createAdditionalInfo(): TextReference? {
return if (isMultiCurrency) {
WalletAdditionalInfoFactory.resolve(cardTypesResolver = scanResponse.cardTypesResolver, wallet = this)
WalletAdditionalInfoFactory.resolve(wallet = this)
} else {
null
}
@ -144,7 +144,7 @@ internal class WalletSkeletonStateConverter(
@DrawableRes
private fun UserWallet.createImageResId(): Int? {
return WalletImageResolver.resolve(cardTypesResolver = scanResponse.cardTypesResolver)
return WalletImageResolver.resolve(userWallet = this)
}
private fun createPullToRefreshConfig(): WalletPullToRefreshConfig {

View file

@ -66,9 +66,8 @@ internal class WalletStateFactory(
WalletLoadedTokensListConverter(
currentStateProvider = currentStateProvider,
tokenListErrorConverter = tokenListErrorConverter,
cardTypeResolverProvider = currentCardTypeResolverProvider,
currentWalletProvider = currentWalletProvider,
appCurrencyProvider = appCurrencyProvider,
currentWalletProvider = currentWalletProvider,
clickIntents = clickIntents,
)
}
@ -91,7 +90,6 @@ internal class WalletStateFactory(
private val singleCurrencyLoadedBalanceConverter by lazy {
WalletSingleCurrencyLoadedBalanceConverter(
currentStateProvider = currentStateProvider,
cardTypeResolverProvider = currentCardTypeResolverProvider,
appCurrencyProvider = appCurrencyProvider,
currentWalletProvider = currentWalletProvider,
currencyStatusErrorConverter = currencyStatusErrorConverter,
@ -186,7 +184,6 @@ internal class WalletStateFactory(
isBottomSheetShow = true,
onBottomSheetDismiss = clickIntents::onDismissBottomSheet,
)
else -> state
}
}

View file

@ -118,7 +118,7 @@ internal class WalletsUnlockStateConverter(
id = id,
title = title,
additionalInfo = userWallet.createAdditionalInfo(),
imageResId = WalletImageResolver.resolve(cardTypesResolver = userWallet.scanResponse.cardTypesResolver),
imageResId = WalletImageResolver.resolve(userWallet = userWallet),
onRenameClick = onRenameClick,
onDeleteClick = onDeleteClick,
)
@ -126,7 +126,7 @@ internal class WalletsUnlockStateConverter(
private fun UserWallet.createAdditionalInfo(): TextReference? {
return if (isMultiCurrency) {
WalletAdditionalInfoFactory.resolve(cardTypesResolver = scanResponse.cardTypesResolver, wallet = this)
WalletAdditionalInfoFactory.resolve(wallet = this)
} else {
null
}

View file

@ -3,7 +3,6 @@ package com.tangem.feature.wallet.presentation.wallet.utils
import com.tangem.common.Provider
import com.tangem.core.ui.utils.BigDecimalFormatter.formatFiatAmount
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.common.CardTypesResolver
import com.tangem.domain.tokens.model.TokenList.FiatBalance
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.feature.wallet.presentation.wallet.domain.WalletAdditionalInfoFactory
@ -12,7 +11,6 @@ import com.tangem.utils.converter.Converter
internal class FiatBalanceToWalletCardConverter(
private val currentState: WalletCardState,
private val cardTypeResolverProvider: Provider<CardTypesResolver>,
private val appCurrencyProvider: Provider<AppCurrency>,
private val currentWalletProvider: Provider<UserWallet>,
private val isWalletContentHidden: Boolean,
@ -37,10 +35,7 @@ internal class FiatBalanceToWalletCardConverter(
imageResId = imageResId,
onDeleteClick = onDeleteClick,
onRenameClick = onRenameClick,
additionalInfo = WalletAdditionalInfoFactory.resolve(
cardTypesResolver = cardTypeResolverProvider(),
wallet = currentWalletProvider(),
),
additionalInfo = WalletAdditionalInfoFactory.resolve(wallet = currentWalletProvider()),
)
}
@ -60,10 +55,7 @@ internal class FiatBalanceToWalletCardConverter(
WalletCardState.Content(
id = currentState.id,
title = currentState.title,
additionalInfo = WalletAdditionalInfoFactory.resolve(
cardTypesResolver = cardTypeResolverProvider(),
wallet = currentWalletProvider(),
),
additionalInfo = WalletAdditionalInfoFactory.resolve(wallet = currentWalletProvider()),
imageResId = currentState.imageResId,
onRenameClick = currentState.onRenameClick,
onDeleteClick = currentState.onDeleteClick,

View file

@ -2,7 +2,6 @@ package com.tangem.feature.wallet.presentation.wallet.utils
import com.tangem.common.Provider
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.common.CardTypesResolver
import com.tangem.domain.tokens.model.TokenList
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.feature.wallet.presentation.wallet.state.WalletMultiCurrencyState
@ -16,7 +15,6 @@ import kotlinx.collections.immutable.toPersistentList
@Suppress("LongParameterList")
internal class TokenListToWalletStateConverter(
private val currentStateProvider: Provider<WalletState>,
private val cardTypeResolverProvider: Provider<CardTypesResolver>,
private val currentWalletProvider: Provider<UserWallet>,
private val appCurrencyProvider: Provider<AppCurrency>,
private val isWalletContentHidden: Boolean,
@ -50,9 +48,8 @@ internal class TokenListToWalletStateConverter(
val selectedWalletCard = walletsListConfig.wallets[selectedWalletIndex]
val converter = FiatBalanceToWalletCardConverter(
currentState = selectedWalletCard,
currentWalletProvider = currentWalletProvider,
cardTypeResolverProvider = cardTypeResolverProvider,
appCurrencyProvider = appCurrencyProvider,
currentWalletProvider = currentWalletProvider,
isWalletContentHidden = isWalletContentHidden,
)