Updated on 2026-08-14
This commit is contained in:
parent
8312c85fa6
commit
2d0393be5d
32 changed files with 543 additions and 306 deletions
|
|
@ -62,7 +62,9 @@ class TapApplication : Application() {
|
|||
NetworkConnectivity.createInstance(store, this)
|
||||
preferencesStorage = PreferencesStorage(this)
|
||||
PicassoHelper.initPicassoWithCaching(this)
|
||||
currenciesRepository = CurrenciesRepository(this)
|
||||
currenciesRepository = CurrenciesRepository(
|
||||
this, store.state.domainNetworks.tangemTechService
|
||||
)
|
||||
walletConnectRepository = WalletConnectRepository(this)
|
||||
|
||||
initFeedbackManager()
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import com.tangem.tap.domain.configurable.config.ConfigManager
|
|||
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.BlockchainNetwork
|
||||
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
|
||||
import com.tangem.tap.features.demo.isDemoCard
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.network.NetworkConnectivity
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ 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.domain.tokens.models.BlockchainNetwork
|
||||
import com.tangem.tap.features.wallet.redux.Currency
|
||||
|
||||
fun WalletManagerFactory.makeWalletManagerForApp(
|
||||
|
|
|
|||
|
|
@ -28,9 +28,11 @@ import com.tangem.operations.issuerAndUserData.ReadIssuerDataCommand
|
|||
import com.tangem.tap.domain.TapSdkError
|
||||
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.domain.tokens.models.BlockchainNetwork
|
||||
import com.tangem.tap.preferencesStorage
|
||||
import com.tangem.tap.scope
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class ScanProductTask(
|
||||
val card: Card? = null,
|
||||
|
|
@ -192,47 +194,50 @@ private class ScanWalletProcessor(
|
|||
session: CardSession,
|
||||
callback: (result: CompletionResult<ScanResponse>) -> Unit
|
||||
) {
|
||||
val derivations = collectDerivations(card)
|
||||
if (derivations.isEmpty() || !card.settings.isHDWalletAllowed) {
|
||||
callback(
|
||||
CompletionResult.Success(
|
||||
ScanResponse(
|
||||
card = card,
|
||||
productType = ProductType.Wallet,
|
||||
walletData = session.environment.walletData,
|
||||
primaryCard = primaryCard
|
||||
scope.launch {
|
||||
val derivations = collectDerivations(card)
|
||||
if (derivations.isEmpty() || !card.settings.isHDWalletAllowed) {
|
||||
callback(
|
||||
CompletionResult.Success(
|
||||
ScanResponse(
|
||||
card = card,
|
||||
productType = ProductType.Wallet,
|
||||
walletData = session.environment.walletData,
|
||||
primaryCard = primaryCard
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
DeriveMultipleWalletPublicKeysTask(derivations).run(session) { result ->
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
val response = ScanResponse(
|
||||
card = card,
|
||||
productType = ProductType.Wallet,
|
||||
walletData = session.environment.walletData,
|
||||
derivedKeys = result.data.entries,
|
||||
primaryCard = primaryCard
|
||||
)
|
||||
callback(CompletionResult.Success(response))
|
||||
return@launch
|
||||
}
|
||||
|
||||
DeriveMultipleWalletPublicKeysTask(derivations).run(session) { result ->
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
val response = ScanResponse(
|
||||
card = card,
|
||||
productType = ProductType.Wallet,
|
||||
walletData = session.environment.walletData,
|
||||
derivedKeys = result.data.entries,
|
||||
primaryCard = primaryCard
|
||||
)
|
||||
callback(CompletionResult.Success(response))
|
||||
}
|
||||
is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error))
|
||||
}
|
||||
is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getBlockchainsToDerive(card: Card): List<BlockchainNetwork> {
|
||||
private suspend fun getBlockchainsToDerive(card: Card): List<BlockchainNetwork> {
|
||||
val currenciesRepository = currenciesRepository ?: return emptyList()
|
||||
|
||||
val cardCurrencies = currenciesRepository.loadSavedCurrencies(card.cardId, card.settings.isHDWalletAllowed).toMutableList()
|
||||
|
||||
val blockchainsToDerive = cardCurrencies.ifEmpty {
|
||||
mutableListOf(
|
||||
BlockchainNetwork(Blockchain.Bitcoin, card),
|
||||
BlockchainNetwork(Blockchain.Ethereum, card))
|
||||
BlockchainNetwork(Blockchain.Ethereum, card)
|
||||
)
|
||||
}
|
||||
|
||||
if (card.settings.isHDWalletAllowed) {
|
||||
|
|
@ -260,7 +265,7 @@ private class ScanWalletProcessor(
|
|||
return blockchainsToDerive.distinct()
|
||||
}
|
||||
|
||||
private fun collectDerivations(card: Card): Map<ByteArrayKey, List<DerivationPath>> {
|
||||
private suspend fun collectDerivations(card: Card): Map<ByteArrayKey, List<DerivationPath>> {
|
||||
val blockchains = getBlockchainsToDerive(card)
|
||||
val derivations = mutableMapOf<ByteArrayKey, List<DerivationPath>>()
|
||||
|
||||
|
|
|
|||
|
|
@ -2,26 +2,31 @@ package com.tangem.tap.domain.tokens
|
|||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import com.squareup.moshi.Json
|
||||
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.domain.common.TapWorkarounds.derivationStyle
|
||||
import com.tangem.common.services.Result
|
||||
import com.tangem.domain.common.extensions.toNetworkId
|
||||
import com.tangem.network.api.tangemTech.TangemTechService
|
||||
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.setCustomIconUrl
|
||||
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
|
||||
import com.tangem.tap.domain.tokens.models.ObsoleteTokenDao
|
||||
import com.tangem.tap.domain.tokens.models.TokenDao
|
||||
import com.tangem.tap.features.demo.DemoHelper
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import timber.log.Timber
|
||||
import java.util.*
|
||||
|
||||
class CurrenciesRepository(val context: Application) {
|
||||
class CurrenciesRepository(
|
||||
private val context: Application,
|
||||
private val tangemNetworkService: TangemTechService
|
||||
) {
|
||||
|
||||
private val moshi = MoshiConverter.defaultMoshi()
|
||||
private val blockchainsAdapter: JsonAdapter<List<Blockchain>> = moshi.adapter(
|
||||
|
|
@ -41,7 +46,7 @@ class CurrenciesRepository(val context: Application) {
|
|||
|
||||
fun saveUpdatedCurrency(cardId: String, blockchainNetwork: BlockchainNetwork) {
|
||||
var changed = false
|
||||
val currencies = loadSavedCurrencies(cardId).map {
|
||||
val currencies = loadSavedCurrenciesWithoutMigration(cardId).map {
|
||||
if (it == blockchainNetwork) {
|
||||
changed = true
|
||||
blockchainNetwork
|
||||
|
|
@ -54,7 +59,7 @@ class CurrenciesRepository(val context: Application) {
|
|||
}
|
||||
|
||||
fun removeToken(cardId: String, token: Token, blockchainNetwork: BlockchainNetwork) {
|
||||
val currencies = loadSavedCurrencies(cardId).map {
|
||||
val currencies = loadSavedCurrenciesWithoutMigration(cardId).map {
|
||||
if (it == blockchainNetwork) {
|
||||
it.copy(tokens = it.tokens.filterNot { it == token })
|
||||
} else {
|
||||
|
|
@ -65,7 +70,8 @@ class CurrenciesRepository(val context: Application) {
|
|||
}
|
||||
|
||||
fun removeBlockchain(cardId: String, blockchainNetwork: BlockchainNetwork) {
|
||||
val currencies = loadSavedCurrencies(cardId).filterNot { it == blockchainNetwork }
|
||||
val currencies = loadSavedCurrenciesWithoutMigration(cardId)
|
||||
.filterNot { it == blockchainNetwork }
|
||||
saveCurrencies(cardId, currencies)
|
||||
}
|
||||
|
||||
|
|
@ -98,12 +104,12 @@ class CurrenciesRepository(val context: Application) {
|
|||
}
|
||||
}
|
||||
|
||||
fun loadSavedCurrencies(
|
||||
suspend fun loadSavedCurrencies(
|
||||
cardId: String,
|
||||
isHdWalletSupported: Boolean = false
|
||||
): List<BlockchainNetwork> {
|
||||
if (DemoHelper.isDemoCardId(cardId)) {
|
||||
return loadDemoCurrencies(cardId)
|
||||
return loadDemoCurrencies()
|
||||
}
|
||||
return try {
|
||||
val json = context.readFileText(getFileNameForBlockchains(cardId))
|
||||
|
|
@ -113,7 +119,21 @@ class CurrenciesRepository(val context: Application) {
|
|||
}
|
||||
}
|
||||
|
||||
private fun loadDemoCurrencies(cardId: String): List<BlockchainNetwork> {
|
||||
fun loadSavedCurrenciesWithoutMigration(
|
||||
cardId: String,
|
||||
): List<BlockchainNetwork> {
|
||||
if (DemoHelper.isDemoCardId(cardId)) {
|
||||
return loadDemoCurrencies()
|
||||
}
|
||||
return try {
|
||||
val json = context.readFileText(getFileNameForBlockchains(cardId))
|
||||
blockchainNetworkAdapter.fromJson(json)?.distinct() ?: emptyList()
|
||||
} catch (exception: Exception) {
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadDemoCurrencies(): List<BlockchainNetwork> {
|
||||
return DemoHelper.config.demoBlockchains.map {
|
||||
BlockchainNetwork(
|
||||
blockchain = it,
|
||||
|
|
@ -123,7 +143,7 @@ class CurrenciesRepository(val context: Application) {
|
|||
}
|
||||
}
|
||||
|
||||
private fun tryToLoadPreviousFormatAndMigrate(
|
||||
private suspend fun tryToLoadPreviousFormatAndMigrate(
|
||||
cardId: String,
|
||||
isHdWalletSupported: Boolean = false
|
||||
): List<BlockchainNetwork> {
|
||||
|
|
@ -137,12 +157,12 @@ class CurrenciesRepository(val context: Application) {
|
|||
}
|
||||
}
|
||||
|
||||
private fun loadSavedCurrenciesOldWay(
|
||||
private suspend fun loadSavedCurrenciesOldWay(
|
||||
cardId: String, isHdWalletSupported: Boolean = false
|
||||
): List<BlockchainNetwork> {
|
||||
val blockchains = loadSavedBlockchains(cardId)
|
||||
val tokens = loadSavedTokens(cardId)
|
||||
val currencies = getSupportedTokens()
|
||||
val ids = getTokensIds(tokens)
|
||||
val derivationStyle = if (isHdWalletSupported) DerivationStyle.LEGACY else null
|
||||
val blockchainNetworks = blockchains.map { blockchain ->
|
||||
BlockchainNetwork(
|
||||
|
|
@ -152,10 +172,7 @@ class CurrenciesRepository(val context: Application) {
|
|||
.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)
|
||||
token.copy(id = ids[token.contractAddress])
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
@ -163,6 +180,21 @@ class CurrenciesRepository(val context: Application) {
|
|||
return blockchainNetworks
|
||||
}
|
||||
|
||||
private suspend fun getTokensIds(tokens: List<TokenDao>): Map<String, String> = coroutineScope {
|
||||
tokens.map {
|
||||
async {
|
||||
tangemNetworkService.getTokens(
|
||||
it.contractAddress,
|
||||
it.blockchainDao.toBlockchain().toNetworkId()
|
||||
)
|
||||
}
|
||||
}.map { it.await() }
|
||||
.map { (it as? Result.Success)?.data?.coins?.firstOrNull()?.id }
|
||||
.mapIndexedNotNull { index, s ->
|
||||
if (s == null) null else tokens[index].contractAddress to s
|
||||
}.toMap()
|
||||
}
|
||||
|
||||
fun saveCurrencies(cardId: String, currencies: List<BlockchainNetwork>) {
|
||||
val json = blockchainNetworkAdapter.toJson(currencies)
|
||||
context.rewriteFile(json, getFileNameForBlockchains(cardId))
|
||||
|
|
@ -177,9 +209,8 @@ class CurrenciesRepository(val context: Application) {
|
|||
}
|
||||
}
|
||||
|
||||
fun getSupportedTokens(isTestNet: Boolean = false): List<Currency> {
|
||||
val fileName = if (isTestNet) "testnet_tokens" else "tokens"
|
||||
val json = context.assets.readJsonFileToString(fileName)
|
||||
fun getTestnetCoins(): List<Currency> {
|
||||
val json = context.assets.readJsonFileToString(FILE_NAME_TESTNET_COINS)
|
||||
return currenciesAdapter.fromJson(json)!!.coins
|
||||
.map { Currency.fromJsonObject(it) }
|
||||
}
|
||||
|
|
@ -232,6 +263,7 @@ class CurrenciesRepository(val context: Application) {
|
|||
companion object {
|
||||
private const val FILE_NAME_PREFIX_TOKENS = "tokens"
|
||||
private const val FILE_NAME_PREFIX_BLOCKCHAINS = "blockchains"
|
||||
private const val FILE_NAME_TESTNET_COINS = "testnet_tokens"
|
||||
|
||||
fun getFileNameForTokens(cardId: String): String = "${FILE_NAME_PREFIX_TOKENS}_$cardId"
|
||||
fun getFileNameForBlockchains(cardId: String): String =
|
||||
|
|
@ -245,145 +277,3 @@ fun Blockchain.getTokensName(): String {
|
|||
else -> this.fullName
|
||||
}
|
||||
}
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class TokenDao(
|
||||
val name: String,
|
||||
val symbol: String,
|
||||
val contractAddress: String,
|
||||
val decimalCount: Int,
|
||||
@Json(name = "blockchain")
|
||||
val blockchainDao: BlockchainDao,
|
||||
val customIconUrl: String? = null,
|
||||
val type: String? = null
|
||||
) {
|
||||
fun toToken(): Token {
|
||||
return Token(
|
||||
name = name,
|
||||
symbol = symbol,
|
||||
contractAddress = contractAddress,
|
||||
decimals = decimalCount,
|
||||
).apply {
|
||||
customIconUrl?.let { this.setCustomIconUrl(it) }
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class BlockchainDao(
|
||||
@Json(name = "key")
|
||||
val name: String,
|
||||
@Json(name = "testnet")
|
||||
val isTestNet: Boolean
|
||||
) {
|
||||
fun toBlockchain(): Blockchain {
|
||||
val blockchain = Blockchain.values().find { it.name.lowercase() == name.lowercase() }
|
||||
?: throw Exception("Invalid BlockchainDao")
|
||||
return if (!isTestNet) blockchain else blockchain.getTestnetVersion()
|
||||
?: throw Exception("Invalid BlockchainDao")
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun fromBlockchain(blockchain: Blockchain): BlockchainDao {
|
||||
val name = blockchain.name.removeSuffix("Testnet").lowercase()
|
||||
return BlockchainDao(name, blockchain.isTestnet())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class ObsoleteTokenDao(
|
||||
val name: String,
|
||||
val symbol: String,
|
||||
val contractAddress: String,
|
||||
val decimalCount: Int,
|
||||
val customIconUrl: String?,
|
||||
) {
|
||||
fun toTokenDao(blockchain: Blockchain): TokenDao {
|
||||
return TokenDao(
|
||||
name = name,
|
||||
symbol = symbol,
|
||||
contractAddress = contractAddress,
|
||||
decimalCount = decimalCount,
|
||||
blockchainDao = BlockchainDao.fromBlockchain(blockchain),
|
||||
customIconUrl = customIconUrl
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
//@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()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ package com.tangem.tap.domain.tokens
|
|||
import com.squareup.moshi.JsonClass
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.domain.common.extensions.fromNetworkId
|
||||
import com.tangem.network.api.tangemTech.CoinsResponse
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class CurrencyFromJson(
|
||||
|
|
@ -44,10 +45,20 @@ data class Currency(
|
|||
id = currency.id,
|
||||
name = currency.name,
|
||||
symbol = currency.symbol,
|
||||
iconUrl = getIconUrl(currency.id),
|
||||
iconUrl = getIconUrl(currency.id, null),
|
||||
contracts = currency.networks?.toContracts() ?: emptyList()
|
||||
)
|
||||
}
|
||||
|
||||
fun fromCoinResponse(currency: CoinsResponse.Coin, imageHost: String?): Currency {
|
||||
return Currency(
|
||||
id = currency.id,
|
||||
name = currency.name,
|
||||
symbol = currency.symbol,
|
||||
iconUrl = getIconUrl(currency.id, imageHost),
|
||||
contracts = currency.networks.mapNotNull { Contract.fromNetwork(it, imageHost) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -67,12 +78,26 @@ data class Contract(
|
|||
blockchain = blockchain,
|
||||
address = contract.contractAddress,
|
||||
decimalCount = contract.decimalCount,
|
||||
iconUrl = getIconUrl(contract.networkId)
|
||||
iconUrl = getIconUrl(contract.networkId, null)
|
||||
)
|
||||
}
|
||||
|
||||
fun fromNetwork(contract: CoinsResponse.Coin.Network, imageHost: String?): Contract? {
|
||||
val blockchain = Blockchain.fromNetworkId(contract.networkId) ?: return null
|
||||
return Contract(
|
||||
networkId = contract.networkId,
|
||||
blockchain = blockchain,
|
||||
address = contract.contractAddress,
|
||||
decimalCount = contract.decimalCount?.toInt(),
|
||||
iconUrl = getIconUrl(contract.networkId, imageHost)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getIconUrl(id: String): String {
|
||||
return "https://s3.eu-central-1.amazonaws.com/tangem.api/coins/large/$id.png"
|
||||
}
|
||||
fun getIconUrl(id: String, imageHost: String? = null): String {
|
||||
return "${imageHost ?: DEFAULT_IMAGE_HOST}large/$id.png"
|
||||
}
|
||||
|
||||
private const val DEFAULT_IMAGE_HOST =
|
||||
"https://s3.eu-central-1.amazonaws.com/tangem.api/coins/"
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
package com.tangem.tap.domain.tokens
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.common.services.Result
|
||||
import com.tangem.domain.common.extensions.toNetworkId
|
||||
import com.tangem.network.api.tangemTech.CoinsResponse
|
||||
import com.tangem.network.api.tangemTech.TangemTechService
|
||||
|
||||
class LoadAvailableCoinsService(
|
||||
private val networkService: TangemTechService,
|
||||
private val currenciesRepository: CurrenciesRepository
|
||||
) {
|
||||
|
||||
suspend fun getSupportedTokens(
|
||||
isTestNet: Boolean = false,
|
||||
supportedBlockchains: List<Blockchain>,
|
||||
page: Int,
|
||||
searchInput: String? = null
|
||||
): Result<LoadedCoins> {
|
||||
if (isTestNet) {
|
||||
return Result.Success(
|
||||
LoadedCoins(
|
||||
currencies = currenciesRepository.getTestnetCoins(),
|
||||
moreAvailable = false,
|
||||
)
|
||||
)
|
||||
}
|
||||
val offset = page * LOAD_PER_PAGE
|
||||
val result = loadCoins(supportedBlockchains, offset, searchInput)
|
||||
|
||||
return when (result) {
|
||||
is Result.Success -> {
|
||||
val data = result.data
|
||||
Result.Success(
|
||||
LoadedCoins(
|
||||
currencies = data.coins.map { Currency.fromCoinResponse(it, data.imageHost) },
|
||||
moreAvailable = data.total > offset + LOAD_PER_PAGE,
|
||||
)
|
||||
)
|
||||
}
|
||||
is Result.Failure -> {
|
||||
Result.Failure(result.error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun loadCoins(
|
||||
supportedBlockchains: List<Blockchain>,
|
||||
offset: Int,
|
||||
searchInput: String? = null
|
||||
): Result<CoinsResponse> {
|
||||
val networkIds = supportedBlockchains.toSet().map { it.toNetworkId() }
|
||||
return networkService.getListOfCoins(
|
||||
networkIds = networkIds,
|
||||
offset = offset,
|
||||
limit = LOAD_PER_PAGE,
|
||||
searchText = searchInput
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val LOAD_PER_PAGE = 100
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
data class LoadedCoins(
|
||||
val currencies: List<Currency>,
|
||||
val moreAvailable: Boolean,
|
||||
)
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
package com.tangem.tap.domain.tokens.models
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class BlockchainDao(
|
||||
@Json(name = "key")
|
||||
val name: String,
|
||||
@Json(name = "testnet")
|
||||
val isTestNet: Boolean
|
||||
) {
|
||||
fun toBlockchain(): Blockchain {
|
||||
val blockchain = Blockchain.values().find { it.name.lowercase() == name.lowercase() }
|
||||
?: throw Exception("Invalid BlockchainDao")
|
||||
return if (!isTestNet) blockchain else blockchain.getTestnetVersion()
|
||||
?: throw Exception("Invalid BlockchainDao")
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun fromBlockchain(blockchain: Blockchain): BlockchainDao {
|
||||
val name = blockchain.name.removeSuffix("Testnet").lowercase()
|
||||
return BlockchainDao(name, blockchain.isTestnet())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
package com.tangem.tap.domain.tokens.models
|
||||
|
||||
import com.squareup.moshi.JsonClass
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.common.card.Card
|
||||
import com.tangem.domain.common.TapWorkarounds.derivationStyle
|
||||
|
||||
@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()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package com.tangem.tap.domain.tokens.models
|
||||
|
||||
import com.squareup.moshi.JsonClass
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class ObsoleteTokenDao(
|
||||
val name: String,
|
||||
val symbol: String,
|
||||
val contractAddress: String,
|
||||
val decimalCount: Int,
|
||||
val customIconUrl: String?,
|
||||
) {
|
||||
fun toTokenDao(blockchain: Blockchain): TokenDao {
|
||||
return TokenDao(
|
||||
name = name,
|
||||
symbol = symbol,
|
||||
contractAddress = contractAddress,
|
||||
decimalCount = decimalCount,
|
||||
blockchainDao = BlockchainDao.fromBlockchain(blockchain),
|
||||
customIconUrl = customIconUrl
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
package com.tangem.tap.domain.tokens.models
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.tap.domain.extensions.setCustomIconUrl
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class TokenDao(
|
||||
val name: String,
|
||||
val symbol: String,
|
||||
val contractAddress: String,
|
||||
val decimalCount: Int,
|
||||
@Json(name = "blockchain")
|
||||
val blockchainDao: BlockchainDao,
|
||||
val customIconUrl: String? = null,
|
||||
val type: String? = null
|
||||
) {
|
||||
fun toToken(): Token {
|
||||
return Token(
|
||||
name = name,
|
||||
symbol = symbol,
|
||||
contractAddress = contractAddress,
|
||||
decimals = decimalCount,
|
||||
).apply {
|
||||
customIconUrl?.let { this.setCustomIconUrl(it) }
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -16,15 +16,17 @@ 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.tokens.BlockchainNetwork
|
||||
import com.tangem.tap.domain.tokens.models.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.scope
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
import kotlinx.coroutines.launch
|
||||
import org.rekotlin.Action
|
||||
import org.rekotlin.Middleware
|
||||
|
||||
|
|
@ -257,14 +259,16 @@ class WalletConnectMiddleware {
|
|||
blockchainToMake,
|
||||
card.derivationStyle?.let { DerivationParams.Default(it) }
|
||||
)
|
||||
if (currenciesRepository.loadSavedCurrencies(card.cardId, card.settings.isHDWalletAllowed)
|
||||
.find { it.blockchain == blockchainToMake } != null
|
||||
) {
|
||||
walletManager?.let {
|
||||
currenciesRepository.saveUpdatedCurrency(
|
||||
card.cardId,
|
||||
BlockchainNetwork.fromWalletManager(walletManager)
|
||||
)
|
||||
scope.launch {
|
||||
if (currenciesRepository.loadSavedCurrencies(card.cardId, card.settings.isHDWalletAllowed)
|
||||
.find { it.blockchain == blockchainToMake } != null
|
||||
) {
|
||||
walletManager?.let {
|
||||
currenciesRepository.saveUpdatedCurrency(
|
||||
card.cardId,
|
||||
BlockchainNetwork.fromWalletManager(walletManager)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
return walletManager
|
||||
|
|
|
|||
|
|
@ -24,7 +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.domain.tokens.models.BlockchainNetwork
|
||||
import com.tangem.tap.features.demo.DemoTransactionSender
|
||||
import com.tangem.tap.features.demo.isDemoWallet
|
||||
import com.tangem.tap.features.send.redux.*
|
||||
|
|
|
|||
|
|
@ -17,7 +17,8 @@ sealed class TokensAction : Action {
|
|||
val supportedBlockchains: List<Blockchain>? = null,
|
||||
val scanResponse: ScanResponse? = null
|
||||
) : TokensAction() {
|
||||
data class Success(val currencies: List<Currency>) : TokensAction()
|
||||
data class Success(val currencies: List<Currency>, val loadMore: Boolean) : TokensAction()
|
||||
object Failure : TokensAction()
|
||||
}
|
||||
|
||||
data class SetAddedCurrencies(
|
||||
|
|
@ -32,4 +33,6 @@ sealed class TokensAction : Action {
|
|||
) : TokensAction()
|
||||
|
||||
object PrepareAndNavigateToAddCustomToken : TokensAction()
|
||||
|
||||
data class SetSearchInput(val searchInput: String) : TokensAction()
|
||||
}
|
||||
|
|
@ -8,11 +8,13 @@ import com.tangem.common.card.EllipticCurve
|
|||
import com.tangem.common.extensions.ByteArrayKey
|
||||
import com.tangem.common.extensions.toMapKey
|
||||
import com.tangem.common.hdWallet.DerivationPath
|
||||
import com.tangem.common.services.Result
|
||||
import com.tangem.domain.DomainWrapped
|
||||
import com.tangem.domain.common.KeyWalletPublicKey
|
||||
import com.tangem.domain.common.ScanResponse
|
||||
import com.tangem.domain.common.TapWorkarounds.derivationStyle
|
||||
import com.tangem.domain.common.TapWorkarounds.isTestCard
|
||||
import com.tangem.domain.common.extensions.supportedBlockchains
|
||||
import com.tangem.domain.features.addCustomToken.CustomCurrency
|
||||
import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction
|
||||
import com.tangem.domain.redux.domainStore
|
||||
|
|
@ -26,10 +28,10 @@ import com.tangem.tap.common.redux.navigation.AppScreen
|
|||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.domain.extensions.makeWalletManagerForApp
|
||||
import com.tangem.tap.domain.tokens.BlockchainNetwork
|
||||
import com.tangem.tap.domain.tokens.LoadAvailableCoinsService
|
||||
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
|
||||
import com.tangem.tap.features.wallet.redux.Currency
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import org.rekotlin.Middleware
|
||||
|
|
@ -40,26 +42,65 @@ class TokensMiddleware {
|
|||
{ next ->
|
||||
{ action ->
|
||||
when (action) {
|
||||
is TokensAction.LoadCurrencies -> handleLoadCurrencies(action)
|
||||
is TokensAction.LoadCurrencies -> handleLoadCurrencies(action.scanResponse)
|
||||
is TokensAction.SaveChanges -> handleSaveChanges(action)
|
||||
is TokensAction.PrepareAndNavigateToAddCustomToken -> handleAddingCustomToken(action)
|
||||
is TokensAction.PrepareAndNavigateToAddCustomToken -> handleAddingCustomToken(
|
||||
action
|
||||
)
|
||||
is TokensAction.SetSearchInput -> {
|
||||
handleLoadCurrencies(
|
||||
scanResponse = store.state.globalState.scanResponse,
|
||||
action.searchInput
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
next(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleLoadCurrencies(action: TokensAction.LoadCurrencies) {
|
||||
val scanResponse = store.state.globalState.scanResponse
|
||||
private fun handleLoadCurrencies(scanResponse: ScanResponse?, newSearchInput: String? = null) {
|
||||
val tokensState = store.state.tokensState
|
||||
|
||||
val isTestcard = scanResponse?.card?.isTestCard ?: false
|
||||
|
||||
val supportedBlockchains: List<Blockchain> =
|
||||
scanResponse?.card?.supportedBlockchains() ?: Blockchain.values().toList()
|
||||
.filter { !it.isTestnet() }
|
||||
|
||||
val loadCoinsService = LoadAvailableCoinsService(
|
||||
store.state.domainNetworks.tangemTechService,
|
||||
currenciesRepository
|
||||
)
|
||||
|
||||
scope.launch {
|
||||
val currencies = async {
|
||||
currenciesRepository.getSupportedTokens(isTestcard)
|
||||
.filter(action.supportedBlockchains?.toSet())
|
||||
|
||||
val loadCoinsResult = if (newSearchInput == null) {
|
||||
loadCoinsService.getSupportedTokens(
|
||||
isTestcard,
|
||||
supportedBlockchains,
|
||||
tokensState.pageToLoad,
|
||||
tokensState.searchInput
|
||||
)
|
||||
} else {
|
||||
loadCoinsService.getSupportedTokens(
|
||||
isTestcard, supportedBlockchains, 0, newSearchInput.ifBlank { null }
|
||||
)
|
||||
}
|
||||
delay(600)
|
||||
store.dispatchOnMain(TokensAction.LoadCurrencies.Success(currencies.await()))
|
||||
when (loadCoinsResult) {
|
||||
is Result.Success -> {
|
||||
val currencies = loadCoinsResult.data.currencies
|
||||
.filter(supportedBlockchains.toSet())
|
||||
store.dispatchOnMain(
|
||||
TokensAction.LoadCurrencies.Success(
|
||||
currencies, loadCoinsResult.data.moreAvailable
|
||||
)
|
||||
)
|
||||
}
|
||||
is Result.Failure -> store.dispatchOnMain(TokensAction.LoadCurrencies.Failure)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -74,7 +115,8 @@ class TokensMiddleware {
|
|||
)
|
||||
|
||||
val blockchainsToAdd = action.addedBlockchains.filter { !currentBlockchains.contains(it) }
|
||||
val blockchainsToRemove = currentBlockchains.filter { !action.addedBlockchains.contains(it) }
|
||||
val blockchainsToRemove =
|
||||
currentBlockchains.filter { !action.addedBlockchains.contains(it) }
|
||||
|
||||
val tokensToAdd = action.addedTokens.filter { !currentTokens.contains(it) }
|
||||
val tokensToRemove = currentTokens.filter { token ->
|
||||
|
|
@ -82,11 +124,13 @@ class TokensMiddleware {
|
|||
}
|
||||
val derivationStyle = scanResponse.card.derivationStyle
|
||||
|
||||
removeCurrenciesIfNeeded(convertToCurrencies(
|
||||
blockchains = blockchainsToRemove,
|
||||
tokens = tokensToRemove,
|
||||
derivationStyle = derivationStyle
|
||||
))
|
||||
removeCurrenciesIfNeeded(
|
||||
convertToCurrencies(
|
||||
blockchains = blockchainsToRemove,
|
||||
tokens = tokensToRemove,
|
||||
derivationStyle = derivationStyle
|
||||
)
|
||||
)
|
||||
|
||||
if (tokensToAdd.isEmpty() && blockchainsToAdd.isEmpty()) {
|
||||
store.dispatchDebugErrorNotification("Nothing to save")
|
||||
|
|
@ -246,7 +290,8 @@ class TokensMiddleware {
|
|||
val rawDerivationPath = currency.derivationPath
|
||||
?: currency.blockchain.derivationPath(derivationStyle)?.rawPath
|
||||
|
||||
val blockchainNetwork = BlockchainNetwork(currency.blockchain, rawDerivationPath, emptyList())
|
||||
val blockchainNetwork =
|
||||
BlockchainNetwork(currency.blockchain, rawDerivationPath, emptyList())
|
||||
WalletAction.MultiWallet.AddToken(currency.token, blockchainNetwork)
|
||||
}
|
||||
}
|
||||
|
|
@ -297,8 +342,15 @@ class TokensMiddleware {
|
|||
walletStore.walletsData.map { walletData -> walletData.currency }
|
||||
}.flatten().map {
|
||||
when (it) {
|
||||
is Currency.Blockchain -> DomainWrapped.Currency.Blockchain(it.blockchain, it.derivationPath)
|
||||
is Currency.Token -> DomainWrapped.Currency.Token(it.token, it.blockchain, it.derivationPath)
|
||||
is Currency.Blockchain -> DomainWrapped.Currency.Blockchain(
|
||||
it.blockchain,
|
||||
it.derivationPath
|
||||
)
|
||||
is Currency.Token -> DomainWrapped.Currency.Token(
|
||||
it.token,
|
||||
it.blockchain,
|
||||
it.derivationPath
|
||||
)
|
||||
}
|
||||
}
|
||||
domainStore.dispatch(AddCustomTokenAction.Init.SetAddedCurrencies(addedCurrencies))
|
||||
|
|
|
|||
|
|
@ -15,9 +15,24 @@ private fun internalReduce(action: Action, state: AppState): TokensState {
|
|||
val tokensState = state.tokensState
|
||||
return when (action) {
|
||||
is TokensAction.ResetState -> TokensState()
|
||||
is TokensAction.LoadCurrencies -> tokensState.copy(scanResponse = action.scanResponse)
|
||||
is TokensAction.LoadCurrencies -> {
|
||||
val loadingState = if (tokensState.currencies.isEmpty()) {
|
||||
LoadCoinsState.LOADING
|
||||
} else {
|
||||
LoadCoinsState.LOADED
|
||||
}
|
||||
tokensState.copy(
|
||||
scanResponse = action.scanResponse,
|
||||
loadCoinsState = loadingState
|
||||
)
|
||||
}
|
||||
is TokensAction.LoadCurrencies.Success -> {
|
||||
tokensState.copy(currencies = action.currencies)
|
||||
tokensState.copy(
|
||||
currencies = tokensState.currencies + action.currencies,
|
||||
needToLoadMore = action.loadMore,
|
||||
pageToLoad = tokensState.pageToLoad + 1,
|
||||
loadCoinsState = LoadCoinsState.LOADED
|
||||
)
|
||||
}
|
||||
is TokensAction.SetAddedCurrencies -> {
|
||||
|
||||
|
|
@ -38,6 +53,15 @@ private fun internalReduce(action: Action, state: AppState): TokensState {
|
|||
is TokensAction.AllowToAddTokens -> {
|
||||
tokensState.copy(allowToAdd = action.allow)
|
||||
}
|
||||
is TokensAction.SetSearchInput -> {
|
||||
tokensState.copy(
|
||||
searchInput = action.searchInput.ifBlank { null },
|
||||
needToLoadMore = true,
|
||||
currencies = emptyList(),
|
||||
pageToLoad = 0,
|
||||
loadCoinsState = LoadCoinsState.LOADING
|
||||
)
|
||||
}
|
||||
else -> tokensState
|
||||
}
|
||||
}
|
||||
|
|
@ -17,9 +17,13 @@ data class TokensState(
|
|||
val nonRemovableTokens: List<ContractAddress> = emptyList(),
|
||||
val nonRemovableBlockchains: List<Blockchain> = emptyList(),
|
||||
val currencies: List<Currency> = emptyList(),
|
||||
val searchInput: String? = null,
|
||||
val allowToAdd: Boolean = true,
|
||||
val derivationStyle: DerivationStyle? = null,
|
||||
val scanResponse: ScanResponse? = null
|
||||
val scanResponse: ScanResponse? = null,
|
||||
val needToLoadMore: Boolean = true,
|
||||
val pageToLoad: Int = 0,
|
||||
val loadCoinsState: LoadCoinsState = LoadCoinsState.LOADING,
|
||||
) : StateType {
|
||||
|
||||
fun canHandleToken(token: TokenWithBlockchain): Boolean {
|
||||
|
|
@ -74,4 +78,8 @@ fun List<Currency>.filter(supportedBlockchains: Set<Blockchain>?): List<Currency
|
|||
}.filterNot {
|
||||
it.contracts.isNullOrEmpty() && !supportedBlockchains.contains(Blockchain.fromNetworkId(it.id))
|
||||
}
|
||||
}
|
||||
|
||||
enum class LoadCoinsState {
|
||||
LOADING, LOADED, ERROR
|
||||
}
|
||||
|
|
@ -17,6 +17,7 @@ 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.dispatchOnMain
|
||||
import com.tangem.tap.common.extensions.getString
|
||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
import com.tangem.tap.features.tokens.redux.ContractAddress
|
||||
|
|
@ -46,6 +47,7 @@ class AddTokensFragment : Fragment(R.layout.fragment_add_tokens),
|
|||
activity?.onBackPressedDispatcher?.addCallback(this, object : OnBackPressedCallback(true) {
|
||||
override fun handleOnBackPressed() {
|
||||
store.dispatch(NavigationAction.PopBackTo())
|
||||
store.dispatch(TokensAction.ResetState)
|
||||
}
|
||||
})
|
||||
val inflater = TransitionInflater.from(requireContext())
|
||||
|
|
@ -81,13 +83,17 @@ class AddTokensFragment : Fragment(R.layout.fragment_add_tokens),
|
|||
store.dispatchNotification(R.string.contract_address_copied_message)
|
||||
}
|
||||
|
||||
val onLoadMore = {
|
||||
store.dispatch(TokensAction.LoadCurrencies(scanResponse = store.state.globalState.scanResponse))
|
||||
}
|
||||
|
||||
cvCurrencies.setContent {
|
||||
AppCompatTheme {
|
||||
CurrenciesScreen(
|
||||
tokensState = tokensState,
|
||||
searchInput = searchInput,
|
||||
onSaveChanges = onSaveChanges,
|
||||
onNetworkItemClicked = onNetworkItemClicked
|
||||
onNetworkItemClicked = onNetworkItemClicked,
|
||||
onLoadMore = onLoadMore
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -123,10 +129,11 @@ 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)
|
||||
.debounce(800)
|
||||
.distinctUntilChanged()
|
||||
.onEach {
|
||||
searchInput.value = it.lowercase()
|
||||
dispatchOnMain(TokensAction.SetSearchInput(searchInput.value))
|
||||
}
|
||||
.launchIn(mainScope)
|
||||
return super.onCreateOptionsMenu(menu, inflater)
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import com.tangem.tap.common.extensions.pixelsToDp
|
|||
import com.tangem.tap.common.redux.AppDialog
|
||||
import com.tangem.tap.domain.tokens.Currency
|
||||
import com.tangem.tap.features.tokens.redux.ContractAddress
|
||||
import com.tangem.tap.features.tokens.redux.LoadCoinsState
|
||||
import com.tangem.tap.features.tokens.redux.TokenWithBlockchain
|
||||
import com.tangem.tap.features.tokens.redux.TokensState
|
||||
import com.tangem.tap.store
|
||||
|
|
@ -35,9 +36,9 @@ 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
|
||||
onNetworkItemClicked: (ContractAddress) -> Unit,
|
||||
onLoadMore: () -> Unit
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val addedTokensState = remember { mutableStateOf(tokensState.value.addedTokens) }
|
||||
|
|
@ -76,7 +77,7 @@ fun CurrenciesScreen(
|
|||
) {
|
||||
|
||||
AnimatedVisibility(
|
||||
visible = tokensState.value.currencies.isEmpty(),
|
||||
visible = tokensState.value.loadCoinsState == LoadCoinsState.LOADING,
|
||||
enter = fadeIn(),
|
||||
exit = fadeOut()
|
||||
) {
|
||||
|
|
@ -90,7 +91,7 @@ fun CurrenciesScreen(
|
|||
}
|
||||
}
|
||||
AnimatedVisibility(
|
||||
visible = tokensState.value.currencies.isNotEmpty(),
|
||||
visible = tokensState.value.loadCoinsState == LoadCoinsState.LOADED,
|
||||
enter = fadeIn(animationSpec = tween(1000)),
|
||||
exit = fadeOut(animationSpec = tween(1000))
|
||||
) {
|
||||
|
|
@ -103,7 +104,6 @@ fun CurrenciesScreen(
|
|||
nonRemovableBlockchains = tokensState.value.nonRemovableBlockchains,
|
||||
addedTokens = addedTokensState.value,
|
||||
addedBlockchains = addedBlockchainsState.value,
|
||||
searchInput = searchInput.value,
|
||||
allowToAdd = tokensState.value.allowToAdd,
|
||||
onAddCurrencyToggled = { currency, token ->
|
||||
onAddCurrencyToggleClick(currency, token)
|
||||
|
|
@ -118,7 +118,8 @@ fun CurrenciesScreen(
|
|||
}
|
||||
|
||||
},
|
||||
onNetworkItemClicked = onNetworkItemClicked
|
||||
onNetworkItemClicked = onNetworkItemClicked,
|
||||
onLoadMore = onLoadMore
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,8 +3,9 @@ package com.tangem.tap.features.tokens.ui.compose
|
|||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.SideEffect
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
|
|
@ -23,10 +24,10 @@ fun ListOfCurrencies(
|
|||
nonRemovableBlockchains: List<Blockchain>,
|
||||
addedTokens: List<TokenWithBlockchain>,
|
||||
addedBlockchains: List<Blockchain>,
|
||||
searchInput: String,
|
||||
allowToAdd: Boolean,
|
||||
onAddCurrencyToggled: (Currency, TokenWithBlockchain?) -> Unit,
|
||||
onNetworkItemClicked: (ContractAddress) -> Unit
|
||||
onNetworkItemClicked: (ContractAddress) -> Unit,
|
||||
onLoadMore: () -> Unit
|
||||
) {
|
||||
|
||||
val expandedCurrencies = remember { mutableStateOf(listOf("")) }
|
||||
|
|
@ -46,31 +47,28 @@ fun ListOfCurrencies(
|
|||
.fillMaxSize(),
|
||||
contentPadding = PaddingValues(bottom = 90.dp)
|
||||
) {
|
||||
|
||||
val filteredCurrencies = if (searchInput.isBlank()) {
|
||||
currencies
|
||||
} else {
|
||||
currencies.asSequence().filter {
|
||||
it.name.lowercase().contains(searchInput) || it.symbol.lowercase()
|
||||
.contains(searchInput)
|
||||
}.toList()
|
||||
}
|
||||
item { header() }
|
||||
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
|
||||
)
|
||||
}
|
||||
itemsIndexed(currencies) { index, currency ->
|
||||
val lastIndex = currencies.lastIndex
|
||||
if (currencies.isNotEmpty()) {
|
||||
if (index + 40 == lastIndex) {
|
||||
SideEffect { onLoadMore() }
|
||||
}
|
||||
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
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,7 @@
|
|||
package com.tangem.tap.features.wallet.redux
|
||||
|
||||
import android.content.Context
|
||||
import com.tangem.blockchain.common.Amount
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.blockchain.common.Wallet
|
||||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.blockchain.common.*
|
||||
import com.tangem.blockchain.common.address.AddressType
|
||||
import com.tangem.common.card.Card
|
||||
import com.tangem.tap.common.entities.FiatCurrency
|
||||
|
|
@ -13,10 +9,10 @@ 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.BlockchainNetwork
|
||||
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
|
||||
import com.tangem.wallet.R
|
||||
import java.math.BigDecimal
|
||||
import org.rekotlin.Action
|
||||
import java.math.BigDecimal
|
||||
|
||||
sealed class WalletAction : Action {
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,7 @@
|
|||
package com.tangem.tap.features.wallet.redux
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import com.tangem.blockchain.common.Amount
|
||||
import com.tangem.blockchain.common.AmountType
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.DerivationStyle
|
||||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.blockchain.common.Wallet
|
||||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.blockchain.common.*
|
||||
import com.tangem.blockchain.common.address.AddressType
|
||||
import com.tangem.blockchain.extensions.isAboveZero
|
||||
import com.tangem.common.extensions.isZero
|
||||
|
|
@ -23,21 +17,16 @@ 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.domain.tokens.models.BlockchainNetwork
|
||||
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsState
|
||||
import com.tangem.tap.features.tokens.redux.TokenWithBlockchain
|
||||
import com.tangem.tap.features.wallet.models.PendingTransaction
|
||||
import com.tangem.tap.features.wallet.models.TotalBalance
|
||||
import com.tangem.tap.features.wallet.models.WalletRent
|
||||
import com.tangem.tap.features.wallet.models.WalletWarning
|
||||
import com.tangem.tap.features.wallet.models.toPendingTransactions
|
||||
import com.tangem.tap.features.wallet.models.toPendingTransactionsForToken
|
||||
import com.tangem.tap.features.wallet.models.*
|
||||
import com.tangem.tap.features.wallet.ui.BalanceStatus
|
||||
import com.tangem.tap.features.wallet.ui.BalanceWidgetData
|
||||
import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager
|
||||
import com.tangem.tap.store
|
||||
import java.math.BigDecimal
|
||||
import org.rekotlin.StateType
|
||||
import java.math.BigDecimal
|
||||
import kotlin.properties.ReadOnlyProperty
|
||||
|
||||
data class WalletState(
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ 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.extensions.makeWalletManagerForApp
|
||||
import com.tangem.tap.domain.tokens.BlockchainNetwork
|
||||
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
|
||||
import com.tangem.tap.features.demo.DemoHelper
|
||||
import com.tangem.tap.features.demo.isDemoCard
|
||||
import com.tangem.tap.features.wallet.redux.Currency
|
||||
|
|
|
|||
|
|
@ -7,7 +7,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.domain.tokens.models.BlockchainNetwork
|
||||
import com.tangem.tap.features.wallet.models.removeUnknownTransactions
|
||||
import com.tangem.tap.features.wallet.models.toPendingTransactions
|
||||
import com.tangem.tap.features.wallet.redux.*
|
||||
|
|
|
|||
|
|
@ -8,7 +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.domain.tokens.models.BlockchainNetwork
|
||||
import com.tangem.tap.features.wallet.models.removeUnknownTransactions
|
||||
import com.tangem.tap.features.wallet.models.toPendingTransactions
|
||||
import com.tangem.tap.features.wallet.redux.*
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import com.tangem.tap.common.redux.AppState
|
|||
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.domain.tokens.models.BlockchainNetwork
|
||||
import com.tangem.tap.features.wallet.models.WalletRent
|
||||
import com.tangem.tap.features.wallet.redux.*
|
||||
import com.tangem.tap.features.wallet.ui.BalanceStatus
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ import com.tangem.tap.common.extensions.*
|
|||
import com.tangem.tap.common.recyclerView.SpaceItemDecoration
|
||||
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.domain.tokens.models.BlockchainNetwork
|
||||
import com.tangem.tap.features.onboarding.getQRReceiveMessage
|
||||
import com.tangem.tap.features.wallet.models.PendingTransaction
|
||||
import com.tangem.tap.features.wallet.redux.*
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import com.tangem.blockchain.common.Amount
|
|||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.tap.common.TestAction
|
||||
import com.tangem.tap.common.TestActions
|
||||
import com.tangem.tap.domain.tokens.BlockchainNetwork
|
||||
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.store
|
||||
import java.math.BigDecimal
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ class AddCustomTokenService(
|
|||
active: Boolean? = null,
|
||||
): Result<List<CoinsResponse.Coin>> {
|
||||
val networksIds = selectNetworksForSearch(networkId)
|
||||
val result = tangemTechService.coins(contractAddress, networksIds, active)
|
||||
val result = tangemTechService.getTokens(contractAddress, networksIds, active)
|
||||
return when (result) {
|
||||
is Result.Success -> {
|
||||
var coinsList = mutableListOf<CoinsResponse.Coin>()
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ interface HttpResponse
|
|||
sealed interface TangemTechResponse : HttpResponse
|
||||
|
||||
data class CoinsResponse(
|
||||
val imageHost: String,
|
||||
val imageHost: String?,
|
||||
val coins: List<Coin>,
|
||||
val total: Int
|
||||
) : TangemTechResponse {
|
||||
|
|
|
|||
|
|
@ -11,8 +11,11 @@ interface TangemTechApi {
|
|||
@GET("coins")
|
||||
suspend fun coins(
|
||||
@Query("contractAddress") contractAddress: String? = null,
|
||||
@Query("networkIds") networkId: String? = null,
|
||||
@Query("networkIds") networkIds: String? = null,
|
||||
@Query("active") active: Boolean? = null,
|
||||
@Query("searchText") searchText: String? = null,
|
||||
@Query("offset") offset: Int? = null,
|
||||
@Query("limit") limit: Int? = null
|
||||
): CoinsResponse
|
||||
|
||||
@GET("rates")
|
||||
|
|
|
|||
|
|
@ -15,15 +15,37 @@ class TangemTechService {
|
|||
private val headerInterceptors = mutableListOf<AddHeaderInterceptor>(
|
||||
CacheControlHttpInterceptor(cacheMaxAge)
|
||||
)
|
||||
|
||||
|
||||
private var api: TangemTechApi = createApi()
|
||||
|
||||
suspend fun coins(
|
||||
contractAddress: String? = null,
|
||||
suspend fun getTokens(
|
||||
contractAddress: String,
|
||||
networkId: String? = null,
|
||||
active: Boolean? = null,
|
||||
): Result<CoinsResponse> = withContext(Dispatchers.IO) {
|
||||
performRequest { api.coins(contractAddress, networkId, active) }
|
||||
performRequest {
|
||||
api.coins(
|
||||
contractAddress = contractAddress,
|
||||
networkIds = networkId,
|
||||
active = active
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getListOfCoins(
|
||||
networkIds: List<String>,
|
||||
searchText: String? = null,
|
||||
offset: Int? = null,
|
||||
limit: Int? = null
|
||||
): Result<CoinsResponse> = withContext(Dispatchers.IO) {
|
||||
performRequest {
|
||||
api.coins(
|
||||
networkIds = networkIds.joinToString(","),
|
||||
searchText = searchText,
|
||||
offset = offset,
|
||||
limit = limit
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun rates(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue