Updated on 2026-08-14

This commit is contained in:
Tangem 2022-04-26 09:15:22 +03:00
parent 4ef4d42a9e
commit 17eac957b1
5 changed files with 132 additions and 113 deletions

View file

@ -1,64 +0,0 @@
package com.tangem.tap.common
/**
[REDACTED_AUTHOR]
*/
open class Throttler<T>(
private val duration: Long,
) : Throttle<T> {
private val items: MutableMap<T, Long> = mutableMapOf()
override fun isStillThrottled(item: T): Boolean {
val inThrottlingUpTo = items[item] ?: return false
val diff = System.currentTimeMillis() - inThrottlingUpTo
return diff < 0
}
override fun updateThrottlingTo(item: T): T {
val now = System.currentTimeMillis()
val throttledUpTo = items[item] ?: 0L
if (throttledUpTo == 0L || throttledUpTo < now) {
val newTime = now + duration
items[item] = newTime
}
return item
}
open fun clear() {
items.clear()
}
}
class ThrottlerWithValues<T, V>(
duration: Long
) : Throttler<T>(duration), ValuesHolder<T, V> {
private val valuesHolder: MutableMap<T, V?> = mutableMapOf()
override fun setValue(item: T, value: V) {
valuesHolder[item] = value
}
override fun geValue(item: T): V? = valuesHolder[item]
override fun remove(item: T) {
valuesHolder.remove(item)
}
override fun clear() {
valuesHolder.clear()
super.clear()
}
}
interface Throttle<T> {
fun isStillThrottled(item: T): Boolean
fun updateThrottlingTo(item: T): T
}
interface ValuesHolder<K, V> {
fun setValue(item: K, value: V)
fun geValue(item: K): V?
fun remove(item: K)
}

View file

@ -0,0 +1,83 @@
package com.tangem.tap.domain
import com.tangem.common.services.Result
import com.tangem.domain.common.ThrottlerWithValues
import com.tangem.network.api.tangemTech.TangemTechService
import com.tangem.tap.features.wallet.redux.Currency
import com.tangem.tap.store
import java.math.BigDecimal
//TODO: refactoring: move to domain
class RatesRepository {
private val tangemTechService: TangemTechService
get() = store.state.domainNetworks.tangemTechService
private val throttler = ThrottlerWithValues<Currency, Result<BigDecimal>?>(60000)
suspend fun loadFiatRate(currencyId: String, coinsList: List<Currency>): Result<RatesResult> {
// get and submit previous result of equivalents.
val throttledResult = coinsList.filter { throttler.isStillThrottled(it) }.map {
Pair(it, throttler.geValue(it))
}
if (throttledResult.isNotEmpty()) {
return handleFiatRatesResult(throttledResult.toMap())
}
val currenciesToUpdate = coinsList.filter { !throttler.isStillThrottled(it) }
val coinIds = currenciesToUpdate.mapNotNull { it.coinId }.distinct()
if (coinIds.isEmpty()) return EMPTY_RESULT
return when (val result = tangemTechService.rates(currencyId, coinIds)) {
is Result.Success -> {
val ratesResultList: Map<String, Result<BigDecimal>> = result.data.rates.mapValues {
Result.Success(it.value.toBigDecimal())
}
val updatedCurrencies = mutableMapOf<Currency, Result<BigDecimal>?>()
currenciesToUpdate.forEach { currency ->
ratesResultList[currency.coinId]?.let {
updatedCurrencies[currency] = it
throttler.updateThrottlingTo(currency)
throttler.setValue(currency, it)
}
}
handleFiatRatesResult(updatedCurrencies)
}
is Result.Failure -> Result.Failure(result.error)
}
}
private fun handleFiatRatesResult(rates: Map<Currency, Result<BigDecimal>?>): Result.Success<RatesResult> {
val success = mutableMapOf<Currency, BigDecimal>()
val failures = mutableMapOf<Currency, Throwable>()
rates.mapNotNull { (currency, priceResult) ->
when (priceResult) {
is Result.Success -> success[currency] = priceResult.data
is Result.Failure -> failures[currency] = priceResult.error
else -> null
}
}
return Result.Success(success to failures)
}
fun clear() {
throttler.clear()
}
companion object {
val EMPTY_RESULT = Result.Success(Pair(
mutableMapOf<Currency, BigDecimal>(),
mutableMapOf<Currency, Throwable>()
))
}
}
typealias RatesResult = Pair<MutableMap<Currency, BigDecimal>, MutableMap<Currency, Throwable>>
val RatesResult.loadedRates
get() = this.first
val RatesResult.failedRates
get() = this.second

View file

@ -6,12 +6,10 @@ import com.tangem.domain.common.ScanResponse
import com.tangem.domain.common.TapWorkarounds.derivationStyle
import com.tangem.domain.common.TapWorkarounds.isStart2Coin
import com.tangem.domain.common.TapWorkarounds.isTestCard
import com.tangem.domain.common.ThrottlerWithValues
import com.tangem.domain.common.extensions.withMainContext
import com.tangem.network.api.tangemTech.TangemTechService
import com.tangem.tap.common.ThrottlerWithValues
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.extensions.safeUpdate
import com.tangem.tap.common.redux.global.FiatCurrencyName
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.currenciesRepository
import com.tangem.tap.domain.configurable.config.ConfigManager
@ -20,28 +18,24 @@ 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.features.demo.isDemoCard
import com.tangem.tap.features.wallet.redux.Currency
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.network.NetworkConnectivity
import com.tangem.tap.store
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.math.BigDecimal
class TapWalletManager {
val walletManagerFactory: WalletManagerFactory
by lazy { WalletManagerFactory(blockchainSdkConfig) }
private val tangemTechService: TangemTechService
get() = store.state.domainNetworks.tangemTechService
val rates: RatesRepository = RatesRepository()
private val blockchainSdkConfig by lazy {
store.state.globalState.configManager?.config?.blockchainSdkConfig ?: BlockchainSdkConfig()
}
private val walletManagersThrottler = ThrottlerWithValues<Blockchain, Result<Wallet>>(10000)
private val fiatRatesThrottler = ThrottlerWithValues<Currency, Result<BigDecimal>?>(60000)
suspend fun loadWalletData(walletManager: WalletManager) {
val blockchain = walletManager.wallet.blockchain
@ -49,7 +43,10 @@ class TapWalletManager {
val result = if (walletManagersThrottler.isStillThrottled(blockchain)) {
walletManagersThrottler.geValue(blockchain)!!
} else {
updateThrottlingForWalletManager(walletManager)
val safeUpdateResult = walletManager.safeUpdate()
walletManagersThrottler.updateThrottlingTo(blockchain)
walletManagersThrottler.setValue(blockchain, safeUpdateResult)
safeUpdateResult
}
when (result) {
is Result.Success -> {
@ -58,92 +55,25 @@ class TapWalletManager {
is Result.Failure -> {
when (result.error) {
is TapError.WalletManager.NoAccountError -> {
dispatchOnMain(
WalletAction.LoadWallet.NoAccount(
walletManager.wallet,
blockchainNetwork,
(result.error as TapError.WalletManager.NoAccountError).customMessage
)
)
}
else -> {
dispatchOnMain(
WalletAction.LoadWallet.Failure(
walletManager.wallet,
result.error.localizedMessage
)
)
}
}
}
}
}
private suspend fun updateThrottlingForWalletManager(walletManager: WalletManager): Result<Wallet> {
val newResult = walletManager.safeUpdate()
val blockchain = walletManager.wallet.blockchain
walletManagersThrottler.updateThrottlingTo(blockchain)
walletManagersThrottler.setValue(blockchain, newResult)
return newResult
}
suspend fun loadFiatRate(currencyId: FiatCurrencyName, wallet: Wallet) {
val coinsList = wallet.getTokens()
.map { Currency.Token(it, wallet.blockchain, wallet.publicKey.derivationPath?.rawPath) }
.plus(Currency.Blockchain(wallet.blockchain, wallet.publicKey.derivationPath?.rawPath))
loadFiatRate(currencyId, coinsList)
}
suspend fun loadFiatRate(currencyId: FiatCurrencyName, coinsList: List<Currency>) {
suspend fun handleFiatRatesResult(rates: Map<Currency, Result<BigDecimal>?>) {
rates.forEach { (currency, priceResult) ->
when (priceResult) {
is Result.Success -> {
dispatchOnMain(WalletAction.LoadFiatRate.Success(
currency to priceResult.data
dispatchOnMain(WalletAction.LoadWallet.NoAccount(
walletManager.wallet,
blockchainNetwork,
(result.error as TapError.WalletManager.NoAccountError).customMessage
))
}
is Result.Failure -> dispatchOnMain(WalletAction.LoadFiatRate.Failure)
null -> {}
}
}
}
// get and submit previous result of equivalents.
val throttledResult = coinsList.filter { fiatRatesThrottler.isStillThrottled(it) }.map {
Pair(it, fiatRatesThrottler.geValue(it))
}
if (throttledResult.isNotEmpty()) {
handleFiatRatesResult(throttledResult.toMap())
}
val currenciesToUpdate = coinsList.filter { !fiatRatesThrottler.isStillThrottled(it) }
val coinIds = currenciesToUpdate.mapNotNull { it.coinId }.distinct()
if (coinIds.isEmpty()) return
//TODO: refactoring: move fiatRatesThrottler to the TangemTechRepository
when (val result = tangemTechService.rates(currencyId, coinIds)) {
is Result.Success -> {
val ratesResultList: Map<String, Result<BigDecimal>> = result.data.rates.mapValues {
Result.Success(it.value.toBigDecimal())
}
val updatedCurrencies = mutableMapOf<Currency, Result<BigDecimal>?>()
currenciesToUpdate.forEach { currency ->
ratesResultList[currency.coinId]?.let {
updatedCurrencies[currency] = it
fiatRatesThrottler.updateThrottlingTo(currency)
fiatRatesThrottler.setValue(currency, it)
else -> {
dispatchOnMain(WalletAction.LoadWallet.Failure(
walletManager.wallet,
result.error.localizedMessage
))
}
}
handleFiatRatesResult(updatedCurrencies)
}
is Result.Failure -> dispatchOnMain(WalletAction.LoadFiatRate.Failure)
}
}
suspend fun onCardScanned(data: ScanResponse) {
walletManagersThrottler.clear()
// fiatRatesThrottler.clear()
store.state.globalState.feedbackManager?.infoHolder?.setCardInfo(data)
updateConfigManager(data)
@ -194,22 +124,18 @@ class TapWalletManager {
if (data.card.isMultiwalletAllowed) {
loadMultiWalletData(data, blockchain, primaryWalletManager)
} else {
dispatchOnMain(
WalletAction.MultiWallet.AddBlockchains(
listOf(BlockchainNetwork.fromWalletManager(primaryWalletManager)),
listOf(primaryWalletManager)
)
)
dispatchOnMain(WalletAction.MultiWallet.AddBlockchains(
listOf(BlockchainNetwork.fromWalletManager(primaryWalletManager)),
listOf(primaryWalletManager)
))
}
} else {
if (data.card.isMultiwalletAllowed) {
loadMultiWalletData(data, blockchain, null)
}
}
dispatchOnMain(
WalletAction.LoadWallet(),
WalletAction.LoadFiatRate()
)
dispatchOnMain(WalletAction.LoadWallet())
dispatchOnMain(WalletAction.LoadFiatRate())
}
private suspend fun loadMultiWalletData(
@ -233,7 +159,6 @@ class TapWalletManager {
)
} else {
val derivationStyle = scanResponse.card.derivationStyle
val blockchainNetworks = listOf(
BlockchainNetwork(Blockchain.Bitcoin, scanResponse.card),
BlockchainNetwork(Blockchain.Ethereum, scanResponse.card)

View file

@ -136,6 +136,7 @@ class DetailsMiddleware {
fun handle(action: DetailsAction.AppCurrencyAction) {
when (action) {
is DetailsAction.AppCurrencyAction.SelectAppCurrency -> {
store.state.globalState.tapWalletManager.rates.clear()
preferencesStorage.saveAppCurrency(action.fiatCurrencyName)
store.dispatch(GlobalAction.ChangeAppCurrency(action.fiatCurrencyName))
store.dispatch(WalletAction.LoadFiatRate())

View file

@ -22,6 +22,8 @@ import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.common.redux.navigation.AppScreen
import com.tangem.tap.common.redux.navigation.NavigationAction
import com.tangem.tap.domain.extensions.toSendableAmounts
import com.tangem.tap.domain.failedRates
import com.tangem.tap.domain.loadedRates
import com.tangem.tap.domain.tokens.BlockchainNetwork
import com.tangem.tap.features.demo.DemoHelper
import com.tangem.tap.features.home.redux.HomeAction
@ -39,6 +41,7 @@ import kotlinx.coroutines.launch
import org.rekotlin.Action
import org.rekotlin.DispatchFunction
import org.rekotlin.Middleware
import timber.log.Timber
import java.math.BigDecimal
class WalletMiddleware {
@ -103,25 +106,32 @@ class WalletMiddleware {
is WalletAction.LoadFiatRate -> {
val appCurrencyId = globalState.appCurrency
scope.launch {
when {
val coinsList = when {
action.wallet != null -> {
globalState.tapWalletManager.loadFiatRate(
currencyId = appCurrencyId,
wallet = action.wallet,
)
val wallet = action.wallet
wallet.getTokens()
.map { Currency.Token(it, wallet.blockchain, wallet.publicKey.derivationPath?.rawPath) }
.plus(Currency.Blockchain(wallet.blockchain, wallet.publicKey.derivationPath?.rawPath))
}
action.coinsList != null -> {
globalState.tapWalletManager.loadFiatRate(
currencyId = appCurrencyId,
coinsList = action.coinsList,
)
action.coinsList != null -> action.coinsList
else -> walletState.walletsData.map { it.currency }
}
val ratesResult = globalState.tapWalletManager.rates.loadFiatRate(
currencyId = appCurrencyId,
coinsList = coinsList,
)
when (ratesResult) {
is Result.Success -> {
ratesResult.data.loadedRates.forEach {
dispatchOnMain(WalletAction.LoadFiatRate.Success(it.toPair()))
}
ratesResult.data.failedRates.forEach { (currency, throwable) ->
Timber.e(throwable, "Loading rates failed for [%s]", currency.currencySymbol)
}
}
else -> {
val coinsList = walletState.walletsData.map { it.currency }
globalState.tapWalletManager.loadFiatRate(
currencyId = appCurrencyId,
coinsList = coinsList,
)
is Result.Failure -> {
store.dispatchDebugErrorNotification("LoadFiatRate.Failure")
dispatchOnMain(WalletAction.LoadFiatRate.Failure)
}
}
}