Updated on 2026-08-14

This commit is contained in:
Tangem 2023-04-13 13:45:29 +03:00
commit 4c9a1af7c4
11 changed files with 3 additions and 511 deletions

View file

@ -7,7 +7,6 @@ import com.tangem.domain.common.extensions.toCoinId
import com.tangem.domain.features.addCustomToken.CustomCurrency
import com.tangem.tap.common.redux.global.CryptoCurrencyName
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
import com.tangem.tap.features.tokens.redux.TokenWithBlockchain
import com.tangem.blockchain.common.Blockchain as SdkBlockchain
import com.tangem.blockchain.common.Token as SdkToken
@ -87,14 +86,6 @@ sealed interface Currency {
}
}
fun fromTokenWithBlockchain(tokenWithBlockchain: TokenWithBlockchain): Token {
return Token(
token = tokenWithBlockchain.token,
blockchain = tokenWithBlockchain.blockchain,
derivationPath = null,
)
}
fun fromTokenResponse(tokenBody: UserTokensResponse.Token): Currency? {
val blockchain = com.tangem.blockchain.common.Blockchain.fromNetworkId(tokenBody.networkId)
?: return null

View file

@ -26,7 +26,7 @@ data class PendingTransaction(
val currency: String = transactionData.amount.currencySymbol
fun nullIfUnknown(address: String): String? = if (address == "unknown") null else address
private fun nullIfUnknown(address: String): String? = if (address == "unknown") null else address
}
enum class PendingTransactionType { Incoming, Outgoing, Unknown }
@ -46,18 +46,10 @@ fun List<TransactionData>.toPendingTransactions(walletAddress: String): List<Pen
return this.mapNotNull { it.toPendingTransaction(walletAddress) }
}
fun List<PendingTransaction>.removeUnknownTransactions(): List<PendingTransaction> {
return this.filter { it.type != PendingTransactionType.Unknown }
}
fun List<PendingTransaction>.filterByCoin(): List<PendingTransaction> {
return this.filter { it.transactionData.amount.type == AmountType.Coin }
}
fun List<PendingTransaction>.filterByToken(token: Token): List<PendingTransaction> {
return this.filter { it.transactionData.amount.currencySymbol == token.symbol }
}
fun TransactionData.toPendingTransactionForToken(token: Token, walletAddress: String): PendingTransaction? {
if (this.amount.currencySymbol != token.symbol) return null
return this.toPendingTransaction(walletAddress)
@ -79,10 +71,6 @@ fun Wallet.hasPendingTransactions(): Boolean {
return getPendingTransactions().isNotEmpty()
}
fun Wallet.hasPendingTransactions(token: Token): Boolean {
return getPendingTransactions(token).isNotEmpty()
}
fun Wallet.getSendableAmounts(): List<Amount> {
return amounts.values
.filter { it.type != AmountType.Reserve }
@ -93,14 +81,6 @@ fun Wallet.hasSendableAmounts(): Boolean {
return getSendableAmounts().isNotEmpty()
}
fun Wallet.hasSendableAmountsOrPendingTransactions(): Boolean {
return hasPendingTransactions() || hasSendableAmounts()
}
fun Wallet.isSendableAmount(type: AmountType): Boolean {
return amounts[type]?.isAboveZero() == true
}
fun Wallet.isSendableAmount(token: Token): Boolean {
return isSendableAmount(AmountType.Token(token))
}

View file

@ -1,11 +0,0 @@
package com.tangem.tap.features.wallet.models
import com.tangem.tap.common.entities.FiatCurrency
import com.tangem.tap.features.wallet.redux.ProgressState
import java.math.BigDecimal
data class TotalBalance(
val state: ProgressState,
val fiatAmount: BigDecimal?,
val fiatCurrency: FiatCurrency,
)

View file

@ -20,6 +20,4 @@ sealed class WalletWarning(val showingPosition: Int) {
data class Rent(val walletRent: WalletStoreModel.WalletRent) : WalletWarning(showingPosition = 40)
}
data class WalletWarningDescription(val title: String, val message: String)
data class WalletRent(val minRentValue: String, val rentExemptValue: String)
data class WalletWarningDescription(val title: String, val message: String)

View file

@ -1,126 +0,0 @@
package com.tangem.tap.features.wallet.redux
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.TransactionData
import com.tangem.common.extensions.isZero
import com.tangem.domain.common.extensions.toNetworkId
import com.tangem.feature.swap.api.SwapFeatureToggleManager
import com.tangem.feature.swap.domain.SwapInteractor
import com.tangem.tap.features.wallet.models.Currency
import com.tangem.tap.features.wallet.models.PendingTransaction
import com.tangem.tap.features.wallet.models.WalletRent
import com.tangem.tap.features.wallet.models.WalletWarning
import com.tangem.tap.features.wallet.ui.BalanceStatus
import com.tangem.tap.features.wallet.ui.BalanceWidgetData
import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager
import java.math.BigDecimal
data class WalletData(
val pendingTransactions: List<PendingTransaction> = emptyList(),
val historyTransactions: List<TransactionData>? = null,
val hashesCountVerified: Boolean? = null,
val walletAddresses: WalletAddresses? = null,
val currencyData: BalanceWidgetData = BalanceWidgetData(),
val updatingWallet: Boolean = false,
val fiatRateString: String? = null,
val fiatRate: BigDecimal? = null,
val mainButton: WalletMainButton = WalletMainButton.SendButton(false),
val currency: Currency,
val walletRent: WalletRent? = null,
val existentialDepositString: String? = null,
) {
fun isAvailableToBuy(exchangeManager: CurrencyExchangeManager): Boolean {
return exchangeManager.availableForBuy(currency)
}
fun isAvailableToSell(exchangeManager: CurrencyExchangeManager): Boolean {
return exchangeManager.availableForSell(currency)
}
fun isAvailableToSwap(
swapFeatureToggleManager: SwapFeatureToggleManager,
swapInteractor: SwapInteractor,
): Boolean {
if (currency.blockchain.id == Blockchain.Optimism.id && !swapFeatureToggleManager.isOptimismSwapEnabled) {
return false
}
return swapInteractor.isAvailableToSwap(currency.blockchain.toNetworkId()) &&
!currency.isCustomCurrency(null)
}
fun getAvailableActions(
swapInteractor: SwapInteractor,
exchangeManager: CurrencyExchangeManager,
swapFeatureToggleManager: SwapFeatureToggleManager,
): Set<CurrencyAction> {
return setOfNotNull(
if (isAvailableToBuy(exchangeManager)) CurrencyAction.Buy else null,
if (isAvailableToSell(exchangeManager)) CurrencyAction.Sell else null,
if (isAvailableToSwap(swapFeatureToggleManager, swapInteractor)) CurrencyAction.Swap else null,
)
}
fun shouldShowMultipleAddress(): Boolean {
val listOfAddresses = walletAddresses?.list ?: return false
return listOfAddresses.size > 1
}
fun shouldEnableTokenSendButton(): Boolean = if (blockchainAmountIsEmpty()) {
false
} else {
!tokenAmountIsEmpty()
}
fun assembleWarnings(): List<WalletWarning> {
val walletWarnings = mutableListOf<WalletWarning>()
assembleNonTypedWarnings(walletWarnings)
assembleBlockchainWarnings(walletWarnings)
assembleTokenWarnings(walletWarnings)
return walletWarnings.sortedBy { it.showingPosition }
}
private fun assembleNonTypedWarnings(walletWarnings: MutableList<WalletWarning>) {
if (currencyData.status == BalanceStatus.SameCurrencyTransactionInProgress) {
walletWarnings.add(WalletWarning.TransactionInProgress(currency.currencyName))
}
if (walletRent != null) {
// TODO: Will be removed in next MR
// walletWarnings.add(WalletWarning.Rent(walletRent))
}
}
private fun assembleBlockchainWarnings(walletWarnings: MutableList<WalletWarning>) = with(currency) {
if (!isBlockchain()) return
if (existentialDepositString != null) {
val warning = WalletWarning.ExistentialDeposit(
currencyName = currencyName,
edStringValueWithSymbol = "$existentialDepositString $currencySymbol",
)
walletWarnings.add(warning)
}
}
private fun assembleTokenWarnings(walletWarnings: MutableList<WalletWarning>) = with(currency) {
if (!isToken()) return
if (blockchainAmountIsEmpty() && !tokenAmountIsEmpty()) {
walletWarnings.add(
WalletWarning.BalanceNotEnoughForFee(
currencyName = currencyName,
blockchainFullName = blockchain.fullName,
blockchainSymbol = blockchain.currency,
),
)
}
}
private fun blockchainAmountIsEmpty(): Boolean = currencyData.blockchainAmount?.isZero() == true
private fun tokenAmountIsEmpty(): Boolean = currencyData.amount?.isZero() == true
}
enum class CurrencyAction {
Buy, Sell, Swap
}

View file

@ -3,9 +3,7 @@ package com.tangem.tap.features.wallet.redux
import android.graphics.Bitmap
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.WalletManager
import com.tangem.blockchain.common.address.AddressType
import com.tangem.tap.common.entities.Button
import com.tangem.tap.common.extensions.toQrCode
import com.tangem.tap.common.redux.global.CryptoCurrencyName
import com.tangem.tap.common.toggleWidget.WidgetState
import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
@ -120,20 +118,6 @@ sealed class WalletMainButton(enabled: Boolean) : Button(enabled) {
class CreateWalletButton(enabled: Boolean) : WalletMainButton(enabled)
}
data class WalletAddresses(
val selectedAddress: AddressData,
val list: List<AddressData>,
)
data class AddressData(
val address: String,
val type: AddressType,
val shareUrl: String,
val exploreUrl: String,
) {
val qrCode: Bitmap by lazy { shareUrl.toQrCode() }
}
data class Artwork(
val artworkId: String,
val artwork: Bitmap? = null,
@ -148,41 +132,4 @@ data class Artwork(
const val TWIN_CARD_2 = "https://app.tangem.com/cards/card_tg086.png"
const val SALT_PAY_URL = "key_for_switch_url_to_drawableId_of_salt_pay_card"
}
}
data class WalletStore(
val walletManager: WalletManager?,
val blockchainNetwork: BlockchainNetwork,
val walletsData: List<WalletData>,
) {
fun updateWallets(walletDataList: List<WalletData>): WalletStore {
val relevantWalletDataList = walletDataList.filter {
it.currency.blockchain == blockchainNetwork.blockchain &&
it.currency.derivationPath == blockchainNetwork.derivationPath
}.toMutableList()
val updatedWalletDataList = walletsData.map { walletData ->
val matchingWalletData = relevantWalletDataList.find { it.currency == walletData.currency }
if (matchingWalletData != null) relevantWalletDataList.remove(matchingWalletData)
matchingWalletData ?: walletData
}
return copy(walletsData = updatedWalletDataList + relevantWalletDataList)
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as WalletStore
if (walletManager != other.walletManager) return false
if (blockchainNetwork != other.blockchainNetwork) return false
return true
}
override fun hashCode(): Int {
var result = walletManager?.hashCode() ?: 0
result = 31 * result + blockchainNetwork.hashCode()
return result
}
}

View file

@ -49,7 +49,7 @@ class AppCurrencyMiddleware(
runCatching { walletRepository.getCurrencyList() }
.onSuccess {
val currenciesList = it.currencies
if (currenciesList.isNotEmpty() && !currenciesList.toSet().equals(storedFiatCurrencies.toSet())) {
if (currenciesList.isNotEmpty() && currenciesList.toSet() != storedFiatCurrencies.toSet()) {
fiatCurrenciesPrefStorage.save(currenciesList)
store.dispatchDialogShow(
WalletDialog.CurrencySelectionDialog(

View file

@ -1,145 +0,0 @@
package com.tangem.tap.features.wallet.redux.middlewares
import com.tangem.common.extensions.isZero
import com.tangem.tap.common.extensions.stripZeroPlainString
import com.tangem.tap.common.extensions.toFiatRateString
import com.tangem.tap.common.extensions.toFiatValue
import com.tangem.tap.common.extensions.toFormattedCurrencyString
import com.tangem.tap.common.extensions.toFormattedFiatValue
import com.tangem.tap.domain.model.TotalFiatBalance
import com.tangem.tap.domain.model.WalletDataModel
import com.tangem.tap.domain.model.WalletStoreModel
import com.tangem.tap.features.wallet.models.TotalBalance
import com.tangem.tap.features.wallet.models.WalletRent
import com.tangem.tap.features.wallet.redux.AddressData
import com.tangem.tap.features.wallet.redux.ProgressState
import com.tangem.tap.features.wallet.redux.WalletAddresses
import com.tangem.tap.features.wallet.redux.WalletData
import com.tangem.tap.features.wallet.redux.WalletMainButton
import com.tangem.tap.features.wallet.redux.WalletStore
import com.tangem.tap.features.wallet.ui.BalanceStatus
import com.tangem.tap.features.wallet.ui.BalanceWidgetData
import com.tangem.tap.store
import java.math.BigDecimal
internal fun List<WalletStoreModel>.mapToReduxModels(): List<WalletStore> {
return this.map { walletStoreModel ->
walletStoreModel.mapToReduxModel()
}
}
internal fun TotalFiatBalance.mapToReduxModel(): TotalBalance {
return TotalBalance(
state = when (this) {
is TotalFiatBalance.Loading -> ProgressState.Loading
is TotalFiatBalance.Error -> ProgressState.Error
is TotalFiatBalance.Loaded -> ProgressState.Done
},
fiatAmount = amount,
fiatCurrency = store.state.globalState.appCurrency,
)
}
internal fun WalletStoreModel.mapToReduxModel(): WalletStore {
val appCurrencySymbol = store.state.globalState.appCurrency.symbol
return WalletStore(
walletManager = walletManager,
blockchainNetwork = blockchainNetwork,
walletsData = walletsData.mapToReduxModels(walletRent, appCurrencySymbol),
)
.updateTokenModels(blockchainWalletData.status.amount)
}
@Suppress("LongMethod", "ComplexMethod")
private fun List<WalletDataModel>.mapToReduxModels(
walletRent: WalletStoreModel.WalletRent?,
appCurrencySymbol: String,
): List<WalletData> {
return this.map { walletDataModel ->
walletDataModel.mapToReduxModel(walletRent, appCurrencySymbol)
}
}
private fun WalletDataModel.mapToReduxModel(
walletRent: WalletStoreModel.WalletRent?,
appCurrencySymbol: String,
): WalletData {
val amount = status.amount
val amountFormatted = amount.toFormattedCurrencyString(
decimals = currency.decimals,
currency = currency.currencySymbol,
)
val fiatAmount = fiatRate?.let { status.amount.toFiatValue(it) }
val fiatAmountFormatted = fiatAmount
?.takeIf { !status.isErrorStatus }
?.toFormattedFiatValue(appCurrencySymbol)
val fiatRateFormatted = fiatRate?.toFiatRateString(appCurrencySymbol)
return WalletData(
currency = currency,
// TODO: Will be updated in next MRs
walletAddresses = walletAddresses?.let { addresses ->
WalletAddresses(
selectedAddress = with(addresses.selectedAddress) {
AddressData(address, type, shareUrl, exploreUrl)
},
list = addresses.list.map {
with(it) { AddressData(address, type, shareUrl, exploreUrl) }
},
)
},
existentialDepositString = existentialDeposit?.toPlainString(),
fiatRate = fiatRate,
fiatRateString = fiatRateFormatted,
pendingTransactions = status.pendingTransactions,
mainButton = WalletMainButton.SendButton(
enabled = !status.amount.isZero() && status.pendingTransactions.isEmpty(),
),
walletRent = walletRent?.let {
WalletRent(
minRentValue = "${it.rent.stripZeroPlainString()} ${currency.blockchain.currency}",
rentExemptValue = "${it.exemptionAmount.stripZeroPlainString()} ${currency.blockchain.currency}",
)
},
currencyData = BalanceWidgetData(
status = when (status) {
is WalletDataModel.Loading -> BalanceStatus.Loading
is WalletDataModel.NoAccount -> BalanceStatus.NoAccount
is WalletDataModel.SameCurrencyTransactionInProgress -> BalanceStatus.SameCurrencyTransactionInProgress
is WalletDataModel.TransactionInProgress -> BalanceStatus.TransactionInProgress
is WalletDataModel.Unreachable -> BalanceStatus.Unreachable
is WalletDataModel.MissedDerivation -> BalanceStatus.MissedDerivation
is WalletDataModel.VerifiedOnline -> BalanceStatus.VerifiedOnline
},
currency = currency.currencyName,
currencySymbol = currency.currencySymbol,
blockchainAmount = BigDecimal.ZERO,
amount = amount,
amountFormatted = amountFormatted,
fiatAmount = fiatAmount,
fiatAmountFormatted = fiatAmountFormatted,
amountToCreateAccount = (status as? WalletDataModel.NoAccount)
?.amountToCreateAccount
?.toString(),
errorMessage = status.errorMessage,
),
historyTransactions = historyTransactions,
)
}
private fun WalletStore.updateTokenModels(blockchainAmount: BigDecimal): WalletStore {
val updatedTokensWalletData = walletsData.filter { it.currency.isToken() }.map {
it.copy(
mainButton = when (it.mainButton) {
is WalletMainButton.SendButton -> {
WalletMainButton.SendButton(it.mainButton.enabled && !blockchainAmount.isZero())
}
is WalletMainButton.CreateWalletButton -> it.mainButton
},
currencyData = it.currencyData.copy(
blockchainAmount = blockchainAmount,
),
)
}
return updateWallets(updatedTokensWalletData)
}

View file

@ -1,73 +0,0 @@
package com.tangem.tap.features.wallet.redux.reducers
import com.tangem.tap.features.wallet.redux.ProgressState
import com.tangem.tap.features.wallet.redux.WalletData
import com.tangem.tap.features.wallet.ui.BalanceStatus
import java.math.BigDecimal
fun List<WalletData>.findProgressState(initialState: ProgressState = ProgressState.Done): ProgressState {
if (this.isEmpty()) return initialState
return this
.mapToProgressState()
.reduce(ProgressState::or)
}
fun List<WalletData>.calculateTotalCryptoAmount(): BigDecimal {
return this
.map { it.currencyData.amount ?: BigDecimal.ZERO }
.reduce(BigDecimal::plus)
}
private fun List<WalletData>.mapToProgressState(): List<ProgressState> {
return this.map { wallet ->
if (wallet.fiatRate == null) {
ProgressState.Error
} else {
when (wallet.currencyData.status) {
BalanceStatus.Refreshing -> ProgressState.Refreshing
BalanceStatus.VerifiedOnline,
BalanceStatus.SameCurrencyTransactionInProgress,
BalanceStatus.TransactionInProgress,
BalanceStatus.NoAccount,
-> ProgressState.Done
BalanceStatus.Unreachable,
BalanceStatus.EmptyCard,
BalanceStatus.UnknownBlockchain,
BalanceStatus.MissedDerivation,
-> ProgressState.Error
BalanceStatus.Loading,
null,
-> ProgressState.Loading
}
}
}
}
private infix fun ProgressState.or(newState: ProgressState): ProgressState {
return when (this) {
ProgressState.Loading -> when (newState) {
ProgressState.Loading,
ProgressState.Refreshing,
ProgressState.Error,
ProgressState.Done,
-> this
}
ProgressState.Done,
ProgressState.Error,
-> when (newState) {
ProgressState.Loading,
ProgressState.Refreshing,
ProgressState.Error,
-> newState
ProgressState.Done -> this
}
ProgressState.Refreshing -> when (newState) {
ProgressState.Loading -> this
ProgressState.Refreshing,
ProgressState.Error,
ProgressState.Done,
-> newState
}
}
}

View file

@ -9,33 +9,6 @@ import com.tangem.tap.features.wallet.ui.utils.getFormattedFiatAmount
import com.tangem.tap.store
import com.tangem.wallet.R
import com.tangem.wallet.databinding.CardBalanceBinding
import java.math.BigDecimal
enum class BalanceStatus {
VerifiedOnline,
TransactionInProgress,
SameCurrencyTransactionInProgress,
Unreachable,
Loading,
Refreshing,
NoAccount,
EmptyCard,
UnknownBlockchain,
MissedDerivation,
}
data class BalanceWidgetData(
val status: BalanceStatus? = null,
val currency: String? = null,
val currencySymbol: String? = null,
val amount: BigDecimal? = null,
val amountFormatted: String? = null,
val fiatAmount: BigDecimal? = null,
val fiatAmountFormatted: String? = null,
val blockchainAmount: BigDecimal? = BigDecimal.ZERO,
val amountToCreateAccount: String? = null,
val errorMessage: String? = null,
)
class BalanceWidget(
private val binding: CardBalanceBinding,
@ -104,19 +77,6 @@ class BalanceWidget(
showStatus(R.id.group_error)
tvStatusErrorMessage.show(!blockchainWalletData.status.errorMessage.isNullOrBlank())
}
// BalanceStatus.EmptyCard -> with(binding.lBalanceError) {
// binding.lBalance.root.hide()
// binding.lBalanceError.root.show()
// if (isTwinCard) {
// tvErrorTitle.text = fragment.getText(R.string.wallet_error_empty_twin_card)
// tvErrorDescriptions.text =
// fragment.getText(R.string.wallet_error_empty_twin_card_subtitle)
// } else {
// tvErrorTitle.text = fragment.getText(R.string.wallet_error_empty_card)
// tvErrorDescriptions.text =
// fragment.getText(R.string.wallet_error_empty_card_subtitle)
// }
// }
is WalletDataModel.NoAccount -> with(binding.lBalanceError) {
binding.lBalance.root.hide()
binding.lBalanceError.root.show()
@ -128,14 +88,6 @@ class BalanceWidget(
blockchainWalletData.currency.currencySymbol,
)
}
// BalanceStatus.UnknownBlockchain -> with(binding.lBalanceError) {
// binding.lBalance.root.hide()
// binding.lBalanceError.root.show()
// tvErrorTitle.text =
// fragment.getText(R.string.wallet_error_unsupported_blockchain)
// tvErrorDescriptions.text =
// fragment.getString(R.string.wallet_error_unsupported_blockchain_subtitle)
// }
else -> {}
}
}

View file

@ -1,21 +0,0 @@
package com.tangem.tap.features.wallet.ui.wallet.saltPay
import com.tangem.blockchain.common.TransactionData
import com.tangem.blockchain.common.TransactionStatus
import com.tangem.tap.features.wallet.models.PendingTransactionType
/**
[REDACTED_AUTHOR]
*/
data class HistoryTransactionData(
val transactionData: TransactionData,
private val walletAddress: String,
) {
fun isInProgress(): Boolean = transactionData.status == TransactionStatus.Unconfirmed
fun getTransactionType(): PendingTransactionType = when {
transactionData.sourceAddress.lowercase() == walletAddress.lowercase() -> PendingTransactionType.Outgoing
transactionData.destinationAddress.lowercase() == walletAddress.lowercase() -> PendingTransactionType.Incoming
else -> PendingTransactionType.Unknown
}
}