Updated on 2026-08-14
This commit is contained in:
commit
12bc959fde
112 changed files with 3193 additions and 47273 deletions
|
|
@ -106,6 +106,7 @@ object TangemSdk {
|
|||
is TangemSdkError.IssuerSignatureLoadingFailed -> TangemSdkError.IssuerSignatureLoadingFailed()
|
||||
is TangemSdkError.BackupFailedFirmware -> TangemSdkError.BackupFailedFirmware()
|
||||
is TangemSdkError.UserForgotTheCode -> TangemSdkError.UserForgotTheCode()
|
||||
is TangemSdkError.BackupFailedIncompatibleBatch -> TangemSdkError.BackupFailedIncompatibleBatch()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,37 @@
|
|||
package com.tangem.tap.common.compose
|
||||
|
||||
import android.graphics.Rect
|
||||
import android.view.ViewTreeObserver
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
|
||||
sealed class Keyboard {
|
||||
data class Opened(val height: Int): Keyboard()
|
||||
object Closed : Keyboard()
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun keyboardAsState(): State<Keyboard> {
|
||||
val keyboardState: MutableState<Keyboard> = remember { mutableStateOf(Keyboard.Closed) }
|
||||
val view = LocalView.current
|
||||
DisposableEffect(view) {
|
||||
val onGlobalListener = ViewTreeObserver.OnGlobalLayoutListener {
|
||||
val rect = Rect()
|
||||
view.getWindowVisibleDisplayFrame(rect)
|
||||
val screenHeight = view.rootView.height
|
||||
val keypadHeight = screenHeight - rect.bottom
|
||||
keyboardState.value = if (keypadHeight > screenHeight * 0.15) {
|
||||
Keyboard.Opened(keypadHeight)
|
||||
} else {
|
||||
Keyboard.Closed
|
||||
}
|
||||
}
|
||||
view.viewTreeObserver.addOnGlobalLayoutListener(onGlobalListener)
|
||||
|
||||
onDispose {
|
||||
view.viewTreeObserver.removeOnGlobalLayoutListener(onGlobalListener)
|
||||
}
|
||||
}
|
||||
|
||||
return keyboardState
|
||||
}
|
||||
|
|
@ -5,20 +5,54 @@ import com.tangem.blockchain.common.Blockchain
|
|||
import com.tangem.wallet.R
|
||||
|
||||
@DrawableRes
|
||||
fun Blockchain.getIconRes(): Int {
|
||||
fun Blockchain.getRoundIconRes(): Int {
|
||||
return when (this) {
|
||||
Blockchain.Unknown -> R.drawable.shape_circle
|
||||
Blockchain.Ducatus -> R.drawable.ic_ducatus
|
||||
Blockchain.Bitcoin, Blockchain.BitcoinTestnet,-> R.drawable.ic_btc
|
||||
Blockchain.BitcoinCash -> R.drawable.ic_btc_cash
|
||||
Blockchain.Litecoin -> R.drawable.ic_ltc
|
||||
Blockchain.Ethereum, Blockchain.EthereumTestnet, -> R.drawable.ic_eth
|
||||
Blockchain.RSK -> R.drawable.ic_rsk
|
||||
Blockchain.Cardano, Blockchain.CardanoShelley -> R.drawable.ic_cardano
|
||||
Blockchain.Binance, Blockchain.BinanceTestnet -> R.drawable.ic_binance
|
||||
Blockchain.Tezos -> R.drawable.ic_tezos
|
||||
Blockchain.XRP -> R.drawable.ic_xrp
|
||||
Blockchain.Stellar -> R.drawable.ic_stellar
|
||||
else -> R.drawable.shape_circle
|
||||
Blockchain.Bitcoin, Blockchain.BitcoinTestnet,-> R.drawable.ic_bitcoin_round
|
||||
Blockchain.BitcoinCash -> R.drawable.ic_bitcoin_cash_round
|
||||
Blockchain.Litecoin -> R.drawable.ic_litecoin_round
|
||||
Blockchain.Ethereum, Blockchain.EthereumTestnet, -> R.drawable.ic_eth_round
|
||||
Blockchain.RSK -> R.drawable.ic_rsk_round
|
||||
Blockchain.Cardano, Blockchain.CardanoShelley -> R.drawable.ic_cardano_round
|
||||
Blockchain.Tezos -> R.drawable.ic_tezos_round
|
||||
Blockchain.XRP -> R.drawable.ic_xrp_round
|
||||
Blockchain.Stellar -> R.drawable.ic_stellar_round
|
||||
Blockchain.Avalanche, Blockchain.AvalancheTestnet -> R.drawable.ic_avalanche_round
|
||||
Blockchain.Polygon, Blockchain.PolygonTestnet -> R.drawable.ic_polygon_round
|
||||
Blockchain.Solana, Blockchain.SolanaTestnet -> R.drawable.ic_solana_round
|
||||
Blockchain.Fantom, Blockchain.FantomTestnet -> R.drawable.ic_fantom_round
|
||||
Blockchain.BSC, Blockchain.BSCTestnet, Blockchain.Binance, Blockchain.BinanceTestnet -> R.drawable.ic_bsc_round
|
||||
else -> R.drawable.ic_tangem_logo
|
||||
}
|
||||
}
|
||||
|
||||
@DrawableRes
|
||||
fun Blockchain.getGreyedOutIconRes(): Int {
|
||||
return when (this) {
|
||||
// Blockchain.Ducatus -> R.drawable.ic_ducatus
|
||||
Blockchain.Bitcoin, Blockchain.BitcoinTestnet,-> R.drawable.ic_bitcoin_no_color
|
||||
Blockchain.BitcoinCash -> R.drawable.ic_bitcoin_cash_no_color
|
||||
Blockchain.Litecoin -> R.drawable.ic_litecoin_no_color
|
||||
Blockchain.Ethereum, Blockchain.EthereumTestnet, -> R.drawable.ic_eth_no_color
|
||||
Blockchain.RSK -> R.drawable.ic_rsk_no_color
|
||||
Blockchain.Cardano, Blockchain.CardanoShelley -> R.drawable.ic_cardano_no_color
|
||||
Blockchain.Tezos -> R.drawable.ic_tezos_no_color
|
||||
Blockchain.XRP -> R.drawable.ic_xrp_no_color
|
||||
Blockchain.Stellar -> R.drawable.ic_stellar_no_color
|
||||
Blockchain.Avalanche, Blockchain.AvalancheTestnet -> R.drawable.ic_avalanche_no_color
|
||||
Blockchain.Polygon, Blockchain.PolygonTestnet -> R.drawable.ic_polygon_no_color
|
||||
Blockchain.Solana, Blockchain.SolanaTestnet -> R.drawable.ic_solana_no_color
|
||||
Blockchain.Fantom, Blockchain.FantomTestnet -> R.drawable.ic_fantom_no_color
|
||||
Blockchain.BSC, Blockchain.BSCTestnet, Blockchain.Binance, Blockchain.BinanceTestnet -> R.drawable.ic_bsc_no_color
|
||||
else -> R.drawable.ic_tangem_logo
|
||||
}
|
||||
}
|
||||
|
||||
fun Blockchain.getNetworkName(): String {
|
||||
return when (this) {
|
||||
Blockchain.Ethereum, Blockchain.EthereumTestnet -> "ERC20"
|
||||
Blockchain.BSC, Blockchain.BSCTestnet -> "BEP20"
|
||||
Blockchain.Binance, Blockchain.BinanceTestnet -> "BEP2"
|
||||
else -> ""
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
package com.tangem.tap.common.extensions
|
||||
|
||||
import android.graphics.*
|
||||
import android.net.Uri
|
||||
import android.widget.TextView
|
||||
import androidx.constraintlayout.utils.widget.ImageFilterView
|
||||
import com.squareup.picasso.Callback
|
||||
|
|
@ -11,6 +10,8 @@ import com.tangem.blockchain.common.Blockchain
|
|||
import com.tangem.blockchain.common.IconsUtil
|
||||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.tap.domain.extensions.getCustomIconUrl
|
||||
import com.tangem.tap.domain.tokens.getIconUrl
|
||||
import com.tangem.tap.domain.tokens.toNetworkId
|
||||
import com.tangem.wallet.R
|
||||
|
||||
fun Picasso.loadCurrenciesIcon(
|
||||
|
|
@ -20,10 +21,11 @@ fun Picasso.loadCurrenciesIcon(
|
|||
blockchain: Blockchain,
|
||||
) {
|
||||
|
||||
val url = if (token != null) {
|
||||
token.getCustomIconUrl()?.let { Uri.parse(it) } ?: IconsUtil.getTokenIconUri(blockchain, token)
|
||||
val url: String? = if (token != null) {
|
||||
token.id?.let { getIconUrl(it) } ?: token.getCustomIconUrl()
|
||||
?: IconsUtil.getTokenIconUri(blockchain, token)?.toString()
|
||||
} else {
|
||||
IconsUtil.getBlockchainIconUri(blockchain)
|
||||
getIconUrl(blockchain.toNetworkId())
|
||||
}
|
||||
|
||||
imageView.setImageDrawable(null)
|
||||
|
|
@ -39,7 +41,7 @@ fun Picasso.loadCurrenciesIcon(
|
|||
}
|
||||
url != null -> {
|
||||
if (token != null) {
|
||||
setTokenImage(imageView, textView, token)
|
||||
setTokenImage(imageView, textView, token, blockchain)
|
||||
}
|
||||
this.load(url)
|
||||
.transform(RoundedCornersTransform())
|
||||
|
|
@ -76,7 +78,7 @@ private fun setOfflineCurrencyImage(
|
|||
) {
|
||||
when (token) {
|
||||
null -> setBlockchainImage(imageView, textView, blockchain)
|
||||
else -> setTokenImage(imageView, textView, token)
|
||||
else -> setTokenImage(imageView, textView, token, blockchain)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -85,7 +87,7 @@ private fun setBlockchainImage(
|
|||
textView: TextView,
|
||||
blockchain: Blockchain,
|
||||
) {
|
||||
imageView.setImageResource(blockchain.getIconRes())
|
||||
imageView.setImageResource(blockchain.getRoundIconRes())
|
||||
imageView.colorFilter = null
|
||||
if (blockchain.isTestnet()) imageView.saturation = 0f
|
||||
textView.text = null
|
||||
|
|
@ -95,9 +97,10 @@ private fun setTokenImage(
|
|||
imageView: ImageFilterView,
|
||||
textView: TextView,
|
||||
token: Token,
|
||||
tokenBlockchain: Blockchain
|
||||
) {
|
||||
imageView.setImageResource(R.drawable.shape_circle)
|
||||
if (token.blockchain.isTestnet()) {
|
||||
if (tokenBlockchain.isTestnet()) {
|
||||
imageView.saturation = 0f
|
||||
} else {
|
||||
imageView.setColorFilter(token.getColor())
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import android.content.res.Resources
|
|||
import android.graphics.drawable.Drawable
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.util.DisplayMetrics
|
||||
import android.util.TypedValue
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
|
|
@ -19,6 +20,7 @@ import androidx.fragment.app.Fragment
|
|||
import com.google.android.material.card.MaterialCardView
|
||||
import com.tangem.common.extensions.VoidCallback
|
||||
|
||||
|
||||
fun Fragment.getDrawable(@DrawableRes drawableResId: Int): Drawable? {
|
||||
return ContextCompat.getDrawable(requireContext(), drawableResId)
|
||||
}
|
||||
|
|
@ -94,6 +96,13 @@ fun Context.dpToPixels(dp: Int): Int =
|
|||
TypedValue.COMPLEX_UNIT_DIP, dp.toFloat(), this.resources.displayMetrics
|
||||
).toInt()
|
||||
|
||||
|
||||
fun Context.pixelsToDp(pixels: Int): Int {
|
||||
return (pixels.toFloat() /
|
||||
(resources.displayMetrics.densityDpi.toFloat() / DisplayMetrics.DENSITY_DEFAULT))
|
||||
.toInt()
|
||||
}
|
||||
|
||||
tailrec fun Context?.getActivity(): Activity? = this as? Activity
|
||||
?: (this as? ContextWrapper)?.baseContext?.getActivity()
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import com.tangem.common.services.Result
|
|||
import com.tangem.domain.common.ScanResponse
|
||||
import com.tangem.domain.common.TapWorkarounds.isStart2Coin
|
||||
import com.tangem.domain.common.TapWorkarounds.isTestCard
|
||||
import com.tangem.domain.common.TapWorkarounds.derivationStyle
|
||||
import com.tangem.domain.common.extensions.withMainContext
|
||||
import com.tangem.tap.common.ThrottlerWithValues
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
|
|
@ -19,6 +20,7 @@ import com.tangem.tap.domain.extensions.isMultiwalletAllowed
|
|||
import com.tangem.tap.domain.extensions.makePrimaryWalletManager
|
||||
import com.tangem.tap.domain.extensions.makeWalletManagersForApp
|
||||
import com.tangem.tap.domain.tokens.CardCurrencies
|
||||
import com.tangem.tap.domain.tokens.BlockchainNetwork
|
||||
import com.tangem.tap.features.demo.isDemoCard
|
||||
import com.tangem.tap.features.wallet.models.PendingTransactionType
|
||||
import com.tangem.tap.features.wallet.models.getPendingTransactions
|
||||
|
|
@ -46,6 +48,7 @@ class TapWalletManager {
|
|||
|
||||
suspend fun loadWalletData(walletManager: WalletManager) {
|
||||
val blockchain = walletManager.wallet.blockchain
|
||||
val blockchainNetwork = BlockchainNetwork.fromWalletManager(walletManager)
|
||||
val result = if (walletManagersThrottler.isStillThrottled(blockchain)) {
|
||||
walletManagersThrottler.geValue(blockchain)!!
|
||||
} else {
|
||||
|
|
@ -54,21 +57,26 @@ class TapWalletManager {
|
|||
when (result) {
|
||||
is Result.Success -> {
|
||||
checkForRentWarning(walletManager)
|
||||
dispatchOnMain(WalletAction.LoadWallet.Success(result.data))
|
||||
dispatchOnMain(WalletAction.LoadWallet.Success(result.data, blockchainNetwork))
|
||||
}
|
||||
is Result.Failure -> {
|
||||
when (result.error) {
|
||||
is TapError.WalletManagerUpdate.NoAccountError -> {
|
||||
dispatchOnMain(WalletAction.LoadWallet.NoAccount(
|
||||
walletManager.wallet,
|
||||
(result.error as TapError.WalletManagerUpdate.NoAccountError).customMessage
|
||||
))
|
||||
dispatchOnMain(
|
||||
WalletAction.LoadWallet.NoAccount(
|
||||
walletManager.wallet,
|
||||
blockchainNetwork,
|
||||
(result.error as TapError.WalletManagerUpdate.NoAccountError).customMessage
|
||||
)
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
dispatchOnMain(WalletAction.LoadWallet.Failure(
|
||||
walletManager.wallet,
|
||||
result.error.localizedMessage
|
||||
))
|
||||
dispatchOnMain(
|
||||
WalletAction.LoadWallet.Failure(
|
||||
walletManager.wallet,
|
||||
result.error.localizedMessage
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -85,8 +93,8 @@ class TapWalletManager {
|
|||
|
||||
suspend fun loadFiatRate(fiatCurrency: FiatCurrencyName, wallet: Wallet) {
|
||||
val currencies = wallet.getTokens()
|
||||
.map { Currency.Token(it) }
|
||||
.plus(Currency.Blockchain(wallet.blockchain))
|
||||
.map { Currency.Token(it, wallet.blockchain, wallet.publicKey.derivationPath?.rawPath) }
|
||||
.plus(Currency.Blockchain(wallet.blockchain, wallet.publicKey.derivationPath?.rawPath))
|
||||
loadFiatRate(fiatCurrency, currencies)
|
||||
}
|
||||
|
||||
|
|
@ -130,7 +138,8 @@ class TapWalletManager {
|
|||
configManager?.turnOff(ConfigManager.isSendingToPayIdEnabled)
|
||||
configManager?.turnOff(ConfigManager.isTopUpEnabled)
|
||||
} else if (blockchain == Blockchain.Bitcoin
|
||||
|| data.walletData?.blockchain == Blockchain.Bitcoin.id) {
|
||||
|| data.walletData?.blockchain == Blockchain.Bitcoin.id
|
||||
) {
|
||||
configManager?.resetToDefault(ConfigManager.isSendingToPayIdEnabled)
|
||||
configManager?.resetToDefault(ConfigManager.isTopUpEnabled)
|
||||
} else {
|
||||
|
|
@ -161,8 +170,10 @@ class TapWalletManager {
|
|||
loadMultiWalletData(data, blockchain, primaryWalletManager)
|
||||
} else {
|
||||
dispatchOnMain(
|
||||
WalletAction.MultiWallet.AddWalletManagers(primaryWalletManager),
|
||||
WalletAction.MultiWallet.AddBlockchains(listOf(blockchain))
|
||||
WalletAction.MultiWallet.AddBlockchains(
|
||||
listOf(BlockchainNetwork.fromWalletManager(primaryWalletManager)),
|
||||
listOf(primaryWalletManager)
|
||||
)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
|
|
@ -177,32 +188,40 @@ class TapWalletManager {
|
|||
}
|
||||
|
||||
private suspend fun loadMultiWalletData(
|
||||
scanResponse: ScanResponse, primaryBlockchain: Blockchain?, primaryWalletManager: WalletManager?
|
||||
scanResponse: ScanResponse,
|
||||
primaryBlockchain: Blockchain?,
|
||||
primaryWalletManager: WalletManager?
|
||||
) {
|
||||
val primaryTokens = primaryWalletManager?.cardTokens?.toList() ?: emptyList()
|
||||
val savedCurrencies = currenciesRepository.loadCardCurrencies(scanResponse.card.cardId)
|
||||
val savedCurrencies = currenciesRepository.loadSavedCurrencies(scanResponse.card.cardId, scanResponse.card.derivationStyle)
|
||||
|
||||
if (savedCurrencies == null) {
|
||||
if (savedCurrencies.isEmpty()) {
|
||||
if (primaryBlockchain != null && primaryWalletManager != null) {
|
||||
val blockchainNetwork = BlockchainNetwork.fromWalletManager(primaryWalletManager)
|
||||
dispatchOnMain(
|
||||
WalletAction.MultiWallet.SaveCurrencies(
|
||||
CardCurrencies(blockchains = listOf(primaryBlockchain), tokens = primaryTokens)
|
||||
WalletAction.MultiWallet.SaveCurrencies(listOf(blockchainNetwork)),
|
||||
WalletAction.MultiWallet.AddBlockchains(
|
||||
listOf(blockchainNetwork),
|
||||
listOf(primaryWalletManager)
|
||||
),
|
||||
WalletAction.MultiWallet.AddWalletManagers(primaryWalletManager),
|
||||
WalletAction.MultiWallet.AddBlockchains(listOf(primaryBlockchain)),
|
||||
WalletAction.MultiWallet.AddTokens(primaryTokens.toList())
|
||||
WalletAction.MultiWallet.AddTokens(primaryTokens.toList(), blockchainNetwork)
|
||||
)
|
||||
|
||||
} else {
|
||||
val blockchains = listOf(Blockchain.Bitcoin, Blockchain.Ethereum)
|
||||
val walletManagers = walletManagerFactory.makeWalletManagersForApp(scanResponse, blockchains.toList())
|
||||
val derivationStyle = scanResponse.card.derivationStyle
|
||||
val blockchainNetworks = listOf(
|
||||
BlockchainNetwork(Blockchain.Bitcoin, scanResponse.card),
|
||||
BlockchainNetwork(Blockchain.Ethereum, scanResponse.card)
|
||||
)
|
||||
|
||||
val walletManagers = walletManagerFactory.makeWalletManagersForApp(
|
||||
scanResponse,
|
||||
blockchainNetworks
|
||||
)
|
||||
|
||||
dispatchOnMain(
|
||||
WalletAction.MultiWallet.SaveCurrencies(CardCurrencies(
|
||||
blockchains = blockchains,
|
||||
tokens = emptyList()
|
||||
)),
|
||||
WalletAction.MultiWallet.AddWalletManagers(walletManagers),
|
||||
WalletAction.MultiWallet.AddBlockchains(blockchains.toList()),
|
||||
WalletAction.MultiWallet.SaveCurrencies(blockchainNetworks),
|
||||
WalletAction.MultiWallet.AddBlockchains(blockchainNetworks, walletManagers),
|
||||
)
|
||||
}
|
||||
dispatchOnMain(
|
||||
|
|
@ -210,22 +229,25 @@ class TapWalletManager {
|
|||
WalletAction.MultiWallet.FindTokensInUse,
|
||||
)
|
||||
} else {
|
||||
val blockchains = savedCurrencies.blockchains.toList()
|
||||
val walletManagers = if (
|
||||
primaryTokens.isNotEmpty() &&
|
||||
primaryWalletManager != null && primaryBlockchain != null
|
||||
primaryWalletManager != null &&
|
||||
primaryBlockchain != null
|
||||
) {
|
||||
val blockchainsWithoutPrimary = blockchains.filterNot { it == primaryBlockchain }
|
||||
walletManagerFactory.makeWalletManagersForApp(scanResponse, blockchainsWithoutPrimary)
|
||||
.plus(primaryWalletManager)
|
||||
val blockchainsWithoutPrimary = savedCurrencies.filterNot { it.blockchain == primaryBlockchain }
|
||||
walletManagerFactory.makeWalletManagersForApp(
|
||||
scanResponse,
|
||||
blockchainsWithoutPrimary
|
||||
).plus(primaryWalletManager)
|
||||
} else {
|
||||
walletManagerFactory.makeWalletManagersForApp(scanResponse, blockchains)
|
||||
walletManagerFactory.makeWalletManagersForApp(scanResponse, savedCurrencies)
|
||||
}
|
||||
dispatchOnMain(
|
||||
WalletAction.MultiWallet.AddWalletManagers(walletManagers),
|
||||
WalletAction.MultiWallet.AddBlockchains(blockchains),
|
||||
WalletAction.MultiWallet.AddTokens(savedCurrencies.tokens.toList()),
|
||||
WalletAction.MultiWallet.AddBlockchains(savedCurrencies, walletManagers),
|
||||
)
|
||||
savedCurrencies.map {
|
||||
dispatchOnMain(WalletAction.MultiWallet.AddTokens(it.tokens, it))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -269,10 +291,12 @@ class TapWalletManager {
|
|||
|
||||
when (val result = rentProvider.minimalBalanceForRentExemption()) {
|
||||
is com.tangem.blockchain.extensions.Result.Success -> {
|
||||
fun isNeedToShowWarning(balance: BigDecimal, rentExempt: BigDecimal): Boolean = balance < rentExempt
|
||||
fun isNeedToShowWarning(balance: BigDecimal, rentExempt: BigDecimal): Boolean =
|
||||
balance < rentExempt
|
||||
|
||||
val balance = walletManager.wallet.fundsAvailable(AmountType.Coin)
|
||||
val outgoingTxs = walletManager.wallet.getPendingTransactions(PendingTransactionType.Outgoing)
|
||||
val outgoingTxs =
|
||||
walletManager.wallet.getPendingTransactions(PendingTransactionType.Outgoing)
|
||||
val rentExempt = result.data
|
||||
val show = if (outgoingTxs.isEmpty()) {
|
||||
isNeedToShowWarning(balance, rentExempt)
|
||||
|
|
@ -284,11 +308,13 @@ class TapWalletManager {
|
|||
if (!show) return
|
||||
|
||||
val currency = walletManager.wallet.blockchain.currency
|
||||
dispatchOnMain(WalletAction.SetWalletRent(
|
||||
blockchain = walletManager.wallet.blockchain,
|
||||
minRent = ("${rentProvider.rentAmount().stripZeroPlainString()} $currency"),
|
||||
rentExempt = ("${rentExempt.stripZeroPlainString()} $currency")
|
||||
))
|
||||
dispatchOnMain(
|
||||
WalletAction.SetWalletRent(
|
||||
blockchain = BlockchainNetwork.fromWalletManager(walletManager),
|
||||
minRent = ("${rentProvider.rentAmount().stripZeroPlainString()} $currency"),
|
||||
rentExempt = ("${rentExempt.stripZeroPlainString()} $currency")
|
||||
)
|
||||
)
|
||||
}
|
||||
is com.tangem.blockchain.extensions.Result.Failure -> {}
|
||||
}
|
||||
|
|
|
|||
91
app/src/main/java/com/tangem/tap/domain/TapWorkarounds.kt
Normal file
91
app/src/main/java/com/tangem/tap/domain/TapWorkarounds.kt
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
package com.tangem.tap.domain
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.DerivationStyle
|
||||
import com.tangem.common.card.Card
|
||||
import com.tangem.common.card.EllipticCurve
|
||||
import com.tangem.common.card.FirmwareVersion
|
||||
import com.tangem.tap.domain.TapWorkarounds.isStart2Coin
|
||||
import com.tangem.tap.domain.TapWorkarounds.isTangemNote
|
||||
import com.tangem.tap.domain.extensions.getSingleWallet
|
||||
import com.tangem.tap.domain.twins.isTangemTwin
|
||||
import java.util.*
|
||||
|
||||
object TapWorkarounds {
|
||||
|
||||
fun isStart2CoinIssuer(cardIssuer: String?): Boolean {
|
||||
return cardIssuer?.lowercase(Locale.US) == START_2_COIN_ISSUER
|
||||
}
|
||||
|
||||
val Card.isStart2Coin: Boolean
|
||||
get() = isStart2CoinIssuer(issuer.name)
|
||||
|
||||
val Card.isTestCard: Boolean
|
||||
get() = batchId == TEST_CARD_BATCH && cardId.startsWith(TEST_CARD_ID_STARTS_WITH)
|
||||
|
||||
val Card.useOldStyleDerivation: Boolean
|
||||
get() = batchId == "AC01" || batchId == "AC02" || batchId == "CB95"
|
||||
|
||||
val Card.derivationStyle: DerivationStyle?
|
||||
get() = if (!settings.isHDWalletAllowed) {
|
||||
null
|
||||
} else if (useOldStyleDerivation) {
|
||||
DerivationStyle.LEGACY
|
||||
} else {
|
||||
DerivationStyle.NEW
|
||||
}
|
||||
|
||||
fun Card.isExcluded(): Boolean {
|
||||
val excludedBatch = excludedBatches.contains(batchId)
|
||||
val excludedIssuerName = excludedIssuers.contains(issuer.name.uppercase(Locale.ROOT))
|
||||
return excludedBatch || excludedIssuerName
|
||||
}
|
||||
|
||||
fun Card.isNotSupportedInThatRelease(): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
@Deprecated("Use ScanResponse.isTangemNote")
|
||||
fun isTangemNote(card: Card): Boolean = tangemNoteBatches.contains(card.batchId)
|
||||
|
||||
fun isTangemWalletBatch(card: Card): Boolean = tangemWalletBatches.contains(card.batchId)
|
||||
|
||||
fun getTangemNoteBlockchain(card: Card): Blockchain? = tangemNoteBatches[card.batchId]
|
||||
|
||||
private const val START_2_COIN_ISSUER = "start2coin"
|
||||
private const val TEST_CARD_BATCH = "99FF"
|
||||
private const val TEST_CARD_ID_STARTS_WITH = "FF99"
|
||||
|
||||
private val excludedBatches = listOf(
|
||||
"0027",
|
||||
"0030",
|
||||
"0031",
|
||||
"0035"
|
||||
)
|
||||
|
||||
private val excludedIssuers = listOf(
|
||||
"TTM BANK"
|
||||
)
|
||||
|
||||
private val tangemWalletBatches = listOf("AC01")
|
||||
|
||||
private val tangemNoteBatches = mapOf(
|
||||
"AB01" to Blockchain.Bitcoin,
|
||||
"AB02" to Blockchain.Ethereum,
|
||||
"AB03" to Blockchain.CardanoShelley,
|
||||
"AB04" to Blockchain.Dogecoin,
|
||||
"AB05" to Blockchain.BSC,
|
||||
"AB06" to Blockchain.XRP,
|
||||
"AB07" to Blockchain.Bitcoin,
|
||||
"AB08" to Blockchain.Ethereum,
|
||||
)
|
||||
}
|
||||
|
||||
val DELAY_SDK_DIALOG_CLOSE = 1400L
|
||||
|
||||
val Card.isMultiwalletAllowed: Boolean
|
||||
get() {
|
||||
return !isTangemTwin() && !isStart2Coin && !isTangemNote(this)
|
||||
&& (firmwareVersion >= FirmwareVersion.MultiWalletAvailable ||
|
||||
getSingleWallet()?.curve == EllipticCurve.Secp256k1)
|
||||
}
|
||||
|
|
@ -1,27 +1,32 @@
|
|||
package com.tangem.tap.domain.extensions
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.blockchain.common.WalletManagerFactory
|
||||
import com.tangem.blockchain.common.*
|
||||
import com.tangem.common.card.Card
|
||||
import com.tangem.common.card.CardWallet
|
||||
import com.tangem.common.card.EllipticCurve
|
||||
import com.tangem.common.extensions.hexToBytes
|
||||
import com.tangem.common.extensions.toMapKey
|
||||
import com.tangem.common.hdWallet.DerivationPath
|
||||
import com.tangem.domain.common.ScanResponse
|
||||
import com.tangem.domain.common.TapWorkarounds.isTestCard
|
||||
import com.tangem.domain.common.TapWorkarounds.useOldStyleDerivation
|
||||
import com.tangem.tap.domain.tokens.BlockchainNetwork
|
||||
import com.tangem.tap.features.wallet.redux.Currency
|
||||
|
||||
fun WalletManagerFactory.makeWalletManagerForApp(
|
||||
scanResponse: ScanResponse,
|
||||
blockchain: Blockchain
|
||||
blockchain: Blockchain,
|
||||
derivationParams: DerivationParams?
|
||||
): WalletManager? {
|
||||
val card = scanResponse.card
|
||||
if (card.isTestCard && blockchain.getTestnetVersion() == null) return null
|
||||
val supportedCurves = blockchain.getSupportedCurves() ?: return null
|
||||
val supportedCurves = blockchain.getSupportedCurves()
|
||||
|
||||
val wallets = card.wallets.filter { wallet -> supportedCurves.contains(wallet.curve) }
|
||||
val wallet = selectWallet(wallets) ?: return null
|
||||
|
||||
val environmentBlockchain = if (card.isTestCard) blockchain.getTestnetVersion()!! else blockchain
|
||||
val environmentBlockchain =
|
||||
if (card.isTestCard) blockchain.getTestnetVersion()!! else blockchain
|
||||
|
||||
val seedKey = wallet.extendedPublicKey
|
||||
return when {
|
||||
|
|
@ -32,16 +37,21 @@ fun WalletManagerFactory.makeWalletManagerForApp(
|
|||
environmentBlockchain, wallet.curve
|
||||
)
|
||||
}
|
||||
seedKey != null -> {
|
||||
seedKey != null && derivationParams != null -> {
|
||||
val derivedKeys = scanResponse.derivedKeys[wallet.publicKey.toMapKey()]
|
||||
val derivedKey = derivedKeys?.get(blockchain.derivationPath())
|
||||
val derivationPath = when (derivationParams) {
|
||||
is DerivationParams.Default -> blockchain.derivationPath(derivationParams.style)
|
||||
is DerivationParams.Custom -> derivationParams.path
|
||||
}
|
||||
val derivedKey = derivedKeys?.get(derivationPath)
|
||||
?: return null
|
||||
|
||||
makeWalletManager(
|
||||
cardId = card.cardId,
|
||||
blockchain = environmentBlockchain,
|
||||
seedKey = wallet.publicKey,
|
||||
derivedKey = derivedKey
|
||||
derivedKey = derivedKey,
|
||||
derivation = derivationParams
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
|
|
@ -55,12 +65,44 @@ fun WalletManagerFactory.makeWalletManagerForApp(
|
|||
}
|
||||
}
|
||||
|
||||
fun WalletManagerFactory.makeWalletManagerForApp(
|
||||
scanResponse: ScanResponse, blockchainNetwork: BlockchainNetwork
|
||||
): WalletManager? {
|
||||
return makeWalletManagerForApp(
|
||||
scanResponse,
|
||||
blockchain = blockchainNetwork.blockchain,
|
||||
derivationParams = getDerivationParams(blockchainNetwork.derivationPath, scanResponse.card)
|
||||
)
|
||||
}
|
||||
|
||||
private fun getDerivationParams(derivationPath: String?, card: Card): DerivationParams? {
|
||||
return derivationPath?.let {
|
||||
DerivationParams.Custom(
|
||||
DerivationPath(it)
|
||||
)
|
||||
} ?: if (!card.settings.isHDWalletAllowed) {
|
||||
null
|
||||
} else if (card.useOldStyleDerivation) {
|
||||
DerivationParams.Default(DerivationStyle.LEGACY)
|
||||
} else {
|
||||
DerivationParams.Default(DerivationStyle.NEW)
|
||||
}
|
||||
}
|
||||
|
||||
fun WalletManagerFactory.makeWalletManagerForApp(
|
||||
scanResponse: ScanResponse, currency: Currency
|
||||
): WalletManager? {
|
||||
return makeWalletManagerForApp(
|
||||
scanResponse,
|
||||
blockchain = currency.blockchain,
|
||||
derivationParams = getDerivationParams(currency.derivationPath, scanResponse.card)
|
||||
)
|
||||
}
|
||||
|
||||
fun WalletManagerFactory.makeWalletManagersForApp(
|
||||
scanResponse: ScanResponse, blockchains: List<Blockchain>,
|
||||
scanResponse: ScanResponse, blockchains: List<BlockchainNetwork>,
|
||||
): List<WalletManager> {
|
||||
val isTestCard = scanResponse.card.isTestCard
|
||||
val filteredBlockchains = blockchains.mapNotNull { if (isTestCard) it.getTestnetVersion() else it }
|
||||
return filteredBlockchains.mapNotNull { makeWalletManagerForApp(scanResponse, it) }
|
||||
return blockchains.mapNotNull { this.makeWalletManagerForApp(scanResponse, it) }
|
||||
}
|
||||
|
||||
fun WalletManagerFactory.makePrimaryWalletManager(
|
||||
|
|
@ -71,7 +113,12 @@ fun WalletManagerFactory.makePrimaryWalletManager(
|
|||
} else {
|
||||
scanResponse.getBlockchain()
|
||||
}
|
||||
return makeWalletManagerForApp(scanResponse, blockchain)
|
||||
val derivationParams = getDerivationParams(null, scanResponse.card)
|
||||
return makeWalletManagerForApp(
|
||||
scanResponse = scanResponse,
|
||||
blockchain = blockchain,
|
||||
derivationParams = derivationParams
|
||||
)
|
||||
}
|
||||
|
||||
private fun selectWallet(wallets: List<CardWallet>): CardWallet? {
|
||||
|
|
|
|||
|
|
@ -20,6 +20,10 @@ import com.tangem.operations.derivation.DeriveMultipleWalletPublicKeysTask
|
|||
import com.tangem.operations.derivation.ExtendedPublicKeysMap
|
||||
import com.tangem.operations.wallet.CreateWalletResponse
|
||||
import com.tangem.operations.wallet.CreateWalletTask
|
||||
import com.tangem.domain.common.ProductType
|
||||
import com.tangem.domain.common.TapWorkarounds.derivationStyle
|
||||
import com.tangem.domain.common.TapWorkarounds.getTangemNoteBlockchain
|
||||
import com.tangem.domain.common.TapWorkarounds.isTestCard
|
||||
import com.tangem.tap.domain.tasks.product.CreateWalletsTask
|
||||
import com.tangem.tap.domain.tasks.product.ProductCommandProcessor
|
||||
import com.tangem.tap.domain.tasks.product.getCurvesForNonCreatedWallets
|
||||
|
|
@ -177,7 +181,7 @@ private class CreateWalletTangemWallet : ProductCommandProcessor<CreateProductWa
|
|||
val blockchainsForCurve = getBlockchains(response.cardId).filter {
|
||||
it.getSupportedCurves().contains(response.wallet.curve)
|
||||
}
|
||||
val derivationPaths = blockchainsForCurve.mapNotNull { it.derivationPath() }
|
||||
val derivationPaths = blockchainsForCurve.mapNotNull { it.derivationPath(card.derivationStyle) }
|
||||
if (derivationPaths.isNotEmpty()) {
|
||||
map[response.wallet.publicKey.toMapKey()] = derivationPaths
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
package com.tangem.tap.domain.tasks.product
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.card.Card
|
||||
import com.tangem.common.card.EllipticCurve
|
||||
import com.tangem.common.card.FirmwareVersion
|
||||
import com.tangem.common.card.WalletData
|
||||
import com.tangem.common.core.CardSession
|
||||
import com.tangem.common.core.CardSessionRunnable
|
||||
import com.tangem.common.core.TangemError
|
||||
|
|
@ -25,11 +27,57 @@ import com.tangem.operations.backup.StartPrimaryCardLinkingTask
|
|||
import com.tangem.operations.derivation.DeriveMultipleWalletPublicKeysTask
|
||||
import com.tangem.operations.issuerAndUserData.ReadIssuerDataCommand
|
||||
import com.tangem.tap.domain.TapSdkError
|
||||
import com.tangem.domain.common.TapWorkarounds
|
||||
import com.tangem.domain.common.TapWorkarounds.derivationStyle
|
||||
import com.tangem.domain.common.TapWorkarounds.getTangemNoteBlockchain
|
||||
import com.tangem.domain.common.TapWorkarounds.isExcluded
|
||||
import com.tangem.domain.common.TapWorkarounds.isNotSupportedInThatRelease
|
||||
import com.tangem.domain.common.TapWorkarounds.useOldStyleDerivation
|
||||
import com.tangem.tap.domain.extensions.getPrimaryCurve
|
||||
import com.tangem.tap.domain.extensions.getSingleWallet
|
||||
import com.tangem.tap.domain.tokens.BlockchainNetwork
|
||||
import com.tangem.tap.domain.tokens.CurrenciesRepository
|
||||
import com.tangem.tap.preferencesStorage
|
||||
|
||||
data class 1ScanResponse(
|
||||
val card: Card,
|
||||
val productType: ProductType,
|
||||
val walletData: WalletData?,
|
||||
val secondTwinPublicKey: String? = null,
|
||||
val derivedKeys: Map<KeyWalletPublicKey, ExtendedPublicKeysMap> = mapOf(),
|
||||
val primaryCard: PrimaryCard? = null
|
||||
) : CommandResponse {
|
||||
|
||||
fun getBlockchain(): Blockchain {
|
||||
if (productType == ProductType.Note) return getTangemNoteBlockchain(card)
|
||||
?: return Blockchain.Unknown
|
||||
val blockchainName: String = walletData?.blockchain ?: return Blockchain.Unknown
|
||||
return Blockchain.fromId(blockchainName)
|
||||
}
|
||||
|
||||
fun getPrimaryToken(): Token? {
|
||||
val cardToken = walletData?.token ?: return null
|
||||
return Token(
|
||||
cardToken.name,
|
||||
cardToken.symbol,
|
||||
cardToken.contractAddress,
|
||||
cardToken.decimals,
|
||||
)
|
||||
}
|
||||
|
||||
fun isTangemNote(): Boolean = productType == ProductType.Note
|
||||
fun isTangemWallet(): Boolean = productType == ProductType.Wallet
|
||||
fun isTangemTwins(): Boolean = productType == ProductType.Twins
|
||||
|
||||
fun supportsHdWallet(): Boolean = card.settings.isHDWalletAllowed
|
||||
fun supportsBackup(): Boolean = card.settings.isBackupAllowed
|
||||
|
||||
fun twinsIsTwinned(): Boolean =
|
||||
card.isTangemTwins() && walletData != null && secondTwinPublicKey != null
|
||||
}
|
||||
|
||||
typealias KeyWalletPublicKey = ByteArrayKey
|
||||
|
||||
class ScanProductTask(
|
||||
val card: Card? = null,
|
||||
private val currenciesRepository: CurrenciesRepository?,
|
||||
|
|
@ -223,28 +271,38 @@ private class ScanWalletProcessor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun getBlockchainsToDerive(card: Card): List<Blockchain> {
|
||||
private fun getBlockchainsToDerive(card: Card): List<BlockchainNetwork> {
|
||||
val currenciesRepository = currenciesRepository ?: return emptyList()
|
||||
val cardCurrencies = currenciesRepository.loadCardCurrencies(card.cardId)
|
||||
val cardCurrencies = currenciesRepository.loadSavedCurrencies(card.cardId, card.derivationStyle).toMutableList()
|
||||
|
||||
val blockchainsToDerive = if (cardCurrencies == null) {
|
||||
mutableListOf(Blockchain.Bitcoin, Blockchain.Ethereum)
|
||||
} else {
|
||||
val tokenBlockchains = cardCurrencies.tokens.map { it.blockchain }
|
||||
(cardCurrencies.blockchains + tokenBlockchains).toMutableList()
|
||||
val blockchainsToDerive = cardCurrencies.ifEmpty {
|
||||
mutableListOf(
|
||||
BlockchainNetwork(Blockchain.Bitcoin, card),
|
||||
BlockchainNetwork(Blockchain.Ethereum, card))
|
||||
}
|
||||
|
||||
if (card.settings.isHDWalletAllowed) {
|
||||
blockchainsToDerive.addAll(
|
||||
listOf(
|
||||
Blockchain.Ethereum,
|
||||
Blockchain.Binance,
|
||||
Blockchain.EthereumTestnet
|
||||
BlockchainNetwork(Blockchain.Ethereum, card),
|
||||
BlockchainNetwork(Blockchain.Binance, card),
|
||||
BlockchainNetwork(Blockchain.EthereumTestnet, card)
|
||||
)
|
||||
)
|
||||
}
|
||||
if (additionalBlockchainsToDerive != null) {
|
||||
blockchainsToDerive.addAll(additionalBlockchainsToDerive)
|
||||
blockchainsToDerive.addAll(additionalBlockchainsToDerive.map { BlockchainNetwork(it, card) })
|
||||
}
|
||||
if (!card.useOldStyleDerivation) {
|
||||
blockchainsToDerive.removeAll(
|
||||
listOf(
|
||||
Blockchain.BSC, Blockchain.BSCTestnet,
|
||||
Blockchain.Polygon, Blockchain.PolygonTestnet,
|
||||
Blockchain.RSK,
|
||||
Blockchain.Fantom, Blockchain.FantomTestnet,
|
||||
Blockchain.Avalanche, Blockchain.AvalancheTestnet,
|
||||
).map { BlockchainNetwork(it, card) }
|
||||
)
|
||||
}
|
||||
return blockchainsToDerive.distinct()
|
||||
}
|
||||
|
|
@ -254,13 +312,13 @@ private class ScanWalletProcessor(
|
|||
val derivations = mutableMapOf<ByteArrayKey, List<DerivationPath>>()
|
||||
|
||||
blockchains.forEach { blockchain ->
|
||||
val curve = blockchain.getPrimaryCurve()
|
||||
val curve = blockchain.blockchain.getPrimaryCurve()
|
||||
|
||||
val wallet = card.wallets.firstOrNull { it.curve == curve } ?: return@forEach
|
||||
if (wallet.chainCode == null) return@forEach
|
||||
|
||||
val key = wallet.publicKey.toMapKey()
|
||||
val path = blockchain.derivationPath()
|
||||
val path = blockchain.derivationPath?.let { DerivationPath(it) }
|
||||
if (path != null) {
|
||||
val addedDerivations = derivations[key]
|
||||
if (addedDerivations != null) {
|
||||
|
|
|
|||
|
|
@ -7,15 +7,19 @@ import com.squareup.moshi.JsonAdapter
|
|||
import com.squareup.moshi.JsonClass
|
||||
import com.squareup.moshi.Types
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.DerivationStyle
|
||||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.common.card.Card
|
||||
import com.tangem.common.card.FirmwareVersion
|
||||
import com.tangem.network.common.MoshiConverter
|
||||
import com.tangem.tap.common.extensions.appendIf
|
||||
import com.tangem.tap.common.extensions.readJsonFileToString
|
||||
import com.tangem.tap.domain.extensions.getCustomIconUrl
|
||||
import com.tangem.tap.domain.TapWorkarounds.derivationStyle
|
||||
import com.tangem.tap.domain.extensions.setCustomIconUrl
|
||||
import com.tangem.tap.features.demo.DemoHelper
|
||||
import timber.log.Timber
|
||||
import java.util.*
|
||||
|
||||
class CurrenciesRepository(val context: Application) {
|
||||
|
||||
|
|
@ -29,52 +33,48 @@ class CurrenciesRepository(val context: Application) {
|
|||
private val obsoleteTokensAdapter: JsonAdapter<List<ObsoleteTokenDao>> = moshi.adapter(
|
||||
Types.newParameterizedType(List::class.java, ObsoleteTokenDao::class.java)
|
||||
)
|
||||
private val currenciesAdapter: JsonAdapter<CurrenciesFromJson> =
|
||||
moshi.adapter(CurrenciesFromJson::class.java)
|
||||
|
||||
fun loadCardCurrencies(cardId: String): CardCurrencies? {
|
||||
val blockchains = loadSavedBlockchains(cardId).toMutableSet()
|
||||
if (DemoHelper.isDemoCardId(cardId)) {
|
||||
blockchains.addAll(DemoHelper.config.demoBlockchains)
|
||||
private val blockchainNetworkAdapter: JsonAdapter<List<BlockchainNetwork>> =
|
||||
moshi.adapter(Types.newParameterizedType(List::class.java, BlockchainNetwork::class.java))
|
||||
|
||||
fun saveUpdatedCurrency(cardId: String, blockchainNetwork: BlockchainNetwork) {
|
||||
var changed = false
|
||||
val currencies = loadSavedCurrencies(cardId).map {
|
||||
if (it == blockchainNetwork) {
|
||||
changed = true
|
||||
blockchainNetwork
|
||||
} else {
|
||||
it
|
||||
}
|
||||
}
|
||||
if (blockchains.isEmpty()) return null
|
||||
|
||||
return CardCurrencies(loadSavedTokens(cardId), blockchains.toList())
|
||||
val updatedCurrencies = if (changed) currencies else currencies + blockchainNetwork
|
||||
saveCurrencies(cardId, updatedCurrencies.distinct())
|
||||
}
|
||||
|
||||
fun saveCardCurrencies(cardId: String, currencies: CardCurrencies) {
|
||||
saveTokens(cardId, currencies.tokens)
|
||||
saveBlockchains(cardId, currencies.blockchains)
|
||||
fun removeToken(cardId: String, token: Token, blockchainNetwork: BlockchainNetwork) {
|
||||
val currencies = loadSavedCurrencies(cardId).map {
|
||||
if (it == blockchainNetwork) {
|
||||
it.copy(tokens = it.tokens.filterNot { it == token })
|
||||
} else {
|
||||
it
|
||||
}
|
||||
}
|
||||
saveCurrencies(cardId, currencies)
|
||||
}
|
||||
|
||||
fun saveAddedToken(cardId: String, token: Token) {
|
||||
val tokens = loadSavedTokens(cardId) + token
|
||||
saveTokens(cardId, tokens)
|
||||
}
|
||||
|
||||
fun saveAddedTokens(cardId: String, tokens: Collection<Token>) {
|
||||
saveTokens(cardId, loadSavedTokens(cardId) + tokens.distinct())
|
||||
}
|
||||
|
||||
fun saveAddedBlockchain(cardId: String, blockchain: Blockchain) {
|
||||
val blockchains = loadSavedBlockchains(cardId) + blockchain
|
||||
saveBlockchains(cardId, blockchains.distinct())
|
||||
}
|
||||
|
||||
fun removeToken(cardId: String, token: Token) {
|
||||
val tokens = loadSavedTokens(cardId).filterNot { it == token }
|
||||
saveTokens(cardId, tokens)
|
||||
}
|
||||
|
||||
fun removeBlockchain(cardId: String, blockchain: Blockchain) {
|
||||
val blockchains = loadSavedBlockchains(cardId).filterNot { it == blockchain }
|
||||
saveBlockchains(cardId, blockchains)
|
||||
fun removeBlockchain(cardId: String, blockchainNetwork: BlockchainNetwork) {
|
||||
val currencies = loadSavedCurrencies(cardId).filterNot { it == blockchainNetwork }
|
||||
saveCurrencies(cardId, currencies)
|
||||
}
|
||||
|
||||
fun removeCurrencies(cardId: String) {
|
||||
saveTokens(cardId, emptyList())
|
||||
saveBlockchains(cardId, emptyList())
|
||||
saveCurrencies(cardId, emptyList())
|
||||
}
|
||||
|
||||
private fun loadSavedTokens(cardId: String): List<Token> {
|
||||
@Deprecated("Use BlockchainNetwork instead")
|
||||
private fun loadSavedTokens(cardId: String): List<TokenDao> {
|
||||
val json = try {
|
||||
context.readFileText(getFileNameForTokens(cardId))
|
||||
} catch (exception: Exception) {
|
||||
|
|
@ -82,17 +82,13 @@ class CurrenciesRepository(val context: Application) {
|
|||
}
|
||||
|
||||
return try {
|
||||
tokensAdapter.fromJson(json)!!.map { it.toToken() }.distinct()
|
||||
tokensAdapter.fromJson(json) ?: emptyList()
|
||||
} catch (exception: Exception) {
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
private fun saveTokens(cardId: String, tokens: List<Token>) {
|
||||
val json = tokensAdapter.toJson(tokens.distinct().map { TokenDao.fromToken(it) })
|
||||
context.rewriteFile(json, getFileNameForTokens(cardId))
|
||||
}
|
||||
|
||||
@Deprecated("Use BlockchainNetwork instead")
|
||||
private fun loadSavedBlockchains(cardId: String): List<Blockchain> {
|
||||
return try {
|
||||
val json = context.readFileText(getFileNameForBlockchains(cardId))
|
||||
|
|
@ -102,8 +98,61 @@ class CurrenciesRepository(val context: Application) {
|
|||
}
|
||||
}
|
||||
|
||||
private fun saveBlockchains(cardId: String, blockchains: List<Blockchain>) {
|
||||
val json = blockchainsAdapter.toJson(blockchains.distinct())
|
||||
fun loadSavedCurrencies(
|
||||
cardId: String,
|
||||
derivationStyle: DerivationStyle? = null
|
||||
): List<BlockchainNetwork> {
|
||||
if (DemoHelper.isDemoCardId(cardId)) {
|
||||
return loadDemoCurrencies(cardId)
|
||||
}
|
||||
return try {
|
||||
val json = context.readFileText(getFileNameForBlockchains(cardId))
|
||||
blockchainNetworkAdapter.fromJson(json)?.distinct() ?: loadSavedCurrenciesOldWay(
|
||||
cardId,
|
||||
derivationStyle
|
||||
)
|
||||
} catch (exception: Exception) {
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadDemoCurrencies(cardId: String): List<BlockchainNetwork> {
|
||||
return DemoHelper.config.demoBlockchains.map {
|
||||
BlockchainNetwork(
|
||||
blockchain = it,
|
||||
derivationPath = it.derivationPath(DerivationStyle.LEGACY)?.rawPath,
|
||||
tokens = emptyList()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadSavedCurrenciesOldWay(
|
||||
cardId: String, derivationStyle: DerivationStyle?
|
||||
): List<BlockchainNetwork> {
|
||||
val blockchains = loadSavedBlockchains(cardId)
|
||||
val tokens = loadSavedTokens(cardId)
|
||||
val currencies = getSupportedTokens()
|
||||
val blockchainNetworks = blockchains.map { blockchain ->
|
||||
BlockchainNetwork(
|
||||
blockchain = blockchain,
|
||||
derivationPath = blockchain.derivationPath(derivationStyle)?.rawPath,
|
||||
tokens = tokens
|
||||
.filter { it.blockchainDao.toBlockchain() == blockchain }
|
||||
.map {
|
||||
val token = it.toToken()
|
||||
val id = currencies
|
||||
.find { it.contracts?.find { it.address == token.contractAddress } != null }
|
||||
?.id
|
||||
token.copy(id = id)
|
||||
}
|
||||
)
|
||||
}
|
||||
saveCurrencies(cardId, blockchainNetworks) // migrate saved currencies
|
||||
return blockchainNetworks
|
||||
}
|
||||
|
||||
fun saveCurrencies(cardId: String, currencies: List<BlockchainNetwork>) {
|
||||
val json = blockchainNetworkAdapter.toJson(currencies)
|
||||
context.rewriteFile(json, getFileNameForBlockchains(cardId))
|
||||
}
|
||||
|
||||
|
|
@ -116,18 +165,10 @@ class CurrenciesRepository(val context: Application) {
|
|||
}
|
||||
}
|
||||
|
||||
fun getSupportedTokens(isTestNet: Boolean = false): List<Token> {
|
||||
val supportedTokens = if (isTestNet) {
|
||||
getSupportedTokens().mapNotNull { it.getTestnetVersion() }
|
||||
} else {
|
||||
getSupportedTokens()
|
||||
}
|
||||
|
||||
val blockchainTokens = supportedTokens.mapNotNull { blockchain ->
|
||||
loadTokensJson(blockchain)?.let { fromJsonToTokensDao(it, blockchain) }
|
||||
}
|
||||
val tokens = blockchainTokens.flatten().map { it.toToken() }
|
||||
return tokens
|
||||
fun getSupportedTokens(isTestNet: Boolean = false): List<Currency> {
|
||||
val fileName = if (isTestNet) "testnet_tokens" else "tokens"
|
||||
val json = context.assets.readJsonFileToString(fileName)
|
||||
return currenciesAdapter.fromJson(json)!!.tokens.map { Currency.fromJsonObject(it) }
|
||||
}
|
||||
|
||||
private fun loadTokensJson(blockchain: Blockchain): String? {
|
||||
|
|
@ -142,7 +183,7 @@ class CurrenciesRepository(val context: Application) {
|
|||
|
||||
private fun getFileName(blockchain: Blockchain): String {
|
||||
return StringBuilder().apply {
|
||||
append(blockchain.id.toLowerCase().replace("/test", ""))
|
||||
append(blockchain.id.lowercase(Locale.getDefault()).replace("/test", ""))
|
||||
append("_tokens")
|
||||
appendIf("_testnet") { blockchain.isTestnet() }
|
||||
}.toString()
|
||||
|
|
@ -152,17 +193,6 @@ class CurrenciesRepository(val context: Application) {
|
|||
return obsoleteTokensAdapter.fromJson(tokenJson)!!.map { it.toTokenDao(blockchain) }
|
||||
}
|
||||
|
||||
private fun getSupportedTokens(): List<Blockchain> {
|
||||
return listOf(
|
||||
Blockchain.Ethereum,
|
||||
Blockchain.BSC,
|
||||
Blockchain.Binance,
|
||||
Blockchain.Polygon,
|
||||
Blockchain.Avalanche,
|
||||
Blockchain.Fantom,
|
||||
)
|
||||
}
|
||||
|
||||
fun getBlockchains(
|
||||
cardFirmware: FirmwareVersion,
|
||||
isTestNet: Boolean = false
|
||||
|
|
@ -178,9 +208,11 @@ class CurrenciesRepository(val context: Application) {
|
|||
// Use this list to temporarily exclude a blockchain from the list of tokens.
|
||||
private fun excludeUnsupportedBlockchains(blockchains: List<Blockchain>): List<Blockchain> {
|
||||
return blockchains.toMutableList().apply {
|
||||
removeAll(listOf(
|
||||
removeAll(
|
||||
listOf(
|
||||
// Any blockchain
|
||||
))
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -218,24 +250,11 @@ data class TokenDao(
|
|||
symbol = symbol,
|
||||
contractAddress = contractAddress,
|
||||
decimals = decimalCount,
|
||||
blockchain = blockchainDao.toBlockchain()
|
||||
).apply {
|
||||
customIconUrl?.let { this.setCustomIconUrl(it) }
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun fromToken(token: Token): TokenDao {
|
||||
return TokenDao(
|
||||
name = token.name,
|
||||
symbol = token.symbol,
|
||||
contractAddress = token.contractAddress,
|
||||
decimalCount = token.decimals,
|
||||
blockchainDao = BlockchainDao.fromBlockchain(token.blockchain),
|
||||
customIconUrl = token.getCustomIconUrl()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
|
|
@ -280,29 +299,78 @@ data class ObsoleteTokenDao(
|
|||
}
|
||||
}
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class CardCurrenciesDao(
|
||||
val tokens: List<TokenDao>,
|
||||
val blockchains: List<Blockchain>,
|
||||
) {
|
||||
fun toCardCurrencies(): CardCurrencies {
|
||||
return CardCurrencies(
|
||||
tokens = tokens.map { it.toToken() }.distinct(),
|
||||
blockchains = blockchains
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun fromCardCurrencies(cardCurrencies: CardCurrencies): CardCurrenciesDao {
|
||||
return CardCurrenciesDao(
|
||||
tokens = cardCurrencies.tokens.map { TokenDao.fromToken(it) }.distinct(),
|
||||
blockchains = cardCurrencies.blockchains
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
//@JsonClass(generateAdapter = true)
|
||||
//data class CardCurrenciesDao(
|
||||
// val tokens: List<TokenDao>,
|
||||
// val blockchains: List<Blockchain>,
|
||||
//) {
|
||||
// fun toCardCurrencies(): CardCurrencies {
|
||||
// return CardCurrencies(
|
||||
// tokens = tokens.map { it.toToken() }.distinct(),
|
||||
// blockchains = blockchains
|
||||
// )
|
||||
// }
|
||||
//
|
||||
// companion object {
|
||||
// fun fromCardCurrencies(cardCurrencies: CardCurrencies): CardCurrenciesDao {
|
||||
// return CardCurrenciesDao(
|
||||
// tokens = cardCurrencies.tokens.map { TokenDao.fromToken(it) }.distinct(),
|
||||
// blockchains = cardCurrencies.blockchains
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
||||
data class CardCurrencies(
|
||||
val tokens: List<Token>,
|
||||
val blockchains: List<Blockchain>,
|
||||
)
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class BlockchainNetwork(
|
||||
val blockchain: Blockchain,
|
||||
val derivationPath: String?,
|
||||
val tokens: List<Token>
|
||||
) {
|
||||
|
||||
constructor(blockchain: Blockchain, card: Card) : this(
|
||||
blockchain = blockchain,
|
||||
derivationPath = if (card.settings.isHDWalletAllowed) blockchain.derivationPath(card.derivationStyle)?.rawPath else null,
|
||||
tokens = emptyList()
|
||||
)
|
||||
|
||||
|
||||
fun updateTokens(tokens: List<Token>): BlockchainNetwork {
|
||||
return copy(
|
||||
tokens = (this.tokens + tokens).distinct()
|
||||
)
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as BlockchainNetwork
|
||||
|
||||
if (blockchain != other.blockchain) return false
|
||||
if (derivationPath != other.derivationPath) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = blockchain.hashCode()
|
||||
result = 31 * result + (derivationPath?.hashCode() ?: 0)
|
||||
return result
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun fromWalletManager(walletManager: WalletManager): BlockchainNetwork {
|
||||
return BlockchainNetwork(
|
||||
walletManager.wallet.blockchain,
|
||||
walletManager.wallet.publicKey.derivationPath?.rawPath,
|
||||
walletManager.cardTokens.toList()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
135
app/src/main/java/com/tangem/tap/domain/tokens/Currency.kt
Normal file
135
app/src/main/java/com/tangem/tap/domain/tokens/Currency.kt
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
package com.tangem.tap.domain.tokens
|
||||
|
||||
import com.squareup.moshi.JsonClass
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class CurrencyFromJson(
|
||||
val id: String,
|
||||
val name: String,
|
||||
val symbol: String,
|
||||
val contracts: List<ContractFromJson>? = null
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class ContractFromJson(
|
||||
val networkId: String,
|
||||
val address: String,
|
||||
val decimalCount: Int
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class CurrenciesFromJson(
|
||||
val imageHost: String,
|
||||
val tokens: List<CurrencyFromJson>
|
||||
)
|
||||
|
||||
|
||||
fun List<ContractFromJson>.toContracts(): List<Contract> {
|
||||
return mapNotNull { Contract.fromJsonObject(it) }
|
||||
}
|
||||
|
||||
data class Currency(
|
||||
val id: String,
|
||||
val name: String,
|
||||
val symbol: String,
|
||||
val iconUrl: String,
|
||||
val contracts: List<Contract>?
|
||||
) {
|
||||
|
||||
companion object {
|
||||
fun fromJsonObject(currency: CurrencyFromJson): Currency {
|
||||
return Currency(
|
||||
id = currency.id,
|
||||
name = currency.name,
|
||||
symbol = currency.symbol,
|
||||
iconUrl = getIconUrl(currency.id),
|
||||
contracts = currency.contracts?.toContracts()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class Contract(
|
||||
val networkId: String,
|
||||
val blockchain: Blockchain,
|
||||
val address: String,
|
||||
val decimalCount: Int,
|
||||
val iconUrl: String,
|
||||
) {
|
||||
|
||||
companion object {
|
||||
fun fromJsonObject(contract: ContractFromJson): Contract? {
|
||||
val blockchain = Blockchain.fromNetworkId(contract.networkId) ?: return null
|
||||
return Contract(
|
||||
networkId = contract.networkId,
|
||||
blockchain = blockchain,
|
||||
address = contract.address,
|
||||
decimalCount = contract.decimalCount,
|
||||
iconUrl = getIconUrl(contract.networkId)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getIconUrl(id: String): String {
|
||||
return "https://s3.eu-central-1.amazonaws.com/tangem.api/coins/large/$id.png"
|
||||
}
|
||||
|
||||
|
||||
fun Blockchain.Companion.fromNetworkId(networkId: String): Blockchain? {
|
||||
return when (networkId) {
|
||||
"avalanche" -> Blockchain.Avalanche
|
||||
"binancecoin" -> Blockchain.Binance
|
||||
"binance-smart-chain" -> Blockchain.BSC
|
||||
"ethereum" -> Blockchain.Ethereum
|
||||
"polygon-pos" -> Blockchain.Polygon
|
||||
"solana" -> Blockchain.Solana
|
||||
"fantom" -> Blockchain.Fantom
|
||||
"bitcoin" -> Blockchain.Bitcoin
|
||||
"bitcoin-cash" -> Blockchain.BitcoinCash
|
||||
"cardano" -> Blockchain.CardanoShelley
|
||||
"dogecoin" -> Blockchain.Dogecoin
|
||||
"ducatus" -> Blockchain.Ducatus
|
||||
"litecoin" -> Blockchain.Litecoin
|
||||
"rsk" -> Blockchain.RSK
|
||||
"stellar" -> Blockchain.Stellar
|
||||
"tezos" -> Blockchain.Tezos
|
||||
"ripple" -> Blockchain.XRP
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
fun Blockchain.toNetworkId(): String {
|
||||
return when (this) {
|
||||
Blockchain.Unknown -> "unknown"
|
||||
Blockchain.Avalanche -> "avalanche"
|
||||
Blockchain.AvalancheTestnet -> "avalaunche"
|
||||
Blockchain.Binance -> "binancecoin"
|
||||
Blockchain.BinanceTestnet -> "binancecoin"
|
||||
Blockchain.BSC -> "binance-smart-chain"
|
||||
Blockchain.BSCTestnet -> "binance-smart-chain"
|
||||
Blockchain.Bitcoin -> "bitcoin"
|
||||
Blockchain.BitcoinTestnet -> "bitcoin"
|
||||
Blockchain.BitcoinCash -> "bitcoin-cash"
|
||||
Blockchain.BitcoinCashTestnet -> "bitcoin-cash"
|
||||
Blockchain.Cardano -> "cardano"
|
||||
Blockchain.CardanoShelley -> "cardano"
|
||||
Blockchain.Dogecoin -> "dogecoin"
|
||||
Blockchain.Ducatus -> "ducatus"
|
||||
Blockchain.Ethereum -> "ethereum"
|
||||
Blockchain.EthereumTestnet -> "ethereum"
|
||||
Blockchain.Fantom -> "fantom"
|
||||
Blockchain.FantomTestnet -> "fantom"
|
||||
Blockchain.Litecoin -> "litecoin"
|
||||
Blockchain.Polygon -> "matic-network"
|
||||
Blockchain.PolygonTestnet -> "matic-networks"
|
||||
Blockchain.RSK -> "rootstock"
|
||||
Blockchain.Stellar -> "stellar"
|
||||
Blockchain.StellarTestnet -> "stellar"
|
||||
Blockchain.Solana -> "solana"
|
||||
Blockchain.SolanaTestnet -> "solana"
|
||||
Blockchain.Tezos -> "tezos"
|
||||
Blockchain.XRP -> "ripple"
|
||||
}
|
||||
}
|
||||
|
|
@ -36,7 +36,7 @@ internal class DemoOnboardingNoteMiddleware : DemoMiddleware {
|
|||
val balanceAmount = config.getBalance(walletManager.wallet.blockchain)
|
||||
val loadedBalance = noteState.walletBalance.copy(
|
||||
value = balanceAmount.value!!,
|
||||
currency = Currency.Blockchain(walletManager.wallet.blockchain),
|
||||
currency = Currency.Blockchain(walletManager.wallet.blockchain, null),
|
||||
state = ProgressState.Done,
|
||||
error = null,
|
||||
criticalError = null
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
package com.tangem.tap.features.details.redux.walletconnect
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.DerivationParams
|
||||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.domain.common.ScanResponse
|
||||
import com.tangem.domain.common.TapWorkarounds.derivationStyle
|
||||
import com.tangem.domain.common.TapWorkarounds.isTestCard
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.extensions.getFromClipboard
|
||||
|
|
@ -14,10 +16,14 @@ import com.tangem.tap.common.redux.navigation.NavigationAction
|
|||
import com.tangem.tap.currenciesRepository
|
||||
import com.tangem.tap.domain.extensions.isMultiwalletAllowed
|
||||
import com.tangem.tap.domain.extensions.makeWalletManagerForApp
|
||||
import com.tangem.tap.domain.isMultiwalletAllowed
|
||||
import com.tangem.tap.domain.tasks.product.ScanResponse
|
||||
import com.tangem.tap.domain.tokens.BlockchainNetwork
|
||||
import com.tangem.tap.domain.walletconnect.BnbHelper
|
||||
import com.tangem.tap.domain.walletconnect.WalletConnectManager
|
||||
import com.tangem.tap.domain.walletconnect.WalletConnectNetworkUtils
|
||||
import com.tangem.tap.features.demo.DemoHelper
|
||||
import com.tangem.tap.features.wallet.redux.Currency
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
|
|
@ -164,14 +170,16 @@ class WalletConnectMiddleware {
|
|||
chainId = chainId, peer = session.peerMeta
|
||||
) ?: Blockchain.Ethereum
|
||||
|
||||
store.dispatch(GlobalAction.ScanCard(additionalBlockchainsToDerive = listOf(blockchain),
|
||||
onSuccess = { scanResponse ->
|
||||
handleScanResponse(scanResponse, session, blockchain)
|
||||
},
|
||||
onFailure = {
|
||||
store.dispatchOnMain(WalletConnectAction.FailureEstablishingSession(null))
|
||||
}, R.string.wallet_connect_scan_card_message
|
||||
)
|
||||
store.dispatch(
|
||||
GlobalAction.ScanCard(
|
||||
additionalBlockchainsToDerive = listOf(blockchain),
|
||||
onSuccess = { scanResponse ->
|
||||
handleScanResponse(scanResponse, session, blockchain)
|
||||
},
|
||||
onFailure = {
|
||||
store.dispatchOnMain(WalletConnectAction.FailureEstablishingSession(null))
|
||||
}, R.string.wallet_connect_scan_card_message
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -229,22 +237,35 @@ class WalletConnectMiddleware {
|
|||
}
|
||||
|
||||
return if (store.state.globalState.scanResponse?.card?.cardId == card.cardId) {
|
||||
store.state.walletState.getWalletManager(blockchainToMake)
|
||||
?: factory.makeWalletManagerForApp(scanResponse, blockchainToMake)
|
||||
val derivationPath = blockchainToMake.derivationPath(card.derivationStyle)?.rawPath
|
||||
store.state.walletState.getWalletManager(
|
||||
Currency.Blockchain(blockchainToMake, derivationPath)
|
||||
)
|
||||
?: factory.makeWalletManagerForApp(
|
||||
scanResponse,
|
||||
blockchainToMake,
|
||||
card.derivationStyle?.let { DerivationParams.Default(it) }
|
||||
)
|
||||
?.also { walletManager ->
|
||||
store.dispatch(WalletAction.MultiWallet.AddWalletManagers(walletManager))
|
||||
store.dispatch(WalletAction.MultiWallet.AddBlockchain(walletManager.wallet.blockchain))
|
||||
store.dispatch(
|
||||
WalletAction.MultiWallet.AddBlockchain(
|
||||
BlockchainNetwork.fromWalletManager(walletManager), walletManager
|
||||
)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
val walletManager = factory.makeWalletManagerForApp(scanResponse, blockchainToMake)
|
||||
if (currenciesRepository.loadCardCurrencies(card.cardId)?.blockchains?.contains(
|
||||
blockchainToMake
|
||||
) != true
|
||||
val walletManager = factory.makeWalletManagerForApp(
|
||||
scanResponse,
|
||||
blockchainToMake,
|
||||
card.derivationStyle?.let { DerivationParams.Default(it) }
|
||||
)
|
||||
if (currenciesRepository.loadSavedCurrencies(card.cardId, card.derivationStyle)
|
||||
.find { it.blockchain == blockchainToMake } != null
|
||||
) {
|
||||
walletManager?.let {
|
||||
currenciesRepository.saveAddedBlockchain(
|
||||
currenciesRepository.saveUpdatedCurrency(
|
||||
card.cardId,
|
||||
blockchainToMake
|
||||
BlockchainNetwork.fromWalletManager(walletManager)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -106,6 +106,7 @@ class AdditionalEmailInfo {
|
|||
var address: String = "",
|
||||
var explorerLink: String = "",
|
||||
var host: String = "",
|
||||
var derivationPath: String = "",
|
||||
)
|
||||
|
||||
var appVersion: String = ""
|
||||
|
|
@ -159,7 +160,8 @@ class AdditionalEmailInfo {
|
|||
blockchain = manager.wallet.blockchain,
|
||||
address = getAddress(manager.wallet),
|
||||
explorerLink = getExploreUri(manager.wallet),
|
||||
host = manager.currentHost
|
||||
host = manager.currentHost,
|
||||
derivationPath = manager.wallet.publicKey.derivationPath?.rawPath ?: ""
|
||||
)
|
||||
)
|
||||
if (manager.cardTokens.isNotEmpty()) {
|
||||
|
|
@ -173,7 +175,8 @@ class AdditionalEmailInfo {
|
|||
blockchain = wallet.blockchain,
|
||||
address = getAddress(wallet),
|
||||
explorerLink = getExploreUri(wallet),
|
||||
host = host
|
||||
host = host,
|
||||
derivationPath = wallet.publicKey.derivationPath?.rawPath ?: ""
|
||||
)
|
||||
|
||||
this.destinationAddress = destinationAddress
|
||||
|
|
@ -325,6 +328,7 @@ class EmailDataBuilder(
|
|||
builder.appendKeyValue("Blockchain", it.blockchain.fullName)
|
||||
builder.appendKeyValue("Host", it.host)
|
||||
builder.appendKeyValue("Wallet address", it.address)
|
||||
builder.appendKeyValue("Derivation path", it.derivationPath)
|
||||
builder.appendKeyValue("Explorer link", it.explorerLink)
|
||||
|
||||
infoHolder.tokens[it.blockchain]?.let { tokens ->
|
||||
|
|
@ -332,6 +336,7 @@ class EmailDataBuilder(
|
|||
appendLine()
|
||||
tokens.forEach { token ->
|
||||
builder.appendKeyValue("Name", token.name)
|
||||
if (token.id != null) builder.appendKeyValue("Id", token.id ?: "")
|
||||
builder.appendKeyValue("Contract address", token.contractAddress)
|
||||
}
|
||||
}
|
||||
|
|
@ -342,6 +347,7 @@ class EmailDataBuilder(
|
|||
fun appendTxFailedBlockchainInfo(error: String): EmailDataBuilder {
|
||||
val walletInfo = infoHolder.onSendErrorWalletInfo ?: AdditionalEmailInfo.EmailWalletInfo()
|
||||
builder.appendKeyValue("Blockchain", walletInfo.blockchain.fullName)
|
||||
builder.appendKeyValue("Derivation path", walletInfo.derivationPath)
|
||||
builder.appendKeyValue("Host", walletInfo.host)
|
||||
builder.appendKeyValue("Token", infoHolder.token)
|
||||
builder.appendKeyValue("Error", error)
|
||||
|
|
|
|||
|
|
@ -7,10 +7,13 @@ import androidx.compose.runtime.mutableStateOf
|
|||
import androidx.compose.ui.platform.ComposeView
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.google.accompanist.appcompattheme.AppCompatTheme
|
||||
import com.tangem.tap.common.redux.navigation.AppScreen
|
||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
import com.tangem.tap.features.home.compose.StoriesScreen
|
||||
import com.tangem.tap.features.home.redux.HomeAction
|
||||
import com.tangem.tap.features.home.redux.HomeState
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupAction
|
||||
import com.tangem.tap.features.tokens.redux.TokensAction
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
import org.rekotlin.StoreSubscriber
|
||||
|
|
@ -35,7 +38,12 @@ class HomeFragment : Fragment(R.layout.fragment_home), StoreSubscriber<HomeState
|
|||
StoriesScreen(
|
||||
homeState,
|
||||
onScanButtonClick = { store.dispatch(HomeAction.ReadCard) },
|
||||
onShopButtonClick = { store.dispatch(HomeAction.GoToShop(getRegionProvider())) }
|
||||
onShopButtonClick = { store.dispatch(HomeAction.GoToShop(getRegionProvider())) },
|
||||
onSearchTokensClick = {
|
||||
store.dispatch(TokensAction.AllowToAddTokens(false))
|
||||
store.dispatch(TokensAction.LoadCurrencies())
|
||||
store.dispatch(NavigationAction.NavigateTo(AppScreen.AddTokens))
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,9 @@ import androidx.compose.foundation.Image
|
|||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.gestures.detectTapGestures
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.Button
|
||||
import androidx.compose.material.ButtonDefaults
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
|
|
@ -16,9 +19,13 @@ import androidx.compose.ui.graphics.ColorFilter
|
|||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.tap.common.compose.SpacerS24
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.tangem.tap.features.home.redux.HomeState
|
||||
import com.tangem.tap.features.wallet.redux.ProgressState
|
||||
import com.tangem.wallet.R
|
||||
|
|
@ -28,7 +35,8 @@ import kotlin.math.max
|
|||
fun StoriesScreen(
|
||||
homeState: MutableState<HomeState> = mutableStateOf(HomeState()),
|
||||
onScanButtonClick: () -> Unit,
|
||||
onShopButtonClick: () -> Unit
|
||||
onShopButtonClick: () -> Unit,
|
||||
onSearchTokensClick: () -> Unit,
|
||||
) {
|
||||
val steps = 6
|
||||
val stepDuration = 8_000
|
||||
|
|
@ -78,7 +86,8 @@ fun StoriesScreen(
|
|||
}
|
||||
)
|
||||
Box(
|
||||
Modifier.weight(1f)
|
||||
Modifier
|
||||
.weight(1f)
|
||||
.fillMaxHeight()
|
||||
.pointerInput(isPressed) {
|
||||
detectTapGestures(
|
||||
|
|
@ -107,7 +116,7 @@ fun StoriesScreen(
|
|||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
SpacerS24()
|
||||
StoriesProgressBar(
|
||||
|
|
@ -138,15 +147,44 @@ fun StoriesScreen(
|
|||
6 -> StoriesWalletForEveryone()
|
||||
}
|
||||
}
|
||||
HomeButtons(
|
||||
isDarkBackground = isDarkBackground,
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomCenter)
|
||||
.padding(16.dp, 0.dp, 16.dp, 37.dp)
|
||||
.fillMaxWidth(),
|
||||
onScanButtonClick = onScanButtonClick,
|
||||
onShopButtonClick = onShopButtonClick
|
||||
)
|
||||
.fillMaxWidth()
|
||||
) {
|
||||
if (currentStep.value == 4) Button(
|
||||
onClick = onSearchTokensClick,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(start = 16.dp, end = 16.dp, bottom = 16.dp)
|
||||
.height(48.dp)
|
||||
,
|
||||
colors = ButtonDefaults.textButtonColors(
|
||||
backgroundColor = Color.White,
|
||||
contentColor = Color(0xFF080C10)
|
||||
)
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(id = R.drawable.ic_search),
|
||||
contentDescription = null
|
||||
)
|
||||
Text(
|
||||
text = stringResource(id = R.string.search_tokens_title),
|
||||
fontWeight = FontWeight.Medium,
|
||||
fontSize = 16.sp,
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
}
|
||||
HomeButtons(
|
||||
isDarkBackground = isDarkBackground,
|
||||
modifier = Modifier
|
||||
.padding(start = 16.dp, top = 0.dp, end = 16.dp, bottom = 37.dp)
|
||||
.fillMaxWidth(),
|
||||
onScanButtonClick = onScanButtonClick,
|
||||
onShopButtonClick = onShopButtonClick
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -154,5 +192,5 @@ fun StoriesScreen(
|
|||
@Preview
|
||||
@Composable
|
||||
fun InstagramScreenPreview() {
|
||||
StoriesScreen(onScanButtonClick = {}, onShopButtonClick = {})
|
||||
StoriesScreen(onScanButtonClick = {}, onShopButtonClick = {}, onSearchTokensClick = {})
|
||||
}
|
||||
|
|
@ -67,7 +67,9 @@ class OnboardingManager(
|
|||
}
|
||||
}
|
||||
|
||||
return balance.copy(currency = Currency.Blockchain(walletManager.wallet.blockchain))
|
||||
return balance.copy(currency = Currency.Blockchain(
|
||||
walletManager.wallet.blockchain, walletManager.wallet.publicKey.derivationPath?.rawPath)
|
||||
)
|
||||
}
|
||||
|
||||
fun activationStarted(cardId: String) {
|
||||
|
|
@ -81,7 +83,7 @@ class OnboardingManager(
|
|||
|
||||
data class OnboardingWalletBalance(
|
||||
val value: BigDecimal = BigDecimal.ZERO,
|
||||
val currency: Currency.Blockchain = Currency.Blockchain(Blockchain.Unknown),
|
||||
val currency: Currency.Blockchain = Currency.Blockchain(Blockchain.Unknown, null),
|
||||
val hasIncomingTransaction: Boolean = false,
|
||||
val state: ProgressState,
|
||||
val error: TapError? = null,
|
||||
|
|
|
|||
|
|
@ -113,7 +113,10 @@ private fun handleNoteAction(appState: () -> AppState?, action: Action, dispatch
|
|||
|
||||
val isLoadedBefore = noteState.walletBalance.state != ProgressState.Loading
|
||||
val balanceIsLoading = noteState.walletBalance.copy(
|
||||
currency = Currency.Blockchain(walletManager.wallet.blockchain),
|
||||
currency = Currency.Blockchain(
|
||||
walletManager.wallet.blockchain,
|
||||
walletManager.wallet.publicKey.derivationPath?.rawPath
|
||||
),
|
||||
state = ProgressState.Loading,
|
||||
error = null,
|
||||
criticalError = null
|
||||
|
|
|
|||
|
|
@ -215,7 +215,10 @@ private fun handle(action: Action, dispatch: DispatchFunction) {
|
|||
}
|
||||
val isLoadedBefore = twinCardsState.walletBalance.state != ProgressState.Loading
|
||||
val balanceIsLoading = twinCardsState.walletBalance.copy(
|
||||
currency = Currency.Blockchain(walletManager.wallet.blockchain),
|
||||
currency = Currency.Blockchain(
|
||||
walletManager.wallet.blockchain,
|
||||
walletManager.wallet.publicKey.derivationPath?.rawPath
|
||||
),
|
||||
state = ProgressState.Loading,
|
||||
error = null,
|
||||
criticalError = null
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import com.tangem.tap.domain.TangemSigner
|
|||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
|
||||
import com.tangem.tap.domain.extensions.minimalAmount
|
||||
import com.tangem.tap.domain.tokens.BlockchainNetwork
|
||||
import com.tangem.tap.features.demo.DemoTransactionSender
|
||||
import com.tangem.tap.features.demo.isDemoWallet
|
||||
import com.tangem.tap.features.send.redux.*
|
||||
|
|
@ -203,11 +204,11 @@ private fun sendTransaction(
|
|||
}
|
||||
scope.launch(Dispatchers.IO) {
|
||||
withContext(Dispatchers.Main) {
|
||||
dispatch(WalletAction.LoadWallet(walletManager.wallet.blockchain))
|
||||
dispatch(WalletAction.LoadWallet(BlockchainNetwork.fromWalletManager(walletManager)))
|
||||
}
|
||||
delay(11000) // more than 10000 to avoid throttling
|
||||
withContext(Dispatchers.Main) {
|
||||
dispatch(WalletAction.LoadWallet(walletManager.wallet.blockchain))
|
||||
dispatch(WalletAction.LoadWallet(BlockchainNetwork.fromWalletManager(walletManager)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,89 +0,0 @@
|
|||
package com.tangem.tap.features.tokens.redux
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.wallet.R
|
||||
|
||||
sealed class CurrencyListItem {
|
||||
var isAdded: Boolean = false
|
||||
var isLocked: Boolean = false
|
||||
|
||||
data class TokenListItem(val token: Token) : CurrencyListItem()
|
||||
data class BlockchainListItem(val blockchain: Blockchain) : CurrencyListItem()
|
||||
|
||||
data class TitleListItem(
|
||||
@StringRes val titleResId: Int,
|
||||
var isContentShown: Boolean = true,
|
||||
val blockchain: Blockchain? = null,
|
||||
) : CurrencyListItem()
|
||||
|
||||
companion object {
|
||||
fun createListOfCurrencies(
|
||||
blockchains: List<Blockchain>,
|
||||
tokens: List<Token>,
|
||||
): List<CurrencyListItem> {
|
||||
val blockchainsItemList = createBlockchainList(blockchains)
|
||||
val tokenItemsLists = tokens.groupBy { it.blockchain }.map {
|
||||
createTokensList(R.string.add_tokens_subtitle_format, it.key, it.value)
|
||||
}
|
||||
return blockchainsItemList + tokenItemsLists.flatten()
|
||||
}
|
||||
|
||||
private fun createBlockchainList(blockchains: List<Blockchain>): List<CurrencyListItem> {
|
||||
val blockchainsTitle = R.string.add_tokens_subtitle_blockchains
|
||||
return listOf(TitleListItem(blockchainsTitle)) + blockchains.map { BlockchainListItem(it) }
|
||||
}
|
||||
|
||||
private fun createTokensList(
|
||||
@StringRes titleResId: Int,
|
||||
blockchain: Blockchain,
|
||||
tokens: List<Token>
|
||||
): List<CurrencyListItem> {
|
||||
val filteredTokens = tokens.filter {
|
||||
it.blockchain == blockchain || it.blockchain == blockchain.getTestnetVersion()
|
||||
}
|
||||
val tokensListItem = filteredTokens.map { TokenListItem(it) }
|
||||
return listOf(TitleListItem(titleResId, blockchain = blockchain)) + tokensListItem
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun List<CurrencyListItem>.removeTokensForBlockchain(blockchain: Blockchain): List<CurrencyListItem> {
|
||||
return filterNot {
|
||||
it is CurrencyListItem.TokenListItem && it.isTheSameOrTestnetBlockchain(blockchain)
|
||||
}
|
||||
}
|
||||
|
||||
fun List<CurrencyListItem>.addTokensForBlockchain(
|
||||
blockchain: Blockchain, fullCurrenciesList: List<CurrencyListItem>,
|
||||
): List<CurrencyListItem> {
|
||||
val tokensToAdd = fullCurrenciesList.filter {
|
||||
it is CurrencyListItem.TokenListItem && it.isTheSameOrTestnetBlockchain(blockchain)
|
||||
}
|
||||
val indexToInsert = indexOfFirst {
|
||||
it is CurrencyListItem.TitleListItem && it.isTheSameOrTestnetBlockchain(blockchain)
|
||||
} + 1
|
||||
return this.toMutableList().apply { addAll(indexToInsert, tokensToAdd) }
|
||||
}
|
||||
|
||||
fun List<CurrencyListItem>.toggleHeaderContentShownValue(blockchain: Blockchain) {
|
||||
map {
|
||||
if (it is CurrencyListItem.TitleListItem && it.isTheSameOrTestnetBlockchain(blockchain)) {
|
||||
it.isContentShown = !it.isContentShown
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if you do not check the testNet blockchains, it will not collapse
|
||||
private fun CurrencyListItem.isTheSameOrTestnetBlockchain(blockchain: Blockchain): Boolean {
|
||||
return when (this) {
|
||||
is CurrencyListItem.TitleListItem -> this.blockchain?.isTheSameOrTestnetBlockchain(blockchain) ?: false
|
||||
is CurrencyListItem.BlockchainListItem -> this.blockchain.isTheSameOrTestnetBlockchain(blockchain)
|
||||
is CurrencyListItem.TokenListItem -> this.token.blockchain.isTheSameOrTestnetBlockchain(blockchain)
|
||||
}
|
||||
}
|
||||
|
||||
private fun Blockchain.isTheSameOrTestnetBlockchain(blockchain: Blockchain): Boolean {
|
||||
return this == blockchain || this == blockchain.getTestnetVersion()
|
||||
}
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
package com.tangem.tap.features.tokens.redux
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.DerivationStyle
|
||||
import com.tangem.tap.domain.tokens.Currency
|
||||
import com.tangem.tap.features.wallet.redux.WalletData
|
||||
import org.rekotlin.Action
|
||||
|
||||
|
|
@ -8,14 +10,19 @@ sealed class TokensAction : Action {
|
|||
|
||||
object ResetState : TokensAction()
|
||||
|
||||
object LoadCurrencies : TokensAction() {
|
||||
data class Success(val currencies: List<CurrencyListItem>) : TokensAction()
|
||||
data class AllowToAddTokens(val allow: Boolean) : TokensAction()
|
||||
|
||||
data class LoadCurrencies(val supportedBlockchains: List<Blockchain>? = null) : TokensAction() {
|
||||
data class Success(val currencies: List<Currency>) : TokensAction()
|
||||
}
|
||||
|
||||
data class SetAddedCurrencies(val wallets: List<WalletData>) : TokensAction()
|
||||
data class SetAddedCurrencies(
|
||||
val wallets: List<WalletData>, val derivationStyle: DerivationStyle?
|
||||
) : TokensAction()
|
||||
data class SetNonRemovableCurrencies(val wallets: List<WalletData>) : TokensAction()
|
||||
|
||||
data class ToggleShowTokensForBlockchain(val isShown: Boolean, val blockchain: Blockchain) : TokensAction()
|
||||
object OpenAllTokens : TokensAction()
|
||||
|
||||
data class SaveChanges(val addedItems: List<CurrencyListItem>) : TokensAction()
|
||||
data class SaveChanges(
|
||||
val addedTokens: List<TokenWithBlockchain>,
|
||||
val addedBlockchains: List<Blockchain>
|
||||
) : TokensAction()
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.tap.features.tokens.redux
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.DerivationParams
|
||||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.card.EllipticCurve
|
||||
|
|
@ -18,6 +19,13 @@ import com.tangem.tap.common.redux.AppState
|
|||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.domain.common.TapWorkarounds.derivationStyle
|
||||
import com.tangem.domain.common.TapWorkarounds.derivationStyle
|
||||
import com.tangem.domain.common.TapWorkarounds.isTestCard
|
||||
import com.tangem.tap.domain.extensions.makeWalletManagerForApp
|
||||
import com.tangem.tap.domain.tasks.product.KeyWalletPublicKey
|
||||
import com.tangem.tap.domain.tasks.product.ScanResponse
|
||||
import com.tangem.tap.domain.tokens.BlockchainNetwork
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
@ -29,7 +37,7 @@ class TokensMiddleware {
|
|||
{ next ->
|
||||
{ action ->
|
||||
when (action) {
|
||||
is TokensAction.LoadCurrencies -> handleLoadCurrencies()
|
||||
is TokensAction.LoadCurrencies -> handleLoadCurrencies(action)
|
||||
is TokensAction.SaveChanges -> handleSaveChanges(action)
|
||||
}
|
||||
next(action)
|
||||
|
|
@ -37,35 +45,41 @@ class TokensMiddleware {
|
|||
}
|
||||
}
|
||||
|
||||
private fun handleLoadCurrencies() {
|
||||
val scanResponse = store.state.globalState.scanResponse ?: return
|
||||
val isTestcard = scanResponse.card.isTestCard
|
||||
private fun handleLoadCurrencies(action: TokensAction.LoadCurrencies) {
|
||||
val scanResponse = store.state.globalState.scanResponse
|
||||
val isTestcard = scanResponse?.card?.isTestCard ?: false
|
||||
|
||||
val currencies = currenciesRepository.getSupportedTokens(isTestcard)
|
||||
.filter(action.supportedBlockchains?.toSet())
|
||||
|
||||
val tokens = currenciesRepository.getSupportedTokens(isTestcard)
|
||||
val blockchains = currenciesRepository.getBlockchains(
|
||||
cardFirmware = scanResponse.card.firmwareVersion,
|
||||
isTestNet = isTestcard
|
||||
).sortedBy { it.fullName }
|
||||
val currencies = CurrencyListItem.createListOfCurrencies(blockchains, tokens)
|
||||
store.dispatch(TokensAction.LoadCurrencies.Success(currencies))
|
||||
}
|
||||
|
||||
private fun handleSaveChanges(action: TokensAction.SaveChanges) {
|
||||
val scanResponse = store.state.globalState.scanResponse ?: return
|
||||
|
||||
val candidatesToAdd = action.addedItems
|
||||
if (candidatesToAdd.isEmpty()) return
|
||||
val currentTokens = store.state.tokensState.addedWallets.toTokens()
|
||||
val currentBlockchains = store.state.tokensState.addedWallets.toBlockchains(
|
||||
store.state.tokensState.derivationStyle
|
||||
)
|
||||
|
||||
val blockchains = candidatesToAdd.filterIsInstance<CurrencyListItem.BlockchainListItem>()
|
||||
.map { it.blockchain }
|
||||
val tokens = candidatesToAdd.filterIsInstance<CurrencyListItem.TokenListItem>()
|
||||
.map { it.token }
|
||||
if (blockchains.isEmpty() && tokens.isEmpty()) return
|
||||
val blockchainsToAdd = action.addedBlockchains.filter { !currentBlockchains.contains(it) }
|
||||
val blockchainsToRemove = currentBlockchains.filter { !action.addedBlockchains.contains(it) }
|
||||
|
||||
val tokensToAdd = action.addedTokens.filter { !currentTokens.contains(it.token) }
|
||||
val tokensToRemove = currentTokens.filter { token -> !action.addedTokens.any { it.token == token } }
|
||||
|
||||
removeCurrenciesIfNeeded(blockchainsToRemove, tokensToRemove)
|
||||
|
||||
if (tokensToAdd.isEmpty() && blockchainsToAdd.isEmpty()) {
|
||||
store.dispatch(NavigationAction.PopBackTo())
|
||||
return
|
||||
}
|
||||
|
||||
if (scanResponse.supportsHdWallet()) {
|
||||
deriveMissingBlockchains(scanResponse, blockchains, tokens)
|
||||
deriveMissingBlockchains(scanResponse, blockchainsToAdd, tokensToAdd)
|
||||
} else {
|
||||
submitAdd(blockchains, tokens)
|
||||
submitAdd(blockchainsToAdd, tokensToAdd, scanResponse)
|
||||
store.dispatch(NavigationAction.PopBackTo())
|
||||
}
|
||||
}
|
||||
|
|
@ -73,7 +87,7 @@ class TokensMiddleware {
|
|||
private fun deriveMissingBlockchains(
|
||||
scanResponse: ScanResponse,
|
||||
blockchains: List<Blockchain>,
|
||||
tokens: List<Token>
|
||||
tokens: List<TokenWithBlockchain>
|
||||
) {
|
||||
val derivationDataList = listOfNotNull(
|
||||
getDerivations(EllipticCurve.Secp256k1, scanResponse, blockchains, tokens),
|
||||
|
|
@ -104,7 +118,7 @@ class TokensMiddleware {
|
|||
derivedKeys = updatedDerivedKeys
|
||||
)
|
||||
store.dispatchOnMain(GlobalAction.SaveScanNoteResponse(updatedScanResponse))
|
||||
submitAdd(blockchains, tokens)
|
||||
submitAdd(blockchains, tokens, scanResponse)
|
||||
|
||||
delay(DELAY_SDK_DIALOG_CLOSE)
|
||||
store.dispatchOnMain(NavigationAction.PopBackTo())
|
||||
|
|
@ -120,13 +134,12 @@ class TokensMiddleware {
|
|||
curve: EllipticCurve,
|
||||
scanResponse: ScanResponse,
|
||||
blockchains: List<Blockchain>,
|
||||
tokens: List<Token>
|
||||
tokens: List<TokenWithBlockchain>
|
||||
): DerivationData? {
|
||||
val wallet = scanResponse.card.wallets.firstOrNull { it.curve == curve } ?: return null
|
||||
|
||||
val tokenBlockchains = tokens.map { it.blockchain }
|
||||
val derivationPathsCandidates = (blockchains + tokenBlockchains).distinct()
|
||||
.mapNotNull { it.derivationPath() }
|
||||
val derivationPathsCandidates = (blockchains + tokens.map { it.blockchain }).distinct()
|
||||
.mapNotNull { it.derivationPath(scanResponse.card.derivationStyle) }
|
||||
|
||||
val mapKeyOfWalletPublicKey = wallet.publicKey.toMapKey()
|
||||
val alreadyDerivedKeys: ExtendedPublicKeysMap =
|
||||
|
|
@ -147,11 +160,38 @@ class TokensMiddleware {
|
|||
val mapKeyOfWalletPublicKey: ByteArrayKey
|
||||
)
|
||||
|
||||
private fun submitAdd(blockchains: List<Blockchain>, tokens: List<Token>) {
|
||||
(blockchains.map {
|
||||
WalletAction.MultiWallet.AddBlockchain(it)
|
||||
private fun submitAdd(
|
||||
blockchains: List<Blockchain>, tokens: List<TokenWithBlockchain>, scanResponse: ScanResponse,
|
||||
) {
|
||||
val factory = store.state.globalState.tapWalletManager.walletManagerFactory
|
||||
|
||||
(blockchains.mapNotNull {
|
||||
val walletManager = factory.makeWalletManagerForApp(
|
||||
scanResponse, it,
|
||||
scanResponse.card.derivationStyle?.let { DerivationParams.Default(it) }
|
||||
) ?: return@mapNotNull null
|
||||
WalletAction.MultiWallet.AddBlockchain(BlockchainNetwork.fromWalletManager(walletManager), walletManager)
|
||||
} + tokens.map {
|
||||
WalletAction.MultiWallet.AddToken(it)
|
||||
val blockchainNetwork = BlockchainNetwork(it.blockchain, scanResponse.card)
|
||||
WalletAction.MultiWallet.AddToken(it.token, blockchainNetwork)
|
||||
}).forEach { store.dispatchOnMain(it) }
|
||||
}
|
||||
|
||||
private fun removeCurrenciesIfNeeded(blockchains: List<Blockchain>, tokens: List<Token>) {
|
||||
if (tokens.isNotEmpty()) {
|
||||
tokens.forEach { token ->
|
||||
store.state.walletState.getWalletData(token)?.let {
|
||||
store.dispatch(WalletAction.MultiWallet.RemoveWallet(it))
|
||||
}
|
||||
}
|
||||
}
|
||||
if (blockchains.isNotEmpty()) {
|
||||
blockchains.forEach { blockchain ->
|
||||
store.state.walletState.getWalletData(blockchain)?.let {
|
||||
store.dispatch(WalletAction.MultiWallet.RemoveWallet(it))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,9 +1,6 @@
|
|||
package com.tangem.tap.features.tokens.redux
|
||||
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.domain.tokens.CardCurrencies
|
||||
import com.tangem.tap.features.wallet.redux.Currency
|
||||
import com.tangem.tap.features.wallet.redux.WalletData
|
||||
import org.rekotlin.Action
|
||||
|
||||
class TokensReducer {
|
||||
|
|
@ -19,35 +16,27 @@ private fun internalReduce(action: Action, state: AppState): TokensState {
|
|||
return when (action) {
|
||||
is TokensAction.ResetState -> TokensState()
|
||||
is TokensAction.LoadCurrencies.Success -> {
|
||||
tokensState.copy(currencies = action.currencies, shownCurrencies = action.currencies)
|
||||
tokensState.copy(currencies = action.currencies)
|
||||
}
|
||||
is TokensAction.SetAddedCurrencies -> {
|
||||
tokensState.copy(addedCurrencies = action.wallets.toCardCurrencies())
|
||||
|
||||
tokensState.copy(
|
||||
addedBlockchains = action.wallets.toBlockchains(action.derivationStyle),
|
||||
addedTokens = action.wallets.toTokensWithBlockchains(action.derivationStyle),
|
||||
addedWallets = action.wallets,
|
||||
derivationStyle = action.derivationStyle
|
||||
)
|
||||
}
|
||||
is TokensAction.ToggleShowTokensForBlockchain -> {
|
||||
if (action.isShown) {
|
||||
val shownCurrencies = tokensState.shownCurrencies
|
||||
.removeTokensForBlockchain(action.blockchain)
|
||||
shownCurrencies.toggleHeaderContentShownValue(action.blockchain)
|
||||
tokensState.copy(shownCurrencies = shownCurrencies)
|
||||
} else {
|
||||
val shownCurrencies = tokensState.shownCurrencies
|
||||
.addTokensForBlockchain(action.blockchain, tokensState.currencies)
|
||||
shownCurrencies.toggleHeaderContentShownValue(action.blockchain)
|
||||
tokensState.copy(shownCurrencies = shownCurrencies)
|
||||
}
|
||||
is TokensAction.SetNonRemovableCurrencies -> {
|
||||
tokensState.copy(
|
||||
nonRemovableBlockchains = action.wallets.toBlockchains(tokensState.derivationStyle),
|
||||
nonRemovableTokens = action.wallets.toTokensContractAddresses(),
|
||||
)
|
||||
}
|
||||
is TokensAction.OpenAllTokens -> {
|
||||
val shownCurrencies = tokensState.currencies
|
||||
shownCurrencies.forEach { if (it is CurrencyListItem.TitleListItem) it.isContentShown = true}
|
||||
tokensState.copy(shownCurrencies = tokensState.currencies)
|
||||
|
||||
is TokensAction.AllowToAddTokens -> {
|
||||
tokensState.copy(allowToAdd = action.allow)
|
||||
}
|
||||
else -> tokensState
|
||||
}
|
||||
}
|
||||
|
||||
private fun List<WalletData>.toCardCurrencies(): CardCurrencies {
|
||||
val tokens = mapNotNull { (it.currency as? Currency.Token)?.token }.distinct()
|
||||
val blockchains = mapNotNull { (it.currency as? Currency.Blockchain)?.blockchain }.distinct()
|
||||
return CardCurrencies(tokens = tokens, blockchains = blockchains)
|
||||
}
|
||||
|
|
@ -1,18 +1,66 @@
|
|||
package com.tangem.tap.features.tokens.redux
|
||||
|
||||
import com.tangem.blockchain.common.Amount
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.DerivationStyle
|
||||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.tap.domain.tokens.CardCurrencies
|
||||
import com.tangem.tap.domain.tokens.Currency
|
||||
import com.tangem.tap.domain.tokens.fromNetworkId
|
||||
import com.tangem.tap.features.wallet.redux.WalletData
|
||||
import org.rekotlin.StateType
|
||||
|
||||
data class TokensState(
|
||||
val addedTokens: LinkedHashSet<TokenWithAmount> = LinkedHashSet(),
|
||||
val addedCurrencies: CardCurrencies? = null,
|
||||
val currencies: List<CurrencyListItem> = emptyList(),
|
||||
val shownCurrencies: List<CurrencyListItem> = emptyList(),
|
||||
val addedWallets: List<WalletData> = emptyList(),
|
||||
val addedTokens: List<TokenWithBlockchain> = emptyList(),
|
||||
val addedBlockchains: List<Blockchain> = emptyList(),
|
||||
val nonRemovableTokens: List<ContractAddress> = emptyList(),
|
||||
val nonRemovableBlockchains: List<Blockchain> = emptyList(),
|
||||
val currencies: List<Currency> = emptyList(),
|
||||
val allowToAdd: Boolean = true,
|
||||
val derivationStyle: DerivationStyle? = null
|
||||
) : StateType
|
||||
|
||||
data class TokenWithAmount(
|
||||
val token: Token,
|
||||
val amount: Amount? = null
|
||||
typealias ContractAddress = String
|
||||
|
||||
fun List<WalletData>.toTokensContractAddresses(): List<ContractAddress> {
|
||||
return mapNotNull { (it.currency as? com.tangem.tap.features.wallet.redux.Currency.Token)?.token?.contractAddress }.distinct()
|
||||
}
|
||||
|
||||
fun List<WalletData>.toTokens(): List<Token> {
|
||||
return mapNotNull { (it.currency as? com.tangem.tap.features.wallet.redux.Currency.Token)?.token }.distinct()
|
||||
}
|
||||
|
||||
fun List<WalletData>.toTokensWithBlockchains(derivationStyle: DerivationStyle?): List<TokenWithBlockchain> {
|
||||
return mapNotNull {
|
||||
if (it.currency !is com.tangem.tap.features.wallet.redux.Currency.Token) return@mapNotNull null
|
||||
if (it.currency.isCustomCurrency(derivationStyle)) return@mapNotNull null
|
||||
TokenWithBlockchain(it.currency.token, it.currency.blockchain)
|
||||
}.distinct()
|
||||
}
|
||||
|
||||
fun List<WalletData>.toBlockchains(derivationStyle: DerivationStyle?): List<Blockchain> {
|
||||
return mapNotNull {
|
||||
if (it.currency.isCustomCurrency(derivationStyle)) {
|
||||
null
|
||||
} else {
|
||||
(it.currency as? com.tangem.tap.features.wallet.redux.Currency.Blockchain)?.blockchain
|
||||
}
|
||||
}.distinct()
|
||||
}
|
||||
|
||||
data class TokenWithBlockchain(
|
||||
val token: Token,
|
||||
val blockchain: Blockchain
|
||||
)
|
||||
|
||||
fun List<Currency>.filter(supportedBlockchains: Set<Blockchain>?): List<Currency> {
|
||||
if (supportedBlockchains == null) return this
|
||||
return map {
|
||||
it.copy(contracts =
|
||||
it.contracts?.filter {
|
||||
supportedBlockchains.contains(it.blockchain) && it.blockchain.canHandleTokens()
|
||||
}
|
||||
)
|
||||
}.filterNot {
|
||||
it.contracts.isNullOrEmpty() && !supportedBlockchains.contains(Blockchain.fromNetworkId(it.id))
|
||||
}
|
||||
}
|
||||
|
|
@ -8,15 +8,22 @@ import android.view.View
|
|||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.widget.SearchView
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.transition.TransitionInflater
|
||||
import by.kirich1409.viewbindingdelegate.viewBinding
|
||||
import com.google.accompanist.appcompattheme.AppCompatTheme
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.tap.common.extensions.copyToClipboard
|
||||
import com.tangem.tap.common.extensions.dispatchNotification
|
||||
import com.tangem.tap.common.extensions.getString
|
||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
import com.tangem.tap.features.tokens.redux.ContractAddress
|
||||
import com.tangem.tap.features.tokens.redux.TokenWithBlockchain
|
||||
import com.tangem.tap.features.tokens.redux.TokensAction
|
||||
import com.tangem.tap.features.tokens.redux.TokensState
|
||||
import com.tangem.tap.features.tokens.ui.adapters.CurrenciesAdapter
|
||||
import com.tangem.tap.features.tokens.ui.compose.CurrenciesScreen
|
||||
import com.tangem.tap.mainScope
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
|
|
@ -27,10 +34,11 @@ import org.rekotlin.StoreSubscriber
|
|||
|
||||
|
||||
class AddTokensFragment : Fragment(R.layout.fragment_add_tokens),
|
||||
StoreSubscriber<TokensState> {
|
||||
StoreSubscriber<TokensState> {
|
||||
|
||||
private lateinit var viewAdapter: CurrenciesAdapter
|
||||
private val binding: FragmentAddTokensBinding by viewBinding(FragmentAddTokensBinding::bind)
|
||||
private var tokensState: MutableState<TokensState> = mutableStateOf(store.state.tokensState)
|
||||
private var searchInput = mutableStateOf("")
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
|
@ -64,27 +72,32 @@ class AddTokensFragment : Fragment(R.layout.fragment_add_tokens),
|
|||
|
||||
(activity as? AppCompatActivity)?.setSupportActionBar(toolbar)
|
||||
toolbar.setNavigationOnClickListener { activity?.onBackPressed() }
|
||||
setupPopularTokensRecyclerView()
|
||||
btnTokensSaveChanges.setOnClickListener {
|
||||
store.dispatch(TokensAction.SaveChanges(viewAdapter.getAddedItems()))
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupPopularTokensRecyclerView() = with(binding) {
|
||||
viewAdapter = CurrenciesAdapter()
|
||||
viewAdapter.setOnItemAddListener {
|
||||
btnTokensSaveChanges.isEnabled = viewAdapter.getAddedItems().isNotEmpty()
|
||||
val onSaveChanges = { tokens: List<TokenWithBlockchain>, blockchains: List<Blockchain> ->
|
||||
store.dispatch(TokensAction.SaveChanges(tokens, blockchains))
|
||||
}
|
||||
val onNetworkItemClicked = { contractAddress: ContractAddress ->
|
||||
context?.copyToClipboard(contractAddress)
|
||||
store.dispatchNotification(R.string.contract_address_copied_message)
|
||||
}
|
||||
|
||||
rvPopularTokens.layoutManager = LinearLayoutManager(context)
|
||||
rvPopularTokens.adapter = viewAdapter
|
||||
cvCurrencies.setContent {
|
||||
AppCompatTheme {
|
||||
CurrenciesScreen(
|
||||
tokensState = tokensState,
|
||||
searchInput = searchInput,
|
||||
onSaveChanges = onSaveChanges,
|
||||
onNetworkItemClicked = onNetworkItemClicked
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun newState(state: TokensState) {
|
||||
if (activity == null || view == null) return
|
||||
|
||||
viewAdapter.addedCurrencies = state.addedCurrencies
|
||||
viewAdapter.submitUnfilteredList(state.shownCurrencies)
|
||||
val toolbarTitle =
|
||||
if (state.allowToAdd) R.string.add_tokens_title else R.string.search_tokens_title
|
||||
binding.toolbar.title = getString(toolbarTitle)
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
|
|
@ -102,13 +115,13 @@ class AddTokensFragment : Fragment(R.layout.fragment_add_tokens),
|
|||
searchView.queryHint = searchView.getString(R.string.add_token_search_hint)
|
||||
searchView.maxWidth = android.R.attr.width
|
||||
searchView.inputtedTextAsFlow()
|
||||
.debounce(400)
|
||||
.distinctUntilChanged()
|
||||
.onEach {
|
||||
viewAdapter.filter(searchView.query)
|
||||
}
|
||||
.launchIn(mainScope)
|
||||
return super.onCreateOptionsMenu(menu, inflater);
|
||||
.debounce(400)
|
||||
.distinctUntilChanged()
|
||||
.onEach {
|
||||
searchInput.value = it.lowercase()
|
||||
}
|
||||
.launchIn(mainScope)
|
||||
return super.onCreateOptionsMenu(menu, inflater)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
|
|
|
|||
|
|
@ -1,254 +0,0 @@
|
|||
package com.tangem.tap.features.tokens.ui.adapters
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.squareup.picasso.Picasso
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.common.extensions.VoidCallback
|
||||
import com.tangem.tap.common.extensions.getString
|
||||
import com.tangem.tap.common.extensions.hide
|
||||
import com.tangem.tap.common.extensions.loadCurrenciesIcon
|
||||
import com.tangem.tap.common.extensions.show
|
||||
import com.tangem.tap.domain.tokens.CardCurrencies
|
||||
import com.tangem.tap.domain.tokens.getTokensName
|
||||
import com.tangem.tap.features.tokens.redux.CurrencyListItem
|
||||
import com.tangem.tap.features.tokens.redux.TokensAction
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
import com.tangem.wallet.databinding.ItemCurrencySubtitleBinding
|
||||
import com.tangem.wallet.databinding.ItemPopularTokenBinding
|
||||
import java.util.*
|
||||
|
||||
class CurrenciesAdapter : ListAdapter<CurrencyListItem, RecyclerView.ViewHolder>(DiffUtilCallback) {
|
||||
|
||||
var addedCurrencies: CardCurrencies? = null
|
||||
|
||||
private var unfilteredList = listOf<CurrencyListItem>()
|
||||
|
||||
private val currentlyAddedItems = mutableListOf<CurrencyListItem>()
|
||||
|
||||
private var vhOnItemAddListener: ((CurrencyListItem) -> Unit) = {
|
||||
currentlyAddedItems.add(it)
|
||||
onItemAddListener?.invoke()
|
||||
}
|
||||
private var onItemAddListener: VoidCallback? = null
|
||||
|
||||
fun submitUnfilteredList(list: List<CurrencyListItem>) {
|
||||
unfilteredList = list
|
||||
submitList(list)
|
||||
}
|
||||
|
||||
fun setOnItemAddListener(itemAddListener: VoidCallback) {
|
||||
onItemAddListener = itemAddListener
|
||||
if (currentlyAddedItems.isNotEmpty()) itemAddListener()
|
||||
}
|
||||
|
||||
fun getAddedItems(): List<CurrencyListItem> {
|
||||
return currentlyAddedItems.toList()
|
||||
}
|
||||
|
||||
override fun getItemViewType(position: Int): Int {
|
||||
return when (currentList[position]) {
|
||||
is CurrencyListItem.TitleListItem -> 0
|
||||
is CurrencyListItem.BlockchainListItem, is CurrencyListItem.TokenListItem -> 1
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||
return when (viewType) {
|
||||
0 -> TitleViewHolder(
|
||||
binding = ItemCurrencySubtitleBinding
|
||||
.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||
)
|
||||
1 -> CurrenciesViewHolder(
|
||||
binding = ItemPopularTokenBinding
|
||||
.inflate(LayoutInflater.from(parent.context), parent, false),
|
||||
onItemAddListener = vhOnItemAddListener
|
||||
)
|
||||
else -> CurrenciesViewHolder(
|
||||
binding = ItemPopularTokenBinding
|
||||
.inflate(LayoutInflater.from(parent.context), parent, false),
|
||||
onItemAddListener = vhOnItemAddListener
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||
val listItem = currentList[position]
|
||||
if (holder is TitleViewHolder && listItem is CurrencyListItem.TitleListItem) {
|
||||
holder.bind(listItem)
|
||||
} else if (holder is CurrenciesViewHolder) {
|
||||
holder.bind(listItem, addedCurrencies, currentlyAddedItems)
|
||||
}
|
||||
}
|
||||
|
||||
object DiffUtilCallback : DiffUtil.ItemCallback<CurrencyListItem>() {
|
||||
override fun areContentsTheSame(
|
||||
oldItem: CurrencyListItem, newItem: CurrencyListItem,
|
||||
) = oldItem == newItem
|
||||
|
||||
override fun areItemsTheSame(
|
||||
oldItem: CurrencyListItem, newItem: CurrencyListItem,
|
||||
) = oldItem == newItem
|
||||
}
|
||||
|
||||
fun filter(query: CharSequence?) {
|
||||
val list = mutableListOf<CurrencyListItem>()
|
||||
|
||||
if (!query.isNullOrEmpty()) {
|
||||
store.dispatch(TokensAction.OpenAllTokens)
|
||||
val queryNormalized = query.toString().toLowerCase(Locale.US)
|
||||
list.addAll(
|
||||
unfilteredList.filter { element ->
|
||||
when (element) {
|
||||
is CurrencyListItem.BlockchainListItem -> {
|
||||
element.blockchain.currency.toLowerCase(Locale.US)
|
||||
.contains(queryNormalized) ||
|
||||
element.blockchain.fullName.toLowerCase(Locale.US)
|
||||
.contains(queryNormalized)
|
||||
|
||||
}
|
||||
is CurrencyListItem.TokenListItem -> {
|
||||
element.token.name.toLowerCase(Locale.US)
|
||||
.contains(queryNormalized) ||
|
||||
element.token.symbol.toLowerCase(Locale.US)
|
||||
.contains(queryNormalized)
|
||||
}
|
||||
is CurrencyListItem.TitleListItem -> true
|
||||
}
|
||||
})
|
||||
} else {
|
||||
list.addAll(unfilteredList)
|
||||
}
|
||||
submitList(list)
|
||||
}
|
||||
|
||||
class CurrenciesViewHolder(
|
||||
private val binding: ItemPopularTokenBinding,
|
||||
private val onItemAddListener: ((CurrencyListItem) -> Unit),
|
||||
) : RecyclerView.ViewHolder(binding.root) {
|
||||
|
||||
fun bind(
|
||||
currency: CurrencyListItem,
|
||||
addedCurrencies: CardCurrencies?,
|
||||
currentlyAddedItems: MutableList<CurrencyListItem>
|
||||
) {
|
||||
when (currency) {
|
||||
is CurrencyListItem.BlockchainListItem -> {
|
||||
val addedBlockchains = addedCurrencies?.blockchains ?: listOf()
|
||||
val currentlyAdded =
|
||||
currentlyAddedItems.filterIsInstance<CurrencyListItem.BlockchainListItem>()
|
||||
bindBlockchain(currency, addedBlockchains, currentlyAdded)
|
||||
}
|
||||
is CurrencyListItem.TokenListItem -> {
|
||||
val addedTokens = addedCurrencies?.tokens ?: listOf()
|
||||
val currentlyAdded =
|
||||
currentlyAddedItems.filterIsInstance<CurrencyListItem.TokenListItem>()
|
||||
bindToken(currency, addedTokens, currentlyAdded)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun bindBlockchain(
|
||||
currency: CurrencyListItem.BlockchainListItem,
|
||||
addedBlockchains: List<Blockchain>,
|
||||
currentlyAdded: List<CurrencyListItem.BlockchainListItem>
|
||||
) = with(binding) {
|
||||
val blockchain = currency.blockchain
|
||||
Picasso.get().loadCurrenciesIcon(ivCurrency, tvTokenLetter, null, blockchain)
|
||||
|
||||
tvCurrencyName.text = blockchain.fullName
|
||||
tvCurrencySymbol.text = blockchain.currency
|
||||
btnAddToken.setOnClickListener {
|
||||
onItemAddListener.invoke(currency)
|
||||
currency.isAdded = true
|
||||
modifyAddTokenButton(currency)
|
||||
}
|
||||
|
||||
val isAddedBefore = addedBlockchains.contains(blockchain)
|
||||
val isCurrentlyAdded = currentlyAdded.any { it.blockchain == blockchain }
|
||||
currency.isAdded = isAddedBefore || isCurrentlyAdded
|
||||
modifyAddTokenButton(currency)
|
||||
}
|
||||
|
||||
private fun bindToken(
|
||||
currency: CurrencyListItem.TokenListItem,
|
||||
addedTokens: List<Token>,
|
||||
currentlyAdded: List<CurrencyListItem.TokenListItem>
|
||||
) = with(binding) {
|
||||
val token = currency.token
|
||||
Picasso.get()
|
||||
.loadCurrenciesIcon(ivCurrency, tvTokenLetter, token, token.blockchain)
|
||||
|
||||
tvCurrencyName.text = token.name
|
||||
tvCurrencySymbol.text = tvCurrencySymbol.getString(
|
||||
R.string.token_symbol_address_format,
|
||||
token.symbol, token.contractAddress
|
||||
)
|
||||
btnAddToken.setOnClickListener {
|
||||
onItemAddListener.invoke(currency)
|
||||
currency.isAdded = true
|
||||
modifyAddTokenButton(currency)
|
||||
}
|
||||
|
||||
val isAddedBefore = addedTokens.any { it == token }
|
||||
val isCurrentlyAdded = currentlyAdded.any { it.token == token }
|
||||
currency.isAdded = isAddedBefore || isCurrentlyAdded
|
||||
|
||||
modifyAddTokenButton(currency)
|
||||
}
|
||||
|
||||
private fun modifyAddTokenButton(currency: CurrencyListItem) = with(binding) {
|
||||
if (currency.isLocked) {
|
||||
btnAddToken.setText(R.string.common_add)
|
||||
btnAddToken.isEnabled = false
|
||||
} else {
|
||||
val text = if (currency.isAdded) R.string.add_token_added else R.string.common_add
|
||||
btnAddToken.setText(text)
|
||||
btnAddToken.isEnabled = !currency.isAdded
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class TitleViewHolder(val binding: ItemCurrencySubtitleBinding) :
|
||||
RecyclerView.ViewHolder(binding.root) {
|
||||
|
||||
fun bind(title: CurrencyListItem.TitleListItem) = with(binding) {
|
||||
if (title.blockchain != null) {
|
||||
val subtitle = root.getString(title.titleResId, title.blockchain.getTokensName())
|
||||
tvSubtitle.text = subtitle.uppercase()
|
||||
clSubtitleContainer.setOnClickListener {
|
||||
val rotation = if (title.isContentShown) -90f else 90f
|
||||
store.dispatch(
|
||||
TokensAction.ToggleShowTokensForBlockchain(
|
||||
title.isContentShown, title.blockchain
|
||||
)
|
||||
)
|
||||
ivToggleSublistVisibility.animate().rotation(rotation).withEndAction {
|
||||
showArrow(title.isContentShown)
|
||||
}
|
||||
}
|
||||
showArrow(title.isContentShown)
|
||||
} else {
|
||||
tvSubtitle.text = root.getString(title.titleResId).uppercase()
|
||||
ivToggleSublistVisibility.hide()
|
||||
}
|
||||
}
|
||||
|
||||
private fun showArrow(isContentShown: Boolean) = with(binding) {
|
||||
ivToggleSublistVisibility.show()
|
||||
val drawable = if (isContentShown) {
|
||||
R.drawable.ic_arrow_angle_down
|
||||
} else {
|
||||
R.drawable.ic_arrow_angle_right
|
||||
}
|
||||
ivToggleSublistVisibility.setImageResource(drawable)
|
||||
ivToggleSublistVisibility.rotation = 0f
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,127 @@
|
|||
package com.tangem.tap.features.tokens.ui.compose
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import coil.compose.SubcomposeAsyncImage
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.tap.common.extensions.getGreyedOutIconRes
|
||||
import com.tangem.tap.common.extensions.getRoundIconRes
|
||||
import com.tangem.tap.domain.tokens.Currency
|
||||
import com.tangem.tap.domain.tokens.fromNetworkId
|
||||
import com.tangem.tap.features.tokens.redux.TokenWithBlockchain
|
||||
import com.tangem.wallet.R
|
||||
|
||||
@Composable
|
||||
fun CollapsedCurrencyItem(
|
||||
currency: Currency,
|
||||
addedTokens: List<TokenWithBlockchain>,
|
||||
addedBlockchains: List<Blockchain>,
|
||||
onCurrencyClick: (String) -> Unit
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable(onClick = { onCurrencyClick(currency.id) })
|
||||
) {
|
||||
val blockchain = Blockchain.fromNetworkId(currency.id)
|
||||
val iconRes = currency.iconUrl
|
||||
|
||||
SubcomposeAsyncImage(
|
||||
model = iconRes,
|
||||
contentDescription = currency.id,
|
||||
loading = { CurrencyPlaceholderIcon(currency.id) },
|
||||
error = { CurrencyPlaceholderIcon(currency.id) },
|
||||
modifier = Modifier
|
||||
.clip(CircleShape)
|
||||
.padding(16.dp)
|
||||
.size(46.dp),
|
||||
)
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxHeight()
|
||||
.weight(1f)
|
||||
.align(Alignment.CenterVertically)
|
||||
) {
|
||||
Text(
|
||||
text = currency.name,
|
||||
fontSize = 17.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
color = Color(0xFF1C1C1E),
|
||||
)
|
||||
Spacer(modifier = Modifier.size(6.dp))
|
||||
Row {
|
||||
if (!currency.contracts.isNullOrEmpty()) {
|
||||
currency.contracts.map { contract ->
|
||||
if (contract.address == currency.symbol) {
|
||||
BlockchainNetworkItem(
|
||||
blockchain = Blockchain.fromNetworkId(contract.networkId),
|
||||
addedBlockchains = addedBlockchains
|
||||
)
|
||||
} else {
|
||||
val added =
|
||||
addedTokens.map { it.token.contractAddress }
|
||||
.contains(contract.address)
|
||||
val icon = if (added) {
|
||||
contract.blockchain.getRoundIconRes()
|
||||
} else {
|
||||
contract.blockchain.getGreyedOutIconRes()
|
||||
}
|
||||
Image(
|
||||
painter = painterResource(id = icon),
|
||||
contentDescription = null,
|
||||
Modifier
|
||||
.size(20.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.size(5.dp))
|
||||
}
|
||||
|
||||
}
|
||||
} else {
|
||||
BlockchainNetworkItem(
|
||||
blockchain = blockchain,
|
||||
addedBlockchains = addedBlockchains
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Image(
|
||||
painter = painterResource(id = R.drawable.ic_arrow_collapsed),
|
||||
contentDescription = null,
|
||||
Modifier
|
||||
.padding(20.dp)
|
||||
.align(Alignment.CenterVertically)
|
||||
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun BlockchainNetworkItem(
|
||||
blockchain: Blockchain?,
|
||||
addedBlockchains: List<Blockchain>
|
||||
) {
|
||||
val added = addedBlockchains.contains(blockchain)
|
||||
val icon =
|
||||
if (added) blockchain?.getRoundIconRes() else blockchain?.getGreyedOutIconRes()
|
||||
if (icon != null) {
|
||||
Image(
|
||||
painter = painterResource(id = icon),
|
||||
contentDescription = null,
|
||||
Modifier
|
||||
.size(20.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.size(5.dp))
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,109 @@
|
|||
package com.tangem.tap.features.tokens.ui.compose
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.ExtendedFloatingActionButton
|
||||
import androidx.compose.material.FabPosition
|
||||
import androidx.compose.material.Scaffold
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.tap.common.compose.Keyboard
|
||||
import com.tangem.tap.common.compose.keyboardAsState
|
||||
import com.tangem.tap.common.extensions.pixelsToDp
|
||||
import com.tangem.tap.domain.tokens.Currency
|
||||
import com.tangem.tap.domain.tokens.fromNetworkId
|
||||
import com.tangem.tap.features.tokens.redux.ContractAddress
|
||||
import com.tangem.tap.features.tokens.redux.TokenWithBlockchain
|
||||
import com.tangem.tap.features.tokens.redux.TokensState
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
|
||||
@Composable
|
||||
fun CurrenciesScreen(
|
||||
tokensState: MutableState<TokensState> = mutableStateOf(store.state.tokensState),
|
||||
searchInput: MutableState<String>,
|
||||
onSaveChanges: (List<TokenWithBlockchain>, List<Blockchain>) -> Unit,
|
||||
onNetworkItemClicked: (ContractAddress) -> Unit
|
||||
) {
|
||||
|
||||
val addedTokensState = remember { mutableStateOf(tokensState.value.addedTokens) }
|
||||
val addedBlockchainsState = remember { mutableStateOf(tokensState.value.addedBlockchains) }
|
||||
|
||||
val isKeyboardOpen by keyboardAsState()
|
||||
|
||||
val onAddCurrencyToggleClick = { currency: Currency, token: TokenWithBlockchain? ->
|
||||
if (token != null) {
|
||||
val mutableList = addedTokensState.value.toMutableList()
|
||||
if (mutableList.contains(token)) {
|
||||
mutableList.remove(token)
|
||||
} else {
|
||||
mutableList.add(token)
|
||||
}
|
||||
addedTokensState.value = mutableList
|
||||
} else {
|
||||
val blockchain = Blockchain.fromNetworkId(currency.id)
|
||||
val mutableList = addedBlockchainsState.value.toMutableList()
|
||||
if (mutableList.contains(blockchain)) {
|
||||
mutableList.remove(blockchain)
|
||||
} else {
|
||||
blockchain?.let { mutableList.add(blockchain) }
|
||||
}
|
||||
addedBlockchainsState.value = mutableList
|
||||
}
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
// topBar =,
|
||||
floatingActionButton = {
|
||||
if (tokensState.value.allowToAdd) SaveChangesButton(isKeyboardOpen) {
|
||||
onSaveChanges(addedTokensState.value, addedBlockchainsState.value)
|
||||
}
|
||||
},
|
||||
floatingActionButtonPosition = FabPosition.Center,
|
||||
) {
|
||||
|
||||
Column {
|
||||
if (tokensState.value.allowToAdd) CurrenciesWarning()
|
||||
ListOfCurrencies(
|
||||
currencies = tokensState.value.currencies,
|
||||
nonRemovableTokens = tokensState.value.nonRemovableTokens,
|
||||
nonRemovableBlockchains = tokensState.value.nonRemovableBlockchains,
|
||||
addedTokens = addedTokensState.value,
|
||||
addedBlockchains = addedBlockchainsState.value,
|
||||
searchInput = searchInput.value,
|
||||
allowToAdd = tokensState.value.allowToAdd,
|
||||
onAddCurrencyToggled = onAddCurrencyToggleClick,
|
||||
onNetworkItemClicked = onNetworkItemClicked
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SaveChangesButton(keyboardState: Keyboard, onSaveChanges: () -> Unit) {
|
||||
|
||||
val padding = if (keyboardState is Keyboard.Opened) {
|
||||
LocalContext.current.pixelsToDp(keyboardState.height)
|
||||
} else {
|
||||
0
|
||||
}
|
||||
|
||||
ExtendedFloatingActionButton(
|
||||
text = {
|
||||
Text(
|
||||
text = stringResource(id = R.string.common_save_changes),
|
||||
)
|
||||
},
|
||||
onClick = onSaveChanges,
|
||||
backgroundColor = Color(0xFF1ACE80),
|
||||
contentColor = Color.White,
|
||||
modifier = Modifier.padding(bottom = padding.dp)
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
package com.tangem.tap.features.tokens.ui.compose
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.tangem.wallet.R
|
||||
|
||||
@Composable
|
||||
fun CurrenciesWarning() {
|
||||
Box(
|
||||
contentAlignment = Alignment.Center,
|
||||
modifier = Modifier
|
||||
.padding(16.dp)
|
||||
.background(Color(0xFFF2F2F2), shape = RoundedCornerShape(10.dp))
|
||||
)
|
||||
{
|
||||
val warning = stringResource(id = R.string.alert_manage_tokens_addresses_message)
|
||||
val end = warning.indexOf(" ")
|
||||
val spanStyles = listOf(
|
||||
AnnotatedString.Range(
|
||||
SpanStyle(fontWeight = FontWeight.Bold),
|
||||
start = 0,
|
||||
end = end
|
||||
)
|
||||
)
|
||||
Text(
|
||||
text = AnnotatedString(text = warning, spanStyles = spanStyles),
|
||||
textAlign = TextAlign.Center,
|
||||
color = Color(0xFF848488),
|
||||
modifier = Modifier.padding(
|
||||
top = 8.dp,
|
||||
bottom = 8.dp,
|
||||
start = 16.dp,
|
||||
end = 16.dp
|
||||
),
|
||||
fontSize = 13.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
package com.tangem.tap.features.tokens.ui.compose
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.tap.domain.tokens.Currency
|
||||
import com.tangem.tap.features.tokens.redux.ContractAddress
|
||||
import com.tangem.tap.features.tokens.redux.TokenWithBlockchain
|
||||
|
||||
@Composable
|
||||
fun CurrencyItem(
|
||||
currency: Currency,
|
||||
nonRemovableTokens: List<ContractAddress>,
|
||||
nonRemovableBlockchains: List<Blockchain>,
|
||||
addedTokens: List<TokenWithBlockchain>,
|
||||
addedBlockchains: List<Blockchain>,
|
||||
allowToAdd: Boolean,
|
||||
expanded: Boolean,
|
||||
onCurrencyClick: (String) -> Unit,
|
||||
onAddCurrencyToggled: (Currency, TokenWithBlockchain?) -> Unit,
|
||||
onNetworkItemClicked: (ContractAddress) -> Unit
|
||||
) {
|
||||
if (expanded) {
|
||||
ExpandedCurrencyItem(
|
||||
currency = currency,
|
||||
nonRemovableTokens = nonRemovableTokens,
|
||||
nonRemovableBlockchains = nonRemovableBlockchains,
|
||||
addedTokens = addedTokens,
|
||||
addedBlockchains = addedBlockchains,
|
||||
allowToAdd = allowToAdd,
|
||||
onCurrencyClick = onCurrencyClick, onAddCurrencyToggled = onAddCurrencyToggled,
|
||||
onNetworkItemClicked = onNetworkItemClicked
|
||||
)
|
||||
} else {
|
||||
CollapsedCurrencyItem(
|
||||
currency = currency, addedTokens = addedTokens,
|
||||
addedBlockchains = addedBlockchains, onCurrencyClick
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
package com.tangem.tap.features.tokens.ui.compose
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
|
||||
@Composable
|
||||
fun CurrencyPlaceholderIcon(id: String) {
|
||||
|
||||
val letterColor: Color = Color.White
|
||||
val circleColor: Color = Color.Black
|
||||
|
||||
Box(
|
||||
contentAlignment = Alignment.Center,
|
||||
modifier = Modifier
|
||||
.background(circleColor, shape = CircleShape)
|
||||
)
|
||||
{
|
||||
Text(
|
||||
text = id.firstOrNull()?.titlecase() ?: "",
|
||||
textAlign = TextAlign.Center,
|
||||
color = letterColor,
|
||||
modifier = Modifier.padding(4.dp),
|
||||
fontSize = 20.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,159 @@
|
|||
package com.tangem.tap.features.tokens.ui.compose
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.Divider
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import coil.compose.SubcomposeAsyncImage
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.tap.domain.tokens.Currency
|
||||
import com.tangem.tap.domain.tokens.fromNetworkId
|
||||
import com.tangem.tap.features.tokens.redux.ContractAddress
|
||||
import com.tangem.tap.features.tokens.redux.TokenWithBlockchain
|
||||
import com.tangem.wallet.R
|
||||
|
||||
@Composable
|
||||
fun ExpandedCurrencyItem(
|
||||
currency: Currency,
|
||||
nonRemovableTokens: List<ContractAddress>,
|
||||
nonRemovableBlockchains: List<Blockchain>,
|
||||
addedTokens: List<TokenWithBlockchain>,
|
||||
addedBlockchains: List<Blockchain>,
|
||||
allowToAdd: Boolean,
|
||||
onCurrencyClick: (String) -> Unit,
|
||||
onAddCurrencyToggled: (Currency, TokenWithBlockchain?) -> Unit,
|
||||
onNetworkItemClicked: (ContractAddress) -> Unit
|
||||
) {
|
||||
val blockchain = Blockchain.fromNetworkId(currency.id)
|
||||
val iconRes = currency.iconUrl
|
||||
|
||||
Column {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable(onClick = { onCurrencyClick(currency.id) })
|
||||
) {
|
||||
SubcomposeAsyncImage(
|
||||
model = iconRes,
|
||||
contentDescription = currency.id,
|
||||
loading = { CurrencyPlaceholderIcon(currency.id) },
|
||||
error = { CurrencyPlaceholderIcon(currency.id) },
|
||||
modifier = Modifier
|
||||
.clip(CircleShape)
|
||||
.padding(start = 16.dp, top = 16.dp, end = 16.dp)
|
||||
.size(46.dp),
|
||||
)
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxHeight()
|
||||
.weight(1f)
|
||||
.padding(top = 14.dp)
|
||||
.align(Alignment.CenterVertically)
|
||||
) {
|
||||
Text(
|
||||
text = currency.name,
|
||||
fontSize = 17.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
color = Color(0xFF1C1C1E),
|
||||
)
|
||||
Spacer(modifier = Modifier.size(6.dp))
|
||||
Text(
|
||||
text = stringResource(id = R.string.currency_subtitle_expanded),
|
||||
fontSize = 13.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
color = Color(0xFF8E8E93),
|
||||
)
|
||||
}
|
||||
Image(
|
||||
painter = painterResource(id = R.drawable.ic_arrow_extended),
|
||||
contentDescription = null,
|
||||
Modifier
|
||||
.padding(20.dp)
|
||||
.align(Alignment.CenterVertically)
|
||||
)
|
||||
}
|
||||
|
||||
val blockchains = currency.contracts?.map { it.blockchain } ?: listOfNotNull(
|
||||
Blockchain.fromNetworkId(currency.id)
|
||||
)
|
||||
|
||||
Row {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxHeight()
|
||||
) {
|
||||
if (blockchains.size > 1) {
|
||||
val dividerHeight = if (blockchains.size == 2) {
|
||||
60
|
||||
} else if (blockchains.size == 3) {
|
||||
110
|
||||
} else {
|
||||
43 * blockchains.size
|
||||
}
|
||||
Divider(
|
||||
color = Color(0xFFDEDEDE),
|
||||
modifier = Modifier
|
||||
.height(dividerHeight.dp)
|
||||
.padding(start = 37.5.dp)
|
||||
.width(1.dp)
|
||||
)
|
||||
}
|
||||
|
||||
Column {
|
||||
blockchains.map {
|
||||
Image(
|
||||
painter = painterResource(id = R.drawable.ic_link),
|
||||
contentDescription = null,
|
||||
Modifier
|
||||
.padding(
|
||||
start = 37.dp,
|
||||
)
|
||||
)
|
||||
Spacer(modifier = Modifier.size(13.5.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 6.dp),
|
||||
) {
|
||||
blockchains.map { blockchain ->
|
||||
val contract = currency.contracts?.firstOrNull { it.blockchain == blockchain }
|
||||
val added = if (contract != null && contract.address != currency.symbol) {
|
||||
addedTokens.map { it.token.contractAddress }.contains(contract.address)
|
||||
} else {
|
||||
addedBlockchains.contains(blockchain)
|
||||
}
|
||||
val canBeRemoved = if (contract != null && contract.address != currency.symbol) {
|
||||
!nonRemovableTokens.contains(contract.address)
|
||||
} else {
|
||||
!nonRemovableBlockchains.contains(blockchain)
|
||||
}
|
||||
NetworkItem(
|
||||
currency = currency,
|
||||
contract = contract,
|
||||
blockchain = blockchain, allowToAdd = allowToAdd,
|
||||
added = added,
|
||||
canBeRemoved = canBeRemoved,
|
||||
onAddCurrencyToggled = onAddCurrencyToggled,
|
||||
onNetworkItemClicked = onNetworkItemClicked,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
package com.tangem.tap.features.tokens.ui.compose
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.tap.domain.tokens.Currency
|
||||
import com.tangem.tap.features.tokens.redux.ContractAddress
|
||||
import com.tangem.tap.features.tokens.redux.TokenWithBlockchain
|
||||
|
||||
|
||||
@Composable
|
||||
fun ListOfCurrencies(
|
||||
currencies: List<Currency>,
|
||||
nonRemovableTokens: List<ContractAddress>,
|
||||
nonRemovableBlockchains: List<Blockchain>,
|
||||
addedTokens: List<TokenWithBlockchain>,
|
||||
addedBlockchains: List<Blockchain>,
|
||||
searchInput: String,
|
||||
allowToAdd: Boolean,
|
||||
onAddCurrencyToggled: (Currency, TokenWithBlockchain?) -> Unit,
|
||||
onNetworkItemClicked: (ContractAddress) -> Unit
|
||||
) {
|
||||
|
||||
val expandedCurrencies = remember { mutableStateOf(listOf("")) }
|
||||
|
||||
val onCurrencyClick = { currencyId: String ->
|
||||
val mutableList = expandedCurrencies.value.toMutableList()
|
||||
if (mutableList.contains(currencyId)) {
|
||||
mutableList.remove(currencyId)
|
||||
} else {
|
||||
mutableList.add(currencyId)
|
||||
}
|
||||
expandedCurrencies.value = mutableList
|
||||
}
|
||||
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
) {
|
||||
|
||||
val filteredCurrencies = if (searchInput.isBlank()) {
|
||||
currencies
|
||||
} else {
|
||||
currencies.asSequence().filter {
|
||||
it.name.lowercase().contains(searchInput) || it.symbol.lowercase()
|
||||
.contains(searchInput)
|
||||
}.toList()
|
||||
}
|
||||
items(filteredCurrencies) { currency ->
|
||||
CurrencyItem(
|
||||
currency = currency,
|
||||
nonRemovableTokens = nonRemovableTokens,
|
||||
nonRemovableBlockchains = nonRemovableBlockchains,
|
||||
addedTokens = addedTokens,
|
||||
addedBlockchains = addedBlockchains,
|
||||
allowToAdd = allowToAdd,
|
||||
expanded = expandedCurrencies.value.contains(currency.id),
|
||||
onCurrencyClick = onCurrencyClick,
|
||||
onAddCurrencyToggled = onAddCurrencyToggled,
|
||||
onNetworkItemClicked = onNetworkItemClicked
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,112 @@
|
|||
package com.tangem.tap.features.tokens.ui.compose
|
||||
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.Switch
|
||||
import androidx.compose.material.SwitchDefaults
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import coil.compose.SubcomposeAsyncImage
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.tap.common.extensions.getGreyedOutIconRes
|
||||
import com.tangem.tap.common.extensions.getNetworkName
|
||||
import com.tangem.tap.common.extensions.getRoundIconRes
|
||||
import com.tangem.tap.domain.tokens.Contract
|
||||
import com.tangem.tap.domain.tokens.Currency
|
||||
import com.tangem.tap.features.tokens.redux.ContractAddress
|
||||
import com.tangem.tap.features.tokens.redux.TokenWithBlockchain
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun NetworkItem(
|
||||
currency: Currency, contract: Contract?,
|
||||
blockchain: Blockchain, allowToAdd: Boolean,
|
||||
added: Boolean, canBeRemoved: Boolean,
|
||||
onAddCurrencyToggled: (Currency, TokenWithBlockchain?) -> Unit,
|
||||
onNetworkItemClicked: (ContractAddress) -> Unit
|
||||
) {
|
||||
|
||||
val isBlockchain = contract == null || contract.address == currency.symbol
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.combinedClickable {
|
||||
if (contract != null) onNetworkItemClicked(contract.address)
|
||||
}
|
||||
) {
|
||||
SubcomposeAsyncImage(
|
||||
model = if (added) blockchain.getRoundIconRes() else blockchain.getGreyedOutIconRes(),
|
||||
contentDescription = blockchain.fullName,
|
||||
loading = { CurrencyPlaceholderIcon(blockchain.id) },
|
||||
error = { CurrencyPlaceholderIcon(blockchain.id) },
|
||||
modifier = Modifier
|
||||
.clip(CircleShape)
|
||||
.padding(start = 8.dp, top = 16.dp, bottom = 16.dp, end = 6.dp)
|
||||
.size(20.dp)
|
||||
.align(Alignment.CenterVertically)
|
||||
)
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxHeight()
|
||||
.weight(1f)
|
||||
.align(Alignment.CenterVertically)
|
||||
) {
|
||||
Text(
|
||||
text = blockchain.name.uppercase(),
|
||||
fontSize = 13.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
color = if (added) Color.Black else Color(0xFF848488),
|
||||
)
|
||||
Spacer(modifier = Modifier.size(3.dp))
|
||||
Text(
|
||||
text = if (isBlockchain) "MAIN" else blockchain.getNetworkName().uppercase(),
|
||||
fontSize = 13.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
color = if (!isBlockchain) Color(0xFF8E8E93) else Color(0xFF1ACE80),
|
||||
)
|
||||
}
|
||||
|
||||
if (allowToAdd) {
|
||||
val token = if (!isBlockchain) {
|
||||
Token(
|
||||
id = currency.id,
|
||||
name = currency.name,
|
||||
symbol = currency.symbol,
|
||||
contractAddress = contract!!.address,
|
||||
decimals = contract.decimalCount,
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
val tokenWithBlockchain =
|
||||
token?.let { TokenWithBlockchain(token, contract!!.blockchain) }
|
||||
|
||||
val currencyToSave = if (isBlockchain && contract != null) {
|
||||
currency.copy(id = contract.networkId)
|
||||
} else {
|
||||
currency
|
||||
}
|
||||
|
||||
Switch(
|
||||
checked = added,
|
||||
enabled = canBeRemoved,
|
||||
onCheckedChange = { onAddCurrencyToggled(currencyToSave, tokenWithBlockchain) },
|
||||
modifier = Modifier.padding(start = 16.dp, end = 16.dp),
|
||||
colors = SwitchDefaults.colors(
|
||||
checkedThumbColor = Color(0xFF1ACE80)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -8,7 +8,7 @@ import com.tangem.tap.common.redux.ErrorAction
|
|||
import com.tangem.tap.common.redux.NotificationAction
|
||||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
|
||||
import com.tangem.tap.domain.tokens.CardCurrencies
|
||||
import com.tangem.tap.domain.tokens.BlockchainNetwork
|
||||
import com.tangem.wallet.R
|
||||
import org.rekotlin.Action
|
||||
import java.math.BigDecimal
|
||||
|
|
@ -24,9 +24,14 @@ sealed class WalletAction : Action {
|
|||
}
|
||||
|
||||
|
||||
data class LoadWallet(val blockchain: Blockchain? = null) : WalletAction() {
|
||||
data class Success(val wallet: Wallet) : WalletAction()
|
||||
data class NoAccount(val wallet: Wallet, val amountToCreateAccount: String) : WalletAction()
|
||||
data class LoadWallet(val blockchain: BlockchainNetwork? = null) : WalletAction() {
|
||||
data class Success(val wallet: Wallet, val blockchain: BlockchainNetwork) : WalletAction()
|
||||
data class NoAccount(
|
||||
val wallet: Wallet,
|
||||
val blockchain: BlockchainNetwork,
|
||||
val amountToCreateAccount: String
|
||||
) : WalletAction()
|
||||
|
||||
data class Failure(val wallet: Wallet, val errorMessage: String? = null) : WalletAction()
|
||||
}
|
||||
|
||||
|
|
@ -35,19 +40,30 @@ sealed class WalletAction : Action {
|
|||
|
||||
sealed class MultiWallet : WalletAction() {
|
||||
data class SetIsMultiwalletAllowed(val isMultiwalletAllowed: Boolean) : MultiWallet()
|
||||
data class AddWalletManagers(val walletManagers: List<WalletManager>) : MultiWallet() {
|
||||
constructor(walletManager: WalletManager) : this(listOf(walletManager))
|
||||
}
|
||||
|
||||
data class AddBlockchain(val blockchain: Blockchain) : MultiWallet()
|
||||
data class AddBlockchains(val blockchains: List<Blockchain>) : MultiWallet()
|
||||
data class AddTokens(val tokens: List<Token>) : MultiWallet()
|
||||
data class AddToken(val token: Token) : MultiWallet()
|
||||
data class SaveCurrencies(val cardCurrencies: CardCurrencies) : MultiWallet()
|
||||
data class AddBlockchain(
|
||||
val blockchain: BlockchainNetwork,
|
||||
val walletManager: WalletManager?
|
||||
) : MultiWallet()
|
||||
|
||||
data class AddBlockchains(
|
||||
val blockchains: List<BlockchainNetwork>, val walletManagers: List<WalletManager>
|
||||
) : MultiWallet()
|
||||
|
||||
data class AddTokens(val tokens: List<Token>, val blockchain: BlockchainNetwork) :
|
||||
MultiWallet()
|
||||
|
||||
data class AddToken(val token: Token, val blockchain: BlockchainNetwork) : MultiWallet()
|
||||
data class SaveCurrencies(val blockchainNetworks: List<BlockchainNetwork>) : MultiWallet()
|
||||
object FindTokensInUse : MultiWallet()
|
||||
object FindBlockchainsInUse : MultiWallet()
|
||||
|
||||
data class TokenLoaded(val amount: Amount, val token: Token) : MultiWallet()
|
||||
data class TokenLoaded(
|
||||
val amount: Amount,
|
||||
val token: Token,
|
||||
val blockchain: BlockchainNetwork
|
||||
) : MultiWallet()
|
||||
|
||||
data class SelectWallet(val walletData: WalletData?) : MultiWallet()
|
||||
data class RemoveWallet(val walletData: WalletData) : MultiWallet()
|
||||
data class SetPrimaryBlockchain(val blockchain: Blockchain) : MultiWallet()
|
||||
|
|
@ -135,5 +151,9 @@ sealed class WalletAction : Action {
|
|||
|
||||
data class ChangeSelectedAddress(val type: AddressType) : WalletAction()
|
||||
|
||||
data class SetWalletRent(val blockchain: Blockchain, val minRent: String, val rentExempt: String) : WalletAction()
|
||||
data class SetWalletRent(
|
||||
val blockchain: BlockchainNetwork,
|
||||
val minRent: String,
|
||||
val rentExempt: String
|
||||
) : WalletAction()
|
||||
}
|
||||
|
|
@ -10,10 +10,12 @@ import com.tangem.tap.common.extensions.toQrCode
|
|||
import com.tangem.tap.common.redux.StateDialog
|
||||
import com.tangem.tap.common.redux.global.CryptoCurrencyName
|
||||
import com.tangem.tap.common.toggleWidget.WidgetState
|
||||
import com.tangem.tap.domain.TapWorkarounds.derivationStyle
|
||||
import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
|
||||
import com.tangem.tap.domain.extensions.buyIsAllowed
|
||||
import com.tangem.tap.domain.extensions.sellIsAllowed
|
||||
import com.tangem.tap.domain.extensions.toSendableAmounts
|
||||
import com.tangem.tap.domain.tokens.BlockchainNetwork
|
||||
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsState
|
||||
import com.tangem.tap.features.wallet.models.PendingTransaction
|
||||
import com.tangem.tap.features.wallet.models.toPendingTransactions
|
||||
|
|
@ -33,8 +35,7 @@ data class WalletState(
|
|||
val hashesCountVerified: Boolean? = null,
|
||||
val walletDialog: StateDialog? = null,
|
||||
val mainWarningsList: List<WarningMessage> = mutableListOf(),
|
||||
val walletsData: List<WalletData> = emptyList(),
|
||||
val walletManagers: List<WalletManager> = emptyList(),
|
||||
val wallets: List<WalletStore> = listOf(),
|
||||
val isMultiwalletAllowed: Boolean = false,
|
||||
val cardCurrency: CryptoCurrencyName? = null,
|
||||
val selectedCurrency: Currency? = null,
|
||||
|
|
@ -52,45 +53,96 @@ data class WalletState(
|
|||
val isTangemTwins: Boolean
|
||||
get() = store.state.globalState.scanResponse?.isTangemTwins() == true
|
||||
|
||||
val primaryWallet = if (walletsData.isNotEmpty()) walletsData[0] else null
|
||||
val primaryWallet: WalletData? = if (wallets.isNotEmpty()) wallets[0].walletsData[0] else null
|
||||
val primaryWalletManager: WalletManager? =
|
||||
if (wallets.isNotEmpty()) wallets[0].walletManager else null
|
||||
|
||||
val shouldShowDetails: Boolean =
|
||||
primaryWallet?.currencyData?.status != BalanceStatus.EmptyCard &&
|
||||
primaryWallet?.currencyData?.status != BalanceStatus.UnknownBlockchain
|
||||
primaryWallet?.currencyData?.status != BalanceStatus.UnknownBlockchain
|
||||
|
||||
val blockchains: List<Blockchain>
|
||||
get() = walletManagers.map { it.wallet.blockchain }
|
||||
get() = wallets.mapNotNull { it.walletManager?.wallet?.blockchain }
|
||||
|
||||
val currencies: List<Currency>
|
||||
get() = walletsData.mapNotNull { it.currency }
|
||||
get() = wallets.flatMap { it.walletsData }.map { it.currency }
|
||||
|
||||
val walletsData: List<WalletData>
|
||||
get() = wallets.flatMap { it.walletsData }
|
||||
|
||||
val walletManagers: List<WalletManager>
|
||||
get() = wallets.mapNotNull { it.walletManager }
|
||||
|
||||
fun getWalletManager(token: Token?): WalletManager? {
|
||||
if (token == null) return null
|
||||
return walletManagers.find { it.wallet.blockchain == token.blockchain }
|
||||
return wallets
|
||||
.mapNotNull { it.walletManager }
|
||||
.find { walletManager ->
|
||||
walletManager.cardTokens.any { it.contractAddress == token.contractAddress }
|
||||
}
|
||||
}
|
||||
|
||||
fun getWalletManager(currency: Currency?): WalletManager? {
|
||||
if (currency?.blockchain == null) return null
|
||||
return walletManagers.find { it.wallet.blockchain == currency.blockchain }
|
||||
return wallets.map { it.walletManager }
|
||||
.find { it?.wallet?.blockchain == currency.blockchain }
|
||||
}
|
||||
|
||||
fun getWalletManager(blockchain: Blockchain): WalletManager? {
|
||||
return walletManagers.find { it.wallet.blockchain == blockchain }
|
||||
fun getWalletManager(blockchain: BlockchainNetwork): WalletManager? {
|
||||
return wallets.find { it.blockchainNetwork == blockchain }?.walletManager
|
||||
}
|
||||
|
||||
fun getWalletData(blockchain: BlockchainNetwork?): WalletData? {
|
||||
if (blockchain == null) return null
|
||||
return walletsData.find {
|
||||
it.currency is Currency.Blockchain &&
|
||||
it.currency.blockchain == blockchain.blockchain &&
|
||||
it.currency.derivationPath == blockchain.derivationPath
|
||||
}
|
||||
}
|
||||
|
||||
fun getWalletStore(currency: Currency?): WalletStore? {
|
||||
if (currency == null) return null
|
||||
return wallets.firstOrNull {
|
||||
it.blockchainNetwork.derivationPath == currency.derivationPath &&
|
||||
(it.blockchainNetwork.blockchain == currency.blockchain)
|
||||
}
|
||||
}
|
||||
|
||||
fun getWalletStore(wallet: Wallet?): WalletStore? {
|
||||
if (wallet == null) return null
|
||||
val currency =
|
||||
Currency.Blockchain(wallet.blockchain, wallet.publicKey.derivationPath?.rawPath)
|
||||
return getWalletStore(currency)
|
||||
}
|
||||
|
||||
fun getWalletStore(blockchainNetwork: BlockchainNetwork?): WalletStore? {
|
||||
if (blockchainNetwork == null) return null
|
||||
return wallets.firstOrNull {
|
||||
it.blockchainNetwork.derivationPath == blockchainNetwork.derivationPath &&
|
||||
(it.blockchainNetwork.blockchain == blockchainNetwork.blockchain)
|
||||
}
|
||||
}
|
||||
|
||||
fun getWalletData(currency: Currency?): WalletData? {
|
||||
if (currency == null) return null
|
||||
return walletsData.find { it.currency == currency }
|
||||
}
|
||||
|
||||
fun getWalletData(blockchain: Blockchain?): WalletData? {
|
||||
if (blockchain == null) return null
|
||||
return walletsData.find { (it.currency as? Currency.Blockchain)?.blockchain == blockchain }
|
||||
return getWalletStore(currency)?.walletsData?.firstOrNull { it.currency == currency }
|
||||
}
|
||||
|
||||
fun getWalletData(token: Token?): WalletData? {
|
||||
if (token == null) return null
|
||||
return walletsData.find { (it.currency as? Currency.Token)?.token == token }
|
||||
return walletsData.find {
|
||||
(it.currency as? Currency.Token)?.token == token &&
|
||||
!it.currency.isCustomCurrency(store.state.globalState.scanResponse!!.card.derivationStyle)
|
||||
}
|
||||
}
|
||||
|
||||
fun getWalletData(blockchain: Blockchain?): WalletData? {
|
||||
if (blockchain == null) return null
|
||||
return walletsData.find {
|
||||
(it.currency as? Currency.Blockchain)?.blockchain == blockchain &&
|
||||
!it.currency.isCustomCurrency(store.state.globalState.scanResponse!!.card.derivationStyle)
|
||||
}
|
||||
}
|
||||
|
||||
fun getSelectedWalletData(): WalletData? {
|
||||
|
|
@ -114,36 +166,90 @@ data class WalletState(
|
|||
|
||||
if (walletData.currency is Currency.Blockchain) {
|
||||
return wallet.recentTransactions.toPendingTransactions(wallet.address).isEmpty() &&
|
||||
wallet.amounts.toSendableAmounts().isEmpty()
|
||||
wallet.amounts.toSendableAmounts().isEmpty()
|
||||
} else if (walletData.currency is Currency.Token) (
|
||||
return wallet.recentTransactions.toPendingTransactionsForToken(
|
||||
walletData.currency.token, wallet.address).isEmpty()
|
||||
&& wallet.amounts[AmountType.Token(token = walletData.currency.token)]
|
||||
?.isAboveZero() != true
|
||||
)
|
||||
return wallet.recentTransactions.toPendingTransactionsForToken(
|
||||
walletData.currency.token, wallet.address
|
||||
).isEmpty()
|
||||
&& wallet.amounts[AmountType.Token(token = walletData.currency.token)]
|
||||
?.isAboveZero() != true
|
||||
)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun isPrimaryCurrency(walletData: WalletData): Boolean {
|
||||
return (walletData.currency is Currency.Blockchain &&
|
||||
walletData.currency.blockchain == store.state.walletState.primaryBlockchain)
|
||||
|| (walletData.currency is Currency.Token &&
|
||||
walletData.currency.token == store.state.walletState.primaryToken)
|
||||
walletData.currency.blockchain == store.state.walletState.primaryBlockchain)
|
||||
|| (walletData.currency is Currency.Token &&
|
||||
walletData.currency.token == store.state.walletState.primaryToken)
|
||||
}
|
||||
|
||||
fun replaceWalletInWallets(walletData: WalletData?): List<WalletData> {
|
||||
if (walletData == null) return walletsData
|
||||
fun replaceWalletInWallets(wallet: WalletStore?): List<WalletStore> {
|
||||
if (wallet == null) return wallets
|
||||
var changed = false
|
||||
val updatedWallets = walletsData.map {
|
||||
if (it.currency == walletData.currency) {
|
||||
val updatedWallets = wallets.map {
|
||||
if (it.blockchainNetwork == wallet.blockchainNetwork) {
|
||||
changed = true
|
||||
walletData
|
||||
wallet
|
||||
} else {
|
||||
it
|
||||
}
|
||||
}
|
||||
return if (changed) updatedWallets else walletsData + walletData
|
||||
return if (changed) updatedWallets else wallets + wallet
|
||||
}
|
||||
|
||||
fun updateWalletData(walletData: WalletData?): WalletState {
|
||||
if (walletData == null) return this
|
||||
return updateWalletsData(listOf(walletData))
|
||||
}
|
||||
|
||||
fun updateWalletsData(
|
||||
walletsData: List<WalletData>
|
||||
): WalletState {
|
||||
|
||||
val walletStores = walletsData
|
||||
.map { BlockchainNetwork(it.currency.blockchain, it.currency.derivationPath, emptyList()) }
|
||||
.distinct().map { getWalletStore(it) }.mapNotNull { it?.updateWallets(walletsData) }
|
||||
|
||||
return updateWalletStores(walletStores)
|
||||
|
||||
}
|
||||
|
||||
fun updateWalletStore(walletStore: WalletStore?): WalletState {
|
||||
return copy(wallets = replaceWalletInWallets(walletStore))
|
||||
}
|
||||
|
||||
fun updateWalletStores(walletStores: List<WalletStore>): WalletState {
|
||||
val walletStores = walletStores.toMutableList()
|
||||
val updatedWallets = wallets.map { oldWalletStore ->
|
||||
val walletStore = walletStores.find { it.blockchainNetwork == oldWalletStore.blockchainNetwork }
|
||||
if (walletStore != null) {
|
||||
walletStores.remove(walletStore)
|
||||
walletStore
|
||||
} else {
|
||||
oldWalletStore
|
||||
}
|
||||
}
|
||||
return copy(wallets = updatedWallets + walletStores)
|
||||
}
|
||||
|
||||
fun removeWallet(walletData: WalletData?): WalletState {
|
||||
if (walletData == null) return this
|
||||
return if (walletData.currency is Currency.Blockchain) {
|
||||
val walletStores = wallets.filterNot {
|
||||
it.blockchainNetwork.blockchain == walletData.currency.blockchain
|
||||
&& it.blockchainNetwork.derivationPath == walletData.currency.derivationPath
|
||||
}
|
||||
copy(wallets = walletStores)
|
||||
} else {
|
||||
val walletStore = getWalletStore(walletData.currency)
|
||||
val walletDataList = walletStore?.walletsData
|
||||
?.filterNot { it.currency == walletData.currency }
|
||||
?: emptyList()
|
||||
val updatedWalletStore = walletStore?.copy(walletsData = walletDataList)
|
||||
updateWalletStore(updatedWalletStore)
|
||||
}
|
||||
}
|
||||
|
||||
fun replaceSomeWallets(newWallets: List<WalletData>): List<WalletData> {
|
||||
|
|
@ -165,20 +271,26 @@ data class WalletState(
|
|||
exchangeManager: CurrencyExchangeManager?,
|
||||
walletData: WalletData
|
||||
): WalletData {
|
||||
return walletData.copy(tradeCryptoState = TradeCryptoState.from(exchangeManager, walletData))
|
||||
return walletData.copy(
|
||||
tradeCryptoState = TradeCryptoState.from(
|
||||
exchangeManager,
|
||||
walletData
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun updateTradeCryptoState(
|
||||
exchangeManager: CurrencyExchangeManager?,
|
||||
walletDataList: List<WalletData>
|
||||
): List<WalletData> {
|
||||
return walletDataList.map { it.copy(tradeCryptoState = TradeCryptoState.from(exchangeManager, it)) }
|
||||
}
|
||||
|
||||
fun addWalletManagers(newWalletManagers: List<WalletManager>): WalletState {
|
||||
val updatedWalletManagers = this.walletManagers +
|
||||
newWalletManagers.filterNot { this.blockchains.contains(it.wallet.blockchain) }
|
||||
return copy(walletManagers = updatedWalletManagers)
|
||||
return walletDataList.map {
|
||||
it.copy(
|
||||
tradeCryptoState = TradeCryptoState.from(
|
||||
exchangeManager,
|
||||
it
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -224,7 +336,8 @@ data class Artwork(
|
|||
const val TWIN_CARD_1 = "https://app.tangem.com/cards/card_tg085.png"
|
||||
const val TWIN_CARD_2 = "https://app.tangem.com/cards/card_tg086.png"
|
||||
|
||||
const val TEMP_CARDANO = "https://verify.tangem.com/card/artwork?artworkId=card_ru039&CID=CB19000000040976&publicKey=0416E29423A6CC77CD07CBA52873E8F6F894B1AFB18EB3688ACC2C8D8E5AC84B80B0BA1B17B85E578E47044CE96BCFF3FB4499FA4941CAD3C1EF300A492B5B9659"
|
||||
const val TEMP_CARDANO =
|
||||
"https://verify.tangem.com/card/artwork?artworkId=card_ru039&CID=CB19000000040976&publicKey=0416E29423A6CC77CD07CBA52873E8F6F894B1AFB18EB3688ACC2C8D8E5AC84B80B0BA1B17B85E578E47044CE96BCFF3FB4499FA4941CAD3C1EF300A492B5B9659"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -233,7 +346,10 @@ data class TradeCryptoState(
|
|||
val buyingAllowed: Boolean = false,
|
||||
) {
|
||||
companion object {
|
||||
fun from(exchangeManager: CurrencyExchangeManager?, walletData: WalletData): TradeCryptoState {
|
||||
fun from(
|
||||
exchangeManager: CurrencyExchangeManager?,
|
||||
walletData: WalletData
|
||||
): TradeCryptoState {
|
||||
val status = exchangeManager ?: return walletData.tradeCryptoState
|
||||
val currency = walletData.currency
|
||||
|
||||
|
|
@ -267,7 +383,9 @@ data class WalletData(
|
|||
|
||||
fun shouldEnableTokenSendButton(): Boolean = !blockchainAmountIsEmpty() || !tokenAmountIsEmpty()
|
||||
|
||||
private fun blockchainAmountIsEmpty(): Boolean = currencyData.blockchainAmount?.isZero() ?: false
|
||||
private fun blockchainAmountIsEmpty(): Boolean =
|
||||
currencyData.blockchainAmount?.isZero() ?: false
|
||||
|
||||
private fun tokenAmountIsEmpty(): Boolean = currencyData.amount?.isZero() == true
|
||||
}
|
||||
|
||||
|
|
@ -280,17 +398,83 @@ sealed interface Currency {
|
|||
|
||||
val blockchain: com.tangem.blockchain.common.Blockchain
|
||||
val currencySymbol: CryptoCurrencyName
|
||||
val derivationPath: String?
|
||||
|
||||
data class Token(
|
||||
val token: com.tangem.blockchain.common.Token
|
||||
val token: com.tangem.blockchain.common.Token,
|
||||
override val blockchain: com.tangem.blockchain.common.Blockchain,
|
||||
override val derivationPath: String?
|
||||
) : Currency {
|
||||
override val blockchain = token.blockchain
|
||||
override val currencySymbol: CryptoCurrencyName = token.symbol
|
||||
override val currencySymbol = token.symbol
|
||||
}
|
||||
|
||||
data class Blockchain(
|
||||
override val blockchain: com.tangem.blockchain.common.Blockchain
|
||||
override val blockchain: com.tangem.blockchain.common.Blockchain,
|
||||
override val derivationPath: String?
|
||||
) : Currency {
|
||||
override val currencySymbol: CryptoCurrencyName = blockchain.currency
|
||||
}
|
||||
|
||||
fun isCustomCurrency(derivationStyle: DerivationStyle?): Boolean {
|
||||
if (derivationPath == null || derivationStyle == null) return false
|
||||
return derivationPath != blockchain.derivationPath(derivationStyle)?.rawPath
|
||||
}
|
||||
|
||||
|
||||
companion object {
|
||||
fun fromBlockchainNetwork(
|
||||
blockchainNetwork: BlockchainNetwork,
|
||||
token: com.tangem.blockchain.common.Token? = null
|
||||
): Currency {
|
||||
return if (token != null) {
|
||||
Currency.Token(
|
||||
token = token,
|
||||
blockchain = blockchainNetwork.blockchain,
|
||||
derivationPath = blockchainNetwork.derivationPath
|
||||
)
|
||||
} else {
|
||||
Currency.Blockchain(
|
||||
blockchain = blockchainNetwork.blockchain,
|
||||
derivationPath = blockchainNetwork.derivationPath
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
@ -10,6 +10,7 @@ import com.tangem.tap.common.redux.navigation.NavigationAction
|
|||
import com.tangem.tap.currenciesRepository
|
||||
import com.tangem.tap.domain.extensions.makeWalletManagerForApp
|
||||
import com.tangem.tap.domain.extensions.makeWalletManagersForApp
|
||||
import com.tangem.tap.domain.tokens.BlockchainNetwork
|
||||
import com.tangem.tap.features.demo.DemoHelper
|
||||
import com.tangem.tap.features.demo.isDemoCard
|
||||
import com.tangem.tap.features.wallet.redux.Currency
|
||||
|
|
@ -31,11 +32,8 @@ class MultiWalletMiddleware {
|
|||
val tapWalletManager = globalState.tapWalletManager
|
||||
|
||||
when (action) {
|
||||
is WalletAction.MultiWallet.AddWalletManagers -> {
|
||||
globalState.feedbackManager?.infoHolder?.setWalletsInfo(action.walletManagers)
|
||||
if (globalState.scanResponse?.isDemoCard() == true) {
|
||||
addDummyBalances(action.walletManagers)
|
||||
}
|
||||
is WalletAction.MultiWallet.AddBlockchains -> {
|
||||
handleAddingWalletManagers(globalState, action.walletManagers)
|
||||
}
|
||||
is WalletAction.MultiWallet.SelectWallet -> {
|
||||
if (action.walletData != null) {
|
||||
|
|
@ -43,33 +41,39 @@ class MultiWalletMiddleware {
|
|||
}
|
||||
}
|
||||
is WalletAction.MultiWallet.AddToken -> {
|
||||
globalState.scanResponse?.card?.cardId?.let {
|
||||
currenciesRepository.saveAddedToken(it, action.token)
|
||||
}
|
||||
addTokens(listOf(action.token), walletState, globalState)
|
||||
addTokens(listOf(action.token), action.blockchain, walletState, globalState)
|
||||
}
|
||||
is WalletAction.MultiWallet.AddTokens -> {
|
||||
addTokens(action.tokens, walletState, globalState)
|
||||
addTokens(action.tokens, action.blockchain, walletState, globalState)
|
||||
}
|
||||
is WalletAction.MultiWallet.AddBlockchain -> {
|
||||
globalState.scanResponse?.let {
|
||||
currenciesRepository.saveAddedBlockchain(it.card.cardId, action.blockchain)
|
||||
if (walletState?.blockchains?.contains(action.blockchain) != true) {
|
||||
tapWalletManager.walletManagerFactory
|
||||
.makeWalletManagerForApp(it, action.blockchain)?.let { walletManager ->
|
||||
store.dispatch(WalletAction.MultiWallet.AddWalletManagers(walletManager))
|
||||
}
|
||||
}
|
||||
action.walletManager?.let {
|
||||
handleAddingWalletManagers(globalState, listOf(action.walletManager))
|
||||
}
|
||||
store.dispatch(WalletAction.LoadFiatRate(
|
||||
currencyList = listOf(Currency.Blockchain(action.blockchain)))
|
||||
|
||||
globalState.scanResponse?.let {
|
||||
currenciesRepository.saveUpdatedCurrency(
|
||||
cardId = it.card.cardId,
|
||||
blockchainNetwork = action.blockchain
|
||||
)
|
||||
}
|
||||
store.dispatch(
|
||||
WalletAction.LoadFiatRate(
|
||||
currencyList = listOf(
|
||||
Currency.Blockchain(
|
||||
action.blockchain.blockchain,
|
||||
action.blockchain.derivationPath
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
store.dispatch(WalletAction.LoadWallet(action.blockchain)
|
||||
store.dispatch(
|
||||
WalletAction.LoadWallet(action.blockchain)
|
||||
)
|
||||
}
|
||||
is WalletAction.MultiWallet.SaveCurrencies -> {
|
||||
globalState.scanResponse?.card?.cardId?.let {
|
||||
currenciesRepository.saveCardCurrencies(it, action.cardCurrencies)
|
||||
currenciesRepository.saveCurrencies(it, action.blockchainNetworks)
|
||||
}
|
||||
}
|
||||
is WalletAction.MultiWallet.RemoveWallet -> {
|
||||
|
|
@ -77,13 +81,27 @@ class MultiWalletMiddleware {
|
|||
when (val currency = action.walletData.currency) {
|
||||
is Currency.Blockchain -> {
|
||||
cardId?.let {
|
||||
currenciesRepository.removeBlockchain(it, currency.blockchain)
|
||||
currenciesRepository.removeBlockchain(
|
||||
cardId = it,
|
||||
blockchainNetwork = BlockchainNetwork(
|
||||
currency.blockchain, currency.derivationPath,
|
||||
emptyList()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
is Currency.Token -> {
|
||||
walletState?.getWalletManager(currency.token)
|
||||
?.removeToken(currency.token)
|
||||
cardId?.let { currenciesRepository.removeToken(it, currency.token) }
|
||||
val walletManager = walletState?.getWalletManager(currency.token)
|
||||
if (walletManager != null) {
|
||||
walletManager.removeToken(currency.token)
|
||||
cardId?.let {
|
||||
currenciesRepository.removeToken(
|
||||
cardId = it,
|
||||
token = currency.token,
|
||||
blockchainNetwork = BlockchainNetwork.fromWalletManager(walletManager)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -94,8 +112,12 @@ class MultiWalletMiddleware {
|
|||
val cardFirmware = scanResponse.card.firmwareVersion
|
||||
val blockchains = currenciesRepository.getBlockchains(cardFirmware)
|
||||
.filterNot { walletState?.blockchains?.contains(it) == true }
|
||||
.map { BlockchainNetwork(it, null, emptyList()) }
|
||||
val walletManagers =
|
||||
tapWalletManager.walletManagerFactory.makeWalletManagersForApp(scanResponse, blockchains)
|
||||
tapWalletManager.walletManagerFactory.makeWalletManagersForApp(
|
||||
scanResponse,
|
||||
blockchains
|
||||
)
|
||||
|
||||
scope.launch {
|
||||
walletManagers.map { walletManager ->
|
||||
|
|
@ -105,18 +127,19 @@ class MultiWalletMiddleware {
|
|||
val coinAmount = wallet.amounts[AmountType.Coin]?.value
|
||||
if (coinAmount != null && !coinAmount.isZero()) {
|
||||
scope.launch(Dispatchers.Main) {
|
||||
if (walletState?.getWalletData(wallet.blockchain) == null) {
|
||||
store.dispatch(
|
||||
WalletAction.MultiWallet.AddWalletManagers(
|
||||
listOfNotNull(walletManager)
|
||||
)
|
||||
)
|
||||
val blockchainNetwork = BlockchainNetwork.fromWalletManager(walletManager)
|
||||
if (walletState?.getWalletData(blockchainNetwork) == null) {
|
||||
store.dispatch(
|
||||
WalletAction.MultiWallet.AddBlockchain(
|
||||
wallet.blockchain
|
||||
blockchainNetwork, walletManager
|
||||
)
|
||||
)
|
||||
store.dispatch(
|
||||
WalletAction.LoadWallet.Success(
|
||||
wallet = wallet,
|
||||
blockchain = blockchainNetwork
|
||||
)
|
||||
)
|
||||
store.dispatch(WalletAction.LoadWallet.Success(wallet))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -130,30 +153,42 @@ class MultiWalletMiddleware {
|
|||
|
||||
val walletFactory = tapWalletManager.walletManagerFactory
|
||||
val card = scanResponse.card
|
||||
val walletManager = walletState?.getWalletManager(Blockchain.Ethereum)
|
||||
?: walletFactory.makeWalletManagerForApp(scanResponse, Blockchain.Ethereum)
|
||||
|
||||
val walletManager = walletState?.getWalletManager(
|
||||
Currency.Blockchain(Blockchain.Ethereum, null)
|
||||
)
|
||||
?: walletFactory.makeWalletManagerForApp(
|
||||
scanResponse,
|
||||
Currency.Blockchain(Blockchain.Ethereum, null)
|
||||
)
|
||||
|
||||
val tokenFinder = walletManager as? TokenFinder ?: return
|
||||
scope.launch {
|
||||
val result = tokenFinder.findTokens()
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
when (result) {
|
||||
is Result.Success -> {
|
||||
if (result.data.isNotEmpty()) {
|
||||
currenciesRepository.saveAddedTokens(card.cardId, result.data)
|
||||
val blockchainNetwork = BlockchainNetwork(
|
||||
walletManager.wallet.blockchain,
|
||||
walletManager.wallet.publicKey.derivationPath?.rawPath,
|
||||
walletManager.cardTokens.toList()
|
||||
)
|
||||
currenciesRepository.saveUpdatedCurrency(
|
||||
card.cardId,
|
||||
blockchainNetwork
|
||||
)
|
||||
store.dispatch(
|
||||
WalletAction.MultiWallet.AddWalletManagers(
|
||||
WalletAction.MultiWallet.AddBlockchain(
|
||||
blockchainNetwork,
|
||||
walletManager
|
||||
)
|
||||
)
|
||||
store.dispatch(
|
||||
WalletAction.MultiWallet.AddBlockchain(
|
||||
walletManager.wallet.blockchain
|
||||
)
|
||||
)
|
||||
store.dispatch(
|
||||
WalletAction.MultiWallet.AddTokens(
|
||||
walletManager.cardTokens.toList()
|
||||
walletManager.cardTokens.toList(),
|
||||
blockchainNetwork
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
@ -173,38 +208,60 @@ class MultiWalletMiddleware {
|
|||
}
|
||||
}
|
||||
|
||||
private fun addTokens(tokens: List<Token>, walletState: WalletState?, globalState: GlobalState?) {
|
||||
private fun handleAddingWalletManagers(
|
||||
globalState: GlobalState,
|
||||
walletManagers: List<WalletManager>
|
||||
) {
|
||||
globalState.feedbackManager?.infoHolder?.setWalletsInfo(walletManagers)
|
||||
if (globalState.scanResponse?.isDemoCard() == true) {
|
||||
addDummyBalances(walletManagers)
|
||||
}
|
||||
}
|
||||
|
||||
private fun addTokens(
|
||||
tokens: List<Token>, blockchainNetwork: BlockchainNetwork,
|
||||
walletState: WalletState?, globalState: GlobalState?
|
||||
) {
|
||||
val scanResponse = globalState?.scanResponse ?: return
|
||||
val wmFactory = globalState.tapWalletManager.walletManagerFactory
|
||||
|
||||
val groupedTokens = tokens.groupBy { it.blockchain }
|
||||
val walletManagers = groupedTokens.mapNotNull { entry ->
|
||||
val blockchain = entry.key
|
||||
val tokensList = entry.value
|
||||
val walletManager = walletState?.getWalletManager(blockchain)
|
||||
?: wmFactory.makeWalletManagerForApp(scanResponse, blockchain)?.also {
|
||||
store.dispatch(WalletAction.MultiWallet.AddWalletManagers(it))
|
||||
store.dispatch(WalletAction.MultiWallet.AddBlockchain(blockchain))
|
||||
}
|
||||
store.dispatch(WalletAction.LoadFiatRate(currencyList = tokensList.map { Currency.Token(it) }))
|
||||
walletManager?.apply { addTokens(tokensList) }
|
||||
}
|
||||
val walletManager = walletState?.getWalletManager(blockchainNetwork)
|
||||
?: wmFactory.makeWalletManagerForApp(scanResponse, blockchainNetwork)?.also {
|
||||
store.dispatch(WalletAction.MultiWallet.AddBlockchain(blockchainNetwork, it))
|
||||
} ?: return
|
||||
|
||||
store.dispatch(WalletAction.LoadFiatRate(currencyList = tokens.map { token ->
|
||||
Currency.Token(
|
||||
token, blockchainNetwork.blockchain, blockchainNetwork.derivationPath
|
||||
)
|
||||
}))
|
||||
walletManager.addTokens(tokens)
|
||||
currenciesRepository.saveUpdatedCurrency(
|
||||
cardId = scanResponse.card.cardId,
|
||||
blockchainNetwork = BlockchainNetwork.fromWalletManager(walletManager)
|
||||
)
|
||||
scope.launch {
|
||||
walletManagers.forEach { walletManager ->
|
||||
when (val result = walletManager.safeUpdate()) {
|
||||
is com.tangem.common.services.Result.Success -> {
|
||||
val wallet = result.data
|
||||
wallet.getTokens()
|
||||
.filter { tokens.contains(it) }
|
||||
.mapNotNull { token -> wallet.getTokenAmount(token)?.let { Pair(token, it) } }
|
||||
.forEach {
|
||||
withContext(Dispatchers.Main) {
|
||||
store.dispatch(WalletAction.MultiWallet.TokenLoaded(it.second, it.first))
|
||||
}
|
||||
when (val result = walletManager.safeUpdate()) {
|
||||
is com.tangem.common.services.Result.Success -> {
|
||||
val wallet = result.data
|
||||
wallet.getTokens()
|
||||
.filter { tokens.contains(it) }
|
||||
.mapNotNull { token ->
|
||||
wallet.getTokenAmount(token)?.let { Pair(token, it) }
|
||||
}
|
||||
.forEach {
|
||||
withContext(Dispatchers.Main) {
|
||||
store.dispatch(
|
||||
WalletAction.MultiWallet.TokenLoaded(
|
||||
it.second,
|
||||
it.first,
|
||||
blockchainNetwork
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
is com.tangem.common.services.Result.Failure -> {}
|
||||
}
|
||||
}
|
||||
is com.tangem.common.services.Result.Failure -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,8 +5,6 @@ import android.net.Uri
|
|||
import androidx.core.content.ContextCompat
|
||||
import com.tangem.blockchain.common.Amount
|
||||
import com.tangem.blockchain.common.AmountType
|
||||
import com.tangem.blockchain.common.Wallet
|
||||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.common.extensions.isZero
|
||||
|
|
@ -25,10 +23,7 @@ import com.tangem.tap.domain.extensions.toSendableAmounts
|
|||
import com.tangem.tap.features.demo.DemoHelper
|
||||
import com.tangem.tap.features.home.redux.HomeAction
|
||||
import com.tangem.tap.features.send.redux.PrepareSendScreen
|
||||
import com.tangem.tap.features.wallet.redux.Currency
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.features.wallet.redux.WalletData
|
||||
import com.tangem.tap.features.wallet.redux.WalletState
|
||||
import com.tangem.tap.features.wallet.redux.*
|
||||
import com.tangem.tap.network.NetworkStateChanged
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
|
|
@ -63,7 +58,11 @@ class WalletMiddleware {
|
|||
when (action) {
|
||||
is WalletAction.TradeCryptoAction -> tradeCryptoMiddleware.handle(state, action)
|
||||
is WalletAction.Warnings -> warningsMiddleware.handle(action, globalState)
|
||||
is WalletAction.MultiWallet -> multiWalletMiddleware.handle(action, walletState, globalState)
|
||||
is WalletAction.MultiWallet -> multiWalletMiddleware.handle(
|
||||
action,
|
||||
walletState,
|
||||
globalState
|
||||
)
|
||||
is WalletAction.LoadWallet -> {
|
||||
scope.launch {
|
||||
if (action.blockchain == null) {
|
||||
|
|
@ -79,9 +78,19 @@ class WalletMiddleware {
|
|||
is WalletAction.LoadWallet.Success -> {
|
||||
val coinAmount = action.wallet.amounts[AmountType.Coin]?.value
|
||||
if (coinAmount != null && !coinAmount.isZero()) {
|
||||
if (walletState.getWalletData(action.wallet.blockchain) == null) {
|
||||
store.dispatch(WalletAction.MultiWallet.AddBlockchain(action.wallet.blockchain))
|
||||
store.dispatch(WalletAction.LoadWallet.Success(action.wallet))
|
||||
if (walletState.getWalletData(action.blockchain) == null) {
|
||||
store.dispatch(
|
||||
WalletAction.MultiWallet.AddBlockchain(
|
||||
action.blockchain,
|
||||
null
|
||||
)
|
||||
)
|
||||
store.dispatch(
|
||||
WalletAction.LoadWallet.Success(
|
||||
action.wallet,
|
||||
action.blockchain
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
store.dispatch(WalletAction.Warnings.CheckHashesCount.CheckHashesCountOnline)
|
||||
|
|
@ -121,7 +130,8 @@ class WalletMiddleware {
|
|||
)
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
val scanNoteResponse = globalState.scanResponse?.copy(card = result.data)
|
||||
val scanNoteResponse =
|
||||
globalState.scanResponse?.copy(card = result.data)
|
||||
scanNoteResponse?.let { store.onCardScanned(scanNoteResponse) }
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
|
|
@ -197,8 +207,10 @@ class WalletMiddleware {
|
|||
}
|
||||
}
|
||||
is WalletAction.ShowDialog.QrCode -> {
|
||||
val selectedWalletData = walletState.getWalletData(walletState.selectedCurrency) ?: return
|
||||
val selectedAddressData = selectedWalletData.walletAddresses?.selectedAddress ?: return
|
||||
val selectedWalletData =
|
||||
walletState.getWalletData(walletState.selectedCurrency) ?: return
|
||||
val selectedAddressData =
|
||||
selectedWalletData.walletAddresses?.selectedAddress ?: return
|
||||
|
||||
val currency = selectedWalletData.currency
|
||||
store.dispatchDialogShow(AppDialog.AddressInfoDialog(currency, selectedAddressData))
|
||||
|
|
@ -209,28 +221,38 @@ class WalletMiddleware {
|
|||
private fun prepareSendAction(amount: Amount?, state: WalletState?): Action {
|
||||
val selectedWalletData = state?.getSelectedWalletData()
|
||||
val currency = selectedWalletData?.currency
|
||||
val walletManager = state?.getWalletManager(currency)
|
||||
val wallet = walletManager?.wallet
|
||||
val walletStore = state?.getWalletStore(currency)
|
||||
|
||||
return if (amount != null) {
|
||||
if (amount.type is AmountType.Token) {
|
||||
prepareSendActionForToken(amount, state, selectedWalletData, wallet, walletManager)
|
||||
prepareSendActionForToken(amount, state, selectedWalletData, walletStore)
|
||||
} else {
|
||||
PrepareSendScreen(amount, selectedWalletData?.fiatRate, walletManager)
|
||||
PrepareSendScreen(amount, selectedWalletData?.fiatRate, walletStore?.walletManager)
|
||||
}
|
||||
} else {
|
||||
val amounts = wallet?.amounts?.toSendableAmounts()
|
||||
val amounts = walletStore?.walletManager?.wallet?.amounts?.toSendableAmounts()
|
||||
if (currency != null && state.isMultiwalletAllowed) {
|
||||
when (currency) {
|
||||
is Currency.Blockchain -> {
|
||||
val amountToSend = amounts?.find { it.currencySymbol == currency.blockchain.currency }
|
||||
?: return WalletAction.Send.ChooseCurrency(amounts)
|
||||
PrepareSendScreen(amountToSend, selectedWalletData.fiatRate, walletManager)
|
||||
val amountToSend =
|
||||
amounts?.find { it.currencySymbol == currency.blockchain.currency }
|
||||
?: return WalletAction.Send.ChooseCurrency(amounts)
|
||||
PrepareSendScreen(
|
||||
coinAmount = amountToSend,
|
||||
coinRate = selectedWalletData.fiatRate,
|
||||
walletManager = walletStore.walletManager
|
||||
)
|
||||
}
|
||||
is Currency.Token -> {
|
||||
val amountToSend = amounts?.find { it.currencySymbol == currency.token.symbol }
|
||||
?: return WalletAction.Send.ChooseCurrency(amounts)
|
||||
prepareSendActionForToken(amountToSend, state, selectedWalletData, wallet, walletManager)
|
||||
val amountToSend =
|
||||
amounts?.find { it.currencySymbol == currency.token.symbol }
|
||||
?: return WalletAction.Send.ChooseCurrency(amounts)
|
||||
prepareSendActionForToken(
|
||||
amount = amountToSend,
|
||||
state = state,
|
||||
selectedWalletData = selectedWalletData,
|
||||
walletStore = walletStore
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
|
@ -238,24 +260,36 @@ class WalletMiddleware {
|
|||
WalletAction.Send.ChooseCurrency(amounts)
|
||||
} else {
|
||||
val amountToSend = amounts?.first()
|
||||
PrepareSendScreen(amountToSend, selectedWalletData?.fiatRate, walletManager)
|
||||
PrepareSendScreen(
|
||||
coinAmount = amountToSend,
|
||||
coinRate = selectedWalletData?.fiatRate,
|
||||
walletManager = walletStore?.walletManager
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun prepareSendActionForToken(
|
||||
amount: Amount, state: WalletState?, selectedWalletData: WalletData?, wallet: Wallet?,
|
||||
walletManager: WalletManager?
|
||||
amount: Amount,
|
||||
state: WalletState?,
|
||||
selectedWalletData: WalletData?,
|
||||
walletStore: WalletStore?
|
||||
): PrepareSendScreen {
|
||||
val coinRate = state?.getWalletData(wallet?.blockchain)?.fiatRate
|
||||
val coinRate = state?.getWalletData(walletStore?.blockchainNetwork)?.fiatRate
|
||||
val tokenRate = if (state?.isMultiwalletAllowed == true) {
|
||||
selectedWalletData?.fiatRate
|
||||
} else {
|
||||
selectedWalletData?.currencyData?.token?.fiatRate
|
||||
}
|
||||
val coinAmount = walletStore?.walletManager?.wallet?.amounts?.get(AmountType.Coin)
|
||||
|
||||
return PrepareSendScreen(
|
||||
wallet?.amounts?.get(AmountType.Coin), coinRate, walletManager,
|
||||
amount, tokenRate)
|
||||
coinAmount = coinAmount,
|
||||
coinRate = coinRate,
|
||||
walletManager = walletStore?.walletManager,
|
||||
tokenAmount = amount,
|
||||
tokenRate = tokenRate
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import com.tangem.common.extensions.isZero
|
|||
import com.tangem.tap.common.extensions.toFiatString
|
||||
import com.tangem.tap.common.extensions.toFormattedCurrencyString
|
||||
import com.tangem.tap.domain.getFirstToken
|
||||
import com.tangem.tap.domain.tokens.BlockchainNetwork
|
||||
import com.tangem.tap.features.wallet.models.removeUnknownTransactions
|
||||
import com.tangem.tap.features.wallet.models.toPendingTransactions
|
||||
import com.tangem.tap.features.wallet.redux.*
|
||||
|
|
@ -17,66 +18,85 @@ import com.tangem.tap.store
|
|||
class MultiWalletReducer {
|
||||
fun reduce(action: WalletAction.MultiWallet, state: WalletState): WalletState {
|
||||
return when (action) {
|
||||
is WalletAction.MultiWallet.AddWalletManagers -> {
|
||||
state.addWalletManagers(action.walletManagers)
|
||||
}
|
||||
is WalletAction.MultiWallet.AddBlockchains -> {
|
||||
val walletsData = action.blockchains.map { blockchain ->
|
||||
val wallet = state.getWalletManager(blockchain)?.wallet
|
||||
val wallets: List<WalletStore> = action.blockchains.map { blockchain ->
|
||||
val walletManager = action.walletManagers.first {
|
||||
it.wallet.blockchain == blockchain.blockchain &&
|
||||
(it.wallet.publicKey.derivationPath?.rawPath == blockchain.derivationPath)
|
||||
}
|
||||
val wallet = walletManager.wallet
|
||||
val cardToken = if (!state.isMultiwalletAllowed) {
|
||||
wallet?.getFirstToken()?.symbol?.let { TokenData("", tokenSymbol = it) }
|
||||
wallet.getFirstToken()?.symbol?.let { TokenData("", tokenSymbol = it) }
|
||||
} else {
|
||||
null
|
||||
}
|
||||
WalletData(
|
||||
val walletData = WalletData(
|
||||
currencyData = BalanceWidgetData(
|
||||
BalanceStatus.Loading,
|
||||
blockchain.fullName,
|
||||
currencySymbol = blockchain.currency,
|
||||
blockchain.blockchain.fullName,
|
||||
currencySymbol = blockchain.blockchain.currency,
|
||||
token = cardToken
|
||||
),
|
||||
walletAddresses = createAddressList(wallet),
|
||||
mainButton = WalletMainButton.SendButton(false),
|
||||
currency = Currency.Blockchain(blockchain),
|
||||
currency = Currency.Blockchain(
|
||||
blockchain.blockchain,
|
||||
blockchain.derivationPath
|
||||
),
|
||||
)
|
||||
|
||||
WalletStore(
|
||||
walletManager = walletManager,
|
||||
blockchainNetwork = blockchain,
|
||||
walletsData = listOf(walletData)
|
||||
)
|
||||
}
|
||||
|
||||
val selectedCurrency = if (!state.isMultiwalletAllowed) {
|
||||
walletsData[0].currency
|
||||
wallets[0].walletsData[0].currency
|
||||
} else {
|
||||
state.selectedCurrency
|
||||
}
|
||||
state.copy(
|
||||
walletsData = walletsData,
|
||||
wallets = wallets,
|
||||
selectedCurrency = selectedCurrency
|
||||
)
|
||||
}
|
||||
is WalletAction.MultiWallet.AddBlockchain -> {
|
||||
val wallet = state.getWalletManager(action.blockchain)?.wallet
|
||||
val walletManager = action.walletManager ?: state.getWalletManager(action.blockchain)
|
||||
val wallet = walletManager?.wallet
|
||||
|
||||
val walletData = WalletData(
|
||||
currencyData = BalanceWidgetData(
|
||||
BalanceStatus.Loading,
|
||||
action.blockchain.fullName,
|
||||
currencySymbol = action.blockchain.currency,
|
||||
action.blockchain.blockchain.fullName,
|
||||
currencySymbol = action.blockchain.blockchain.currency,
|
||||
),
|
||||
walletAddresses = createAddressList(wallet),
|
||||
mainButton = WalletMainButton.SendButton(false),
|
||||
currency = Currency.Blockchain(action.blockchain),
|
||||
currency = Currency.Blockchain(
|
||||
action.blockchain.blockchain,
|
||||
action.blockchain.derivationPath
|
||||
),
|
||||
)
|
||||
val newState = state.copy(walletsData = state.replaceWalletInWallets(walletData))
|
||||
val walletStore = WalletStore(
|
||||
walletManager = walletManager,
|
||||
blockchainNetwork = action.blockchain,
|
||||
walletsData = listOf(walletData)
|
||||
)
|
||||
|
||||
val newState = state.updateWalletStore(walletStore)
|
||||
if (wallet != null && wallet.amounts[AmountType.Coin]?.value != null) {
|
||||
OnWalletLoadedReducer().reduce(wallet, newState)
|
||||
OnWalletLoadedReducer().reduce(wallet, action.blockchain, newState)
|
||||
} else {
|
||||
newState
|
||||
}
|
||||
}
|
||||
is WalletAction.MultiWallet.AddTokens -> {
|
||||
val wallets = action.tokens.mapNotNull { token -> token.toWallet(state) }
|
||||
state.copy(walletsData = state.replaceSomeWallets(wallets))
|
||||
addTokens(action.tokens, action.blockchain, state)
|
||||
}
|
||||
is WalletAction.MultiWallet.AddToken -> {
|
||||
val walletData = action.token.toWallet(state) ?: return state
|
||||
state.copy(walletsData = state.replaceWalletInWallets(walletData))
|
||||
addTokens(listOf(action.token), action.blockchain, state)
|
||||
}
|
||||
is WalletAction.MultiWallet.TokenLoaded -> {
|
||||
val pendingTransactions = state.getWalletManager(action.token)
|
||||
|
|
@ -84,7 +104,8 @@ class MultiWalletReducer {
|
|||
wallet.recentTransactions.toPendingTransactions(wallet.address)
|
||||
} ?: emptyList()
|
||||
|
||||
val sendButtonEnabled = action.amount.value?.isZero() == false && pendingTransactions.isEmpty()
|
||||
val sendButtonEnabled =
|
||||
action.amount.value?.isZero() == false && pendingTransactions.isEmpty()
|
||||
val tokenPendingTransactions = pendingTransactions
|
||||
.filter { it.currency == action.amount.currencySymbol }
|
||||
val tokenBalanceStatus = when {
|
||||
|
|
@ -107,10 +128,13 @@ class MultiWalletReducer {
|
|||
),
|
||||
pendingTransactions = pendingTransactions.removeUnknownTransactions(),
|
||||
mainButton = WalletMainButton.SendButton(sendButtonEnabled),
|
||||
currency = Currency.Token(action.token)
|
||||
currency = Currency.Token(
|
||||
token = action.token,
|
||||
blockchain = action.blockchain.blockchain,
|
||||
derivationPath = action.blockchain.derivationPath
|
||||
)
|
||||
)
|
||||
val wallets = state.replaceWalletInWallets(newTokenWalletData)
|
||||
state.copy(walletsData = wallets)
|
||||
state.updateWalletData(newTokenWalletData)
|
||||
}
|
||||
is WalletAction.MultiWallet.SetIsMultiwalletAllowed ->
|
||||
state.copy(isMultiwalletAllowed = action.isMultiwalletAllowed)
|
||||
|
|
@ -119,21 +143,7 @@ class MultiWalletReducer {
|
|||
state.copy(selectedCurrency = action.walletData?.currency)
|
||||
|
||||
is WalletAction.MultiWallet.RemoveWallet -> {
|
||||
val wallets = state.walletsData.filterNot {
|
||||
it.currency == action.walletData.currency
|
||||
}
|
||||
if (action.walletData.currency is Currency.Blockchain) {
|
||||
state.copy(
|
||||
walletsData = wallets,
|
||||
walletManagers = state.walletManagers.filterNot {
|
||||
it.wallet.blockchain == action.walletData.currency.blockchain
|
||||
}
|
||||
)
|
||||
} else {
|
||||
state.copy(walletsData = wallets)
|
||||
}
|
||||
|
||||
|
||||
state.removeWallet(action.walletData)
|
||||
}
|
||||
is WalletAction.MultiWallet.SetPrimaryBlockchain ->
|
||||
state.copy(primaryBlockchain = action.blockchain)
|
||||
|
|
@ -147,7 +157,14 @@ class MultiWalletReducer {
|
|||
}
|
||||
}
|
||||
|
||||
fun Token.toWallet(state: WalletState): WalletData? {
|
||||
private fun addTokens(
|
||||
tokens: List<Token>, blockchain: BlockchainNetwork, state: WalletState
|
||||
): WalletState {
|
||||
val wallets = tokens.mapNotNull { token -> token.toWallet(state, blockchain) }
|
||||
return state.updateWalletsData(wallets)
|
||||
}
|
||||
|
||||
fun Token.toWallet(state: WalletState, blockchain: BlockchainNetwork): WalletData? {
|
||||
if (!state.isMultiwalletAllowed) return null
|
||||
if (state.currencies.any { it is Currency.Token && it.token == this }) {
|
||||
return null
|
||||
|
|
@ -164,6 +181,6 @@ fun Token.toWallet(state: WalletState): WalletData? {
|
|||
),
|
||||
walletAddresses = walletAddresses,
|
||||
mainButton = WalletMainButton.SendButton(false),
|
||||
currency = Currency.Token(this),
|
||||
currency = Currency.fromBlockchainNetwork(blockchain, this)
|
||||
)
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@ 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.getFirstToken
|
||||
import com.tangem.tap.domain.tokens.BlockchainNetwork
|
||||
import com.tangem.tap.features.wallet.models.removeUnknownTransactions
|
||||
import com.tangem.tap.features.wallet.models.toPendingTransactions
|
||||
import com.tangem.tap.features.wallet.redux.*
|
||||
|
|
@ -19,25 +20,30 @@ import java.math.RoundingMode
|
|||
|
||||
class OnWalletLoadedReducer {
|
||||
|
||||
fun reduce(wallet: Wallet, walletState: WalletState): WalletState {
|
||||
fun reduce(wallet: Wallet, blockchainNetwork: BlockchainNetwork, walletState: WalletState): WalletState {
|
||||
return if (!walletState.isMultiwalletAllowed) {
|
||||
onSingleWalletLoaded(wallet, walletState)
|
||||
} else {
|
||||
onMultiWalletLoaded(wallet, walletState)
|
||||
onMultiWalletLoaded(wallet, blockchainNetwork, walletState)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onMultiWalletLoaded(wallet: Wallet, walletState: WalletState): WalletState {
|
||||
private fun onMultiWalletLoaded(
|
||||
wallet: Wallet,
|
||||
blockchainNetwork: BlockchainNetwork,
|
||||
walletState: WalletState
|
||||
): WalletState {
|
||||
val fiatCurrencySymbol = store.state.globalState.appCurrency
|
||||
val exchangeManager = store.state.globalState.currencyExchangeManager
|
||||
|
||||
val coinAmountValue = wallet.amounts[AmountType.Coin]?.value
|
||||
if (walletState.getWalletData(wallet.blockchain) == null) {
|
||||
if (walletState.getWalletData(blockchainNetwork) == null) {
|
||||
return walletState
|
||||
}
|
||||
val formattedAmount = coinAmountValue?.toFormattedCurrencyString(
|
||||
wallet.blockchain.decimals(),
|
||||
wallet.blockchain.currency)
|
||||
wallet.blockchain.currency
|
||||
)
|
||||
|
||||
val pendingTransactions = wallet.recentTransactions
|
||||
.toPendingTransactions(wallet.address)
|
||||
|
|
@ -48,7 +54,7 @@ class OnWalletLoadedReducer {
|
|||
} else {
|
||||
BalanceStatus.VerifiedOnline
|
||||
}
|
||||
val walletData = walletState.getWalletData(wallet.blockchain)
|
||||
val walletData = walletState.getWalletData(blockchainNetwork)
|
||||
|
||||
val fiatAmount = walletData?.fiatRate?.let { coinAmountValue?.toFiatValue(it) }
|
||||
val newWalletData = walletData?.copy(
|
||||
|
|
@ -63,29 +69,34 @@ class OnWalletLoadedReducer {
|
|||
),
|
||||
pendingTransactions = pendingTransactions.removeUnknownTransactions(),
|
||||
mainButton = WalletMainButton.SendButton(coinSendButton),
|
||||
currency = Currency.Blockchain(wallet.blockchain),
|
||||
currency = Currency.fromBlockchainNetwork(blockchainNetwork),
|
||||
tradeCryptoState = TradeCryptoState.from(exchangeManager, walletData),
|
||||
)
|
||||
|
||||
val tokens = wallet.getTokens().mapNotNull { token ->
|
||||
val tokenWalletData = walletState.getWalletData(token)
|
||||
val tokenPendingTransactions = pendingTransactions.filter { it.currency == token.symbol }
|
||||
val tokenPendingTransactions =
|
||||
pendingTransactions.filter { it.currency == token.symbol }
|
||||
val tokenBalanceStatus = when {
|
||||
tokenPendingTransactions.isNotEmpty() -> BalanceStatus.TransactionInProgress
|
||||
pendingTransactions.isNotEmpty() -> BalanceStatus.SameCurrencyTransactionInProgress
|
||||
else -> BalanceStatus.VerifiedOnline
|
||||
}
|
||||
val tokenAmountValue = wallet.getTokenAmount(token)?.value
|
||||
val tokenFiatAmount = tokenWalletData?.fiatRate?.let { rate -> tokenAmountValue?.toFiatValue(rate) }
|
||||
val tokenFiatAmount =
|
||||
tokenWalletData?.fiatRate?.let { rate -> tokenAmountValue?.toFiatValue(rate) }
|
||||
|
||||
val tokenSendButton = newWalletData?.shouldEnableTokenSendButton() == true
|
||||
&& tokenPendingTransactions.isEmpty()
|
||||
&& tokenPendingTransactions.isEmpty()
|
||||
tokenWalletData?.copy(
|
||||
currencyData = tokenWalletData.currencyData.copy(
|
||||
status = tokenBalanceStatus,
|
||||
blockchainAmount = coinAmountValue,
|
||||
amount = tokenAmountValue,
|
||||
amountFormatted = tokenAmountValue?.toFormattedCurrencyString(token.decimals, token.symbol),
|
||||
amountFormatted = tokenAmountValue?.toFormattedCurrencyString(
|
||||
token.decimals,
|
||||
token.symbol
|
||||
),
|
||||
fiatAmount = tokenFiatAmount,
|
||||
fiatAmountFormatted = tokenFiatAmount?.toFormattedFiatValue(fiatCurrencySymbol)
|
||||
),
|
||||
|
|
@ -102,8 +113,9 @@ class OnWalletLoadedReducer {
|
|||
} else {
|
||||
ProgressState.Done
|
||||
}
|
||||
return walletState.copy(
|
||||
state = state, walletsData = wallets, error = null
|
||||
val newState = walletState.updateWalletsData(wallets)
|
||||
return newState.copy(
|
||||
state = state, error = null
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -118,7 +130,8 @@ class OnWalletLoadedReducer {
|
|||
val tokenAmount = wallet.getTokenAmount(token)
|
||||
if (tokenAmount != null) {
|
||||
val tokenFiatRate = walletState.primaryWallet?.currencyData?.token?.fiatRate
|
||||
val tokenFiatAmount = tokenFiatRate?.let { tokenAmount.value?.toFiatString(it, fiatCurrencySymbol) }
|
||||
val tokenFiatAmount =
|
||||
tokenFiatRate?.let { tokenAmount.value?.toFiatString(it, fiatCurrencySymbol) }
|
||||
TokenData(
|
||||
tokenAmount.value?.toFormattedCurrencyString(
|
||||
token.decimals, token.symbol
|
||||
|
|
@ -134,7 +147,8 @@ class OnWalletLoadedReducer {
|
|||
val amount = wallet.amounts[AmountType.Coin]?.value
|
||||
val formattedAmount = amount?.toFormattedCurrencyString(
|
||||
wallet.blockchain.decimals(),
|
||||
wallet.blockchain.currency)
|
||||
wallet.blockchain.currency
|
||||
)
|
||||
val fiatRate = walletState.primaryWallet?.fiatRate
|
||||
val fiatAmountRaw = fiatRate?.multiply(amount)?.setScale(2, RoundingMode.DOWN)
|
||||
val fiatAmount = fiatRate?.let { amount?.toFiatString(it, fiatCurrencySymbol) }
|
||||
|
|
@ -161,9 +175,11 @@ class OnWalletLoadedReducer {
|
|||
mainButton = WalletMainButton.SendButton(sendButtonEnabled),
|
||||
tradeCryptoState = TradeCryptoState.from(exchangeManager, walletState.primaryWallet),
|
||||
)
|
||||
val wallets = walletData?.let { listOf(walletData) } ?: emptyList()
|
||||
return walletState.copy(
|
||||
state = ProgressState.Done, walletsData = wallets, error = null
|
||||
val wallets = listOfNotNull(walletData)
|
||||
val updatedStore = walletState.getWalletStore(walletData?.currency)?.updateWallets(wallets)
|
||||
|
||||
return walletState.updateWalletStore(updatedStore).copy(
|
||||
state = ProgressState.Done, error = null
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -13,6 +13,7 @@ import com.tangem.tap.common.redux.global.FiatCurrencyName
|
|||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.domain.extensions.getArtworkUrl
|
||||
import com.tangem.tap.domain.getFirstToken
|
||||
import com.tangem.tap.domain.tokens.BlockchainNetwork
|
||||
import com.tangem.tap.features.wallet.redux.*
|
||||
import com.tangem.tap.features.wallet.ui.BalanceStatus
|
||||
import com.tangem.tap.features.wallet.ui.BalanceWidgetData
|
||||
|
|
@ -46,11 +47,21 @@ private fun internalReduce(action: Action, state: AppState): WalletState {
|
|||
is WalletAction.EmptyWallet -> {
|
||||
newState = newState.copy(
|
||||
state = ProgressState.Done,
|
||||
walletsData = listOf(
|
||||
WalletData(
|
||||
currencyData = BalanceWidgetData(BalanceStatus.EmptyCard),
|
||||
mainButton = WalletMainButton.CreateWalletButton(true),
|
||||
currency = Currency.Blockchain(Blockchain.Unknown)
|
||||
wallets = listOf(
|
||||
WalletStore(
|
||||
walletManager = null,
|
||||
blockchainNetwork = BlockchainNetwork(
|
||||
Blockchain.Unknown,
|
||||
null,
|
||||
emptyList()
|
||||
),
|
||||
walletsData = listOf(
|
||||
WalletData(
|
||||
currencyData = BalanceWidgetData(BalanceStatus.EmptyCard),
|
||||
mainButton = WalletMainButton.CreateWalletButton(true),
|
||||
currency = Currency.Blockchain(Blockchain.Unknown, null)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
|
@ -58,27 +69,43 @@ private fun internalReduce(action: Action, state: AppState): WalletState {
|
|||
is WalletAction.LoadData.Failure -> {
|
||||
when (action.error) {
|
||||
is TapError.NoInternetConnection -> {
|
||||
val wallets = newState.walletsData
|
||||
val wallets = newState.wallets
|
||||
.map {
|
||||
it.copy(
|
||||
currencyData = it.currencyData.copy(
|
||||
status = BalanceStatus.Unreachable
|
||||
)
|
||||
walletsData = it.walletsData.map {
|
||||
it.copy(
|
||||
currencyData = it.currencyData.copy(
|
||||
status = BalanceStatus.Unreachable
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
newState = newState.copy(
|
||||
state = ProgressState.Error,
|
||||
error = ErrorType.NoInternetConnection,
|
||||
walletsData = wallets
|
||||
wallets = wallets
|
||||
)
|
||||
}
|
||||
is TapError.UnknownBlockchain -> {
|
||||
newState = newState.copy(
|
||||
state = ProgressState.Done,
|
||||
walletsData = listOf(
|
||||
WalletData(
|
||||
currencyData = BalanceWidgetData(BalanceStatus.UnknownBlockchain),
|
||||
currency = Currency.Blockchain(Blockchain.Unknown)
|
||||
wallets = listOf(
|
||||
WalletStore(
|
||||
walletManager = null,
|
||||
blockchainNetwork = BlockchainNetwork(
|
||||
Blockchain.Unknown,
|
||||
null,
|
||||
emptyList()
|
||||
),
|
||||
walletsData = listOf(
|
||||
WalletData(
|
||||
currencyData = BalanceWidgetData(BalanceStatus.UnknownBlockchain),
|
||||
currency = Currency.Blockchain(Blockchain.Unknown, null)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
|
@ -94,26 +121,37 @@ private fun internalReduce(action: Action, state: AppState): WalletState {
|
|||
}
|
||||
is WalletAction.LoadWallet -> {
|
||||
if (action.blockchain == null) {
|
||||
val wallets = newState.walletsData.map { wallet ->
|
||||
wallet.copy(
|
||||
currencyData = wallet.currencyData.copy(
|
||||
status = BalanceStatus.Loading,
|
||||
currency = wallet.currencyData.currency,
|
||||
currencySymbol = wallet.currencyData.currencySymbol,
|
||||
),
|
||||
mainButton = WalletMainButton.SendButton(false),
|
||||
tradeCryptoState = TradeCryptoState.from(exchangeManager, wallet)
|
||||
val wallets = newState.wallets.map {
|
||||
it.copy(
|
||||
walletsData = it.walletsData.map { walletData ->
|
||||
walletData.copy(
|
||||
currencyData = walletData.currencyData.copy(
|
||||
status = BalanceStatus.Loading,
|
||||
currency = walletData.currencyData.currency,
|
||||
currencySymbol = walletData.currencyData.currencySymbol,
|
||||
),
|
||||
mainButton = WalletMainButton.SendButton(false),
|
||||
tradeCryptoState = TradeCryptoState.from(
|
||||
exchangeManager,
|
||||
walletData
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
newState = newState.copy(
|
||||
state = ProgressState.Loading,
|
||||
walletsData = wallets,
|
||||
wallets = wallets,
|
||||
)
|
||||
} else {
|
||||
val walletManager = newState.getWalletManager(action.blockchain) ?: return newState
|
||||
val blockchain = walletManager.wallet.blockchain
|
||||
val currencies = listOf(Currency.Blockchain(blockchain)) +
|
||||
walletManager.cardTokens.map { Currency.Token(it) }
|
||||
val currencies = listOf(Currency.fromBlockchainNetwork(action.blockchain)) +
|
||||
walletManager.cardTokens.map {
|
||||
Currency.fromBlockchainNetwork(
|
||||
action.blockchain,
|
||||
it
|
||||
)
|
||||
}
|
||||
val newWallets = newState.walletsData.filter { currencies.contains(it.currency) }
|
||||
.map { wallet ->
|
||||
wallet.copy(
|
||||
|
|
@ -126,38 +164,52 @@ private fun internalReduce(action: Action, state: AppState): WalletState {
|
|||
tradeCryptoState = TradeCryptoState.from(exchangeManager, wallet)
|
||||
)
|
||||
}
|
||||
val wallets = newState.replaceSomeWallets(newWallets)
|
||||
newState = newState.copy(walletsData = newState.updateTradeCryptoState(exchangeManager, wallets))
|
||||
val wallets = newState.updateTradeCryptoState(exchangeManager, newState.replaceSomeWallets(newWallets))
|
||||
val walletStore = newState.getWalletStore(action.blockchain)?.updateWallets(wallets)
|
||||
newState = newState.updateWalletStore(walletStore)
|
||||
}
|
||||
}
|
||||
is WalletAction.LoadWallet.Success -> newState = onWalletLoadedReducer.reduce(action.wallet, newState)
|
||||
is WalletAction.LoadWallet.Success -> newState = onWalletLoadedReducer.reduce(
|
||||
wallet = action.wallet,
|
||||
blockchainNetwork = action.blockchain,
|
||||
walletState = newState
|
||||
)
|
||||
is WalletAction.LoadWallet.NoAccount -> {
|
||||
val walletData = newState.getWalletData(action.wallet.blockchain)?.copy(
|
||||
val walletData = newState.getWalletData(action.blockchain)?.copy(
|
||||
currencyData = BalanceWidgetData(
|
||||
BalanceStatus.NoAccount, action.wallet.blockchain.fullName,
|
||||
currencySymbol = action.wallet.blockchain.currency,
|
||||
amountToCreateAccount = action.amountToCreateAccount
|
||||
)
|
||||
)
|
||||
val wallets = newState.replaceWalletInWallets(walletData)
|
||||
var updatedWalletStore = newState.getWalletStore(action.blockchain)
|
||||
?.updateWallets(listOfNotNull(walletData))
|
||||
|
||||
val progressState =
|
||||
if (wallets.any { it.currencyData.status == BalanceStatus.Loading }) {
|
||||
if (updatedWalletStore?.walletsData?.any { it.currencyData.status == BalanceStatus.Loading } == true) {
|
||||
ProgressState.Loading
|
||||
} else {
|
||||
ProgressState.Done
|
||||
}
|
||||
newState = newState.copy(
|
||||
state = progressState,
|
||||
walletsData = newState.updateTradeCryptoState(exchangeManager, wallets)
|
||||
)
|
||||
updatedWalletStore =
|
||||
updatedWalletStore?.updateWallets(
|
||||
newState.updateTradeCryptoState(exchangeManager, updatedWalletStore.walletsData)
|
||||
)
|
||||
|
||||
newState = newState.updateWalletStore(updatedWalletStore)
|
||||
.copy(
|
||||
state = progressState
|
||||
)
|
||||
}
|
||||
|
||||
is WalletAction.LoadWallet.Failure -> {
|
||||
val message = if (newState.error == ErrorType.NoInternetConnection) {
|
||||
null
|
||||
} else {
|
||||
action.errorMessage
|
||||
}
|
||||
val walletData = newState.getWalletData(action.wallet.blockchain)
|
||||
val walletStore = newState.getWalletStore(action.wallet)
|
||||
val walletData = walletStore?.walletsData?.first { it.currency is Currency.Blockchain }
|
||||
val newWalletData = walletData?.copy(
|
||||
currencyData = walletData.currencyData.copy(
|
||||
status = BalanceStatus.Unreachable,
|
||||
|
|
@ -173,17 +225,23 @@ private fun internalReduce(action: Action, state: AppState): WalletState {
|
|||
)
|
||||
)
|
||||
}
|
||||
val wallets = newState.replaceSomeWallets(listOfNotNull(newWalletData) + tokenWallets)
|
||||
val updatedWallets =
|
||||
newState.updateTradeCryptoState(
|
||||
exchangeManager,
|
||||
walletStore!!.updateWallets(listOfNotNull(newWalletData) + tokenWallets).walletsData
|
||||
)
|
||||
|
||||
newState = newState.updateWalletsData(updatedWallets)
|
||||
|
||||
val progressState =
|
||||
if (wallets.any { it.currencyData.status == BalanceStatus.Loading }) {
|
||||
if (newState.walletsData.any { it.currencyData.status == BalanceStatus.Loading }) {
|
||||
ProgressState.Loading
|
||||
} else {
|
||||
ProgressState.Done
|
||||
}
|
||||
|
||||
newState = newState.copy(
|
||||
state = progressState,
|
||||
walletsData = newState.updateTradeCryptoState(exchangeManager, wallets)
|
||||
)
|
||||
}
|
||||
is WalletAction.SetArtworkId -> {
|
||||
|
|
@ -230,25 +288,21 @@ private fun internalReduce(action: Action, state: AppState): WalletState {
|
|||
?: return newState
|
||||
val address = walletAddresses.list.firstOrNull { it.type == action.type }
|
||||
?: return newState
|
||||
val wallets = newState.replaceWalletInWallets(
|
||||
selectedWalletData?.copy(
|
||||
walletAddresses = WalletAddresses(
|
||||
address,
|
||||
walletAddresses.list
|
||||
)
|
||||
newState = newState.updateWalletData(selectedWalletData?.copy(
|
||||
walletAddresses = WalletAddresses(
|
||||
address,
|
||||
walletAddresses.list
|
||||
)
|
||||
)
|
||||
newState = newState.copy(walletsData = wallets)
|
||||
))
|
||||
}
|
||||
is WalletAction.SetWalletRent -> {
|
||||
var walletData = newState.getWalletData(action.blockchain)
|
||||
if (walletData == null) {
|
||||
newState
|
||||
} else {
|
||||
walletData = walletData.copy(warningRent = WalletRent(action.minRent, action.rentExempt))
|
||||
newState = newState.copy(
|
||||
walletsData = newState.replaceSomeWallets(listOf(walletData))
|
||||
)
|
||||
walletData =
|
||||
walletData.copy(warningRent = WalletRent(action.minRent, action.rentExempt))
|
||||
newState = newState.updateWalletsData(listOf(walletData))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -322,8 +376,11 @@ private fun setMultiWalletFiatRate(
|
|||
rate: BigDecimal, rateFormatted: String, currency: Currency,
|
||||
appCurrency: FiatCurrencyName, state: WalletState
|
||||
): WalletState {
|
||||
|
||||
val walletStore = state.getWalletStore(currency) ?: return state
|
||||
val wallet = walletStore.walletManager?.wallet
|
||||
val walletData = state.getWalletData(currency) ?: return state
|
||||
val wallet = state.getWalletManager(currency)?.wallet
|
||||
|
||||
val fiatAmount = when (currency) {
|
||||
is Currency.Blockchain ->
|
||||
wallet?.amounts?.get(AmountType.Coin)?.value?.toFiatValue(rate)
|
||||
|
|
@ -338,14 +395,14 @@ private fun setMultiWalletFiatRate(
|
|||
),
|
||||
fiatRate = rate, fiatRateString = rateFormatted
|
||||
)
|
||||
return state.copy(walletsData = state.replaceWalletInWallets(newWalletData))
|
||||
return state.updateWalletData(newWalletData)
|
||||
}
|
||||
|
||||
private fun setSingeWalletFiatRate(
|
||||
rate: BigDecimal, rateFormatted: String, currency: Currency,
|
||||
appCurrency: FiatCurrencyName, state: WalletState
|
||||
): WalletState {
|
||||
val wallet = state.walletManagers[0].wallet
|
||||
val wallet = state.primaryWalletManager?.wallet ?: return state
|
||||
val token = wallet.getFirstToken()
|
||||
|
||||
if (currency == state.primaryWallet?.currency) {
|
||||
|
|
@ -356,7 +413,7 @@ private fun setSingeWalletFiatRate(
|
|||
fiatRate = rate,
|
||||
fiatRateString = rateFormatted
|
||||
)
|
||||
return state.copy(walletsData = listOf(walletData))
|
||||
return state.updateWalletData(walletData)
|
||||
} else if (currency is Currency.Token && currency.token == token) {
|
||||
val tokenFiatAmount = wallet.getTokenAmount(token)?.value?.toFiatString(rate, appCurrency)
|
||||
val tokenData = state.primaryWallet?.currencyData?.token?.copy(
|
||||
|
|
@ -371,7 +428,7 @@ private fun setSingeWalletFiatRate(
|
|||
)
|
||||
)
|
||||
val wallets = walletData?.let { listOf(walletData) } ?: emptyList()
|
||||
return state.copy(walletsData = wallets)
|
||||
return state.updateWalletsData(wallets)
|
||||
}
|
||||
return state
|
||||
}
|
||||
|
|
@ -18,6 +18,7 @@ import com.tangem.tap.common.SnackbarHandler
|
|||
import com.tangem.tap.common.extensions.*
|
||||
import com.tangem.tap.common.redux.StateDialog
|
||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
import com.tangem.tap.domain.tokens.BlockchainNetwork
|
||||
import com.tangem.tap.features.onboarding.getQRReceiveMessage
|
||||
import com.tangem.tap.features.wallet.models.PendingTransaction
|
||||
import com.tangem.tap.features.wallet.redux.*
|
||||
|
|
@ -114,7 +115,7 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details),
|
|||
if (selectedWallet.currencyData.status != BalanceStatus.Loading) {
|
||||
store.dispatch(
|
||||
WalletAction.LoadWallet(
|
||||
blockchain = selectedWallet.currency.blockchain
|
||||
blockchain = BlockchainNetwork(selectedWallet.currency.blockchain, selectedWallet.currency.derivationPath, emptyList())
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
@ -188,7 +189,7 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details),
|
|||
|
||||
private fun showPendingTransactionsIfPresent(pendingTransactions: List<PendingTransaction>) {
|
||||
pendingTransactionAdapter.submitList(pendingTransactions)
|
||||
binding.rvPendingTransaction?.show(pendingTransactions.isNotEmpty())
|
||||
binding.rvPendingTransaction.show(pendingTransactions.isNotEmpty())
|
||||
}
|
||||
|
||||
private fun setupAddressCard(state: WalletData) = with(binding.lWalletDetails) {
|
||||
|
|
@ -212,7 +213,7 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details),
|
|||
chipGroupAddressType.hide()
|
||||
}
|
||||
tvAddress.text = state.walletAddresses.selectedAddress.address
|
||||
tvExplore?.setOnClickListener {
|
||||
tvExplore.setOnClickListener {
|
||||
store.dispatch(
|
||||
WalletAction.ExploreAddress(
|
||||
state.walletAddresses.selectedAddress.exploreUrl,
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import com.tangem.blockchain.common.Token
|
|||
import com.tangem.tap.common.extensions.getString
|
||||
import com.tangem.tap.common.extensions.loadCurrenciesIcon
|
||||
import com.tangem.tap.common.extensions.show
|
||||
import com.tangem.tap.domain.TapWorkarounds.derivationStyle
|
||||
import com.tangem.tap.features.wallet.redux.Currency
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.features.wallet.redux.WalletData
|
||||
|
|
@ -26,7 +27,11 @@ class WalletAdapter
|
|||
return currentList[position].currencyData.currencySymbol?.hashCode()?.toLong() ?: 0
|
||||
}
|
||||
|
||||
fun submitList(list: List<WalletData>, primaryBlockchain: Blockchain?, primaryToken: Token? = null) {
|
||||
fun submitList(
|
||||
list: List<WalletData>,
|
||||
primaryBlockchain: Blockchain?,
|
||||
primaryToken: Token? = null
|
||||
) {
|
||||
// We used this method to sort the list of currencies. Sorting is disabled for now.
|
||||
super.submitList(list)
|
||||
}
|
||||
|
|
@ -44,21 +49,22 @@ class WalletAdapter
|
|||
|
||||
object DiffUtilCallback : DiffUtil.ItemCallback<WalletData>() {
|
||||
override fun areContentsTheSame(
|
||||
oldItem: WalletData, newItem: WalletData
|
||||
oldItem: WalletData, newItem: WalletData
|
||||
) = oldItem == newItem
|
||||
|
||||
override fun areItemsTheSame(
|
||||
oldItem: WalletData, newItem: WalletData
|
||||
oldItem: WalletData, newItem: WalletData
|
||||
) = oldItem == newItem
|
||||
}
|
||||
|
||||
class WalletsViewHolder(val binding: ItemCurrencyWalletBinding) :
|
||||
RecyclerView.ViewHolder(binding.root) {
|
||||
RecyclerView.ViewHolder(binding.root) {
|
||||
|
||||
fun bind(wallet: WalletData) = with(binding) {
|
||||
tvCurrency.text = wallet.currencyData.currency
|
||||
tvAmount.text = wallet.currencyData.amountFormatted?.takeWhile { !it.isWhitespace() }
|
||||
tvCurrencySymbol.text = wallet.currencyData.amountFormatted?.takeLastWhile { !it.isWhitespace() }
|
||||
tvCurrencySymbol.text =
|
||||
wallet.currencyData.amountFormatted?.takeLastWhile { !it.isWhitespace() }
|
||||
tvAmountFiat.text = wallet.currencyData.fiatAmountFormatted
|
||||
tvExchangeRate.text = wallet.fiatRateString
|
||||
cardWallet.setOnClickListener {
|
||||
|
|
@ -67,6 +73,11 @@ class WalletAdapter
|
|||
val blockchain = wallet.currency.blockchain
|
||||
val token = (wallet.currency as? Currency.Token)?.token
|
||||
|
||||
val isCustom =
|
||||
wallet.currency.isCustomCurrency(store.state.globalState.scanResponse?.card?.derivationStyle)
|
||||
tvExchangeRate.show(!isCustom)
|
||||
tvCustomCurrency.show(isCustom)
|
||||
|
||||
Picasso.get().loadCurrenciesIcon(
|
||||
imageView = ivCurrency,
|
||||
textView = tvTokenLetter,
|
||||
|
|
|
|||
|
|
@ -8,6 +8,9 @@ import com.tangem.tap.common.extensions.show
|
|||
import com.tangem.tap.common.redux.StateDialog
|
||||
import com.tangem.tap.common.redux.navigation.AppScreen
|
||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
import com.tangem.tap.currenciesRepository
|
||||
import com.tangem.tap.domain.TapWorkarounds.derivationStyle
|
||||
import com.tangem.tap.domain.TapWorkarounds.isTestCard
|
||||
import com.tangem.tap.features.tokens.redux.TokensAction
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.features.wallet.redux.WalletDialog
|
||||
|
|
@ -97,8 +100,20 @@ class MultiWalletView : WalletView {
|
|||
walletsAdapter.submitList(state.walletsData, state.primaryBlockchain, state.primaryToken)
|
||||
|
||||
binding.btnAddToken.setOnClickListener {
|
||||
store.dispatch(TokensAction.LoadCurrencies)
|
||||
store.dispatch(TokensAction.SetAddedCurrencies(state.walletsData))
|
||||
val card = store.state.globalState.scanResponse!!.card
|
||||
store.dispatch(TokensAction.LoadCurrencies(
|
||||
supportedBlockchains = currenciesRepository.getBlockchains(
|
||||
card.firmwareVersion,
|
||||
card.isTestCard
|
||||
)))
|
||||
store.dispatch(TokensAction.AllowToAddTokens(true))
|
||||
store.dispatch(TokensAction.SetAddedCurrencies(
|
||||
wallets = state.walletsData,
|
||||
derivationStyle = card.derivationStyle
|
||||
))
|
||||
store.dispatch(TokensAction.SetNonRemovableCurrencies(
|
||||
state.walletsData.filterNot { state.canBeRemoved(it) })
|
||||
)
|
||||
store.dispatch(NavigationAction.NavigateTo(AppScreen.AddTokens))
|
||||
}
|
||||
handleErrorStates(state = state, binding = binding, fragment = fragment)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue