diff --git a/app/src/main/java/com/tangem/tap/domain/RatesRepository.kt b/app/src/main/java/com/tangem/tap/domain/RatesRepository.kt new file mode 100644 index 0000000000..4c9cafbdee --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/RatesRepository.kt @@ -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?>(60000) + + suspend fun loadFiatRate(currencyId: String, coinsList: List): Result { +// 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> = result.data.rates.mapValues { + Result.Success(it.value.toBigDecimal()) + } + val updatedCurrencies = mutableMapOf?>() + 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?>): Result.Success { + val success = mutableMapOf() + val failures = mutableMapOf() + + 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(), + mutableMapOf() + )) + } +} + +typealias RatesResult = Pair, MutableMap> + +val RatesResult.loadedRates + get() = this.first + +val RatesResult.failedRates + get() = this.second \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt index 87de3dfcd7..42f82fe17c 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt @@ -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>(10000) - private val fiatRatesThrottler = ThrottlerWithValues?>(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 { - 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) { - suspend fun handleFiatRatesResult(rates: Map?>) { - 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> = result.data.rates.mapValues { - Result.Success(it.value.toBigDecimal()) - } - val updatedCurrencies = mutableMapOf?>() - 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) diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt index 3bd207b288..9f1b6b6187 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt @@ -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()) diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt index b77bbb3b08..034122f2c1 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt @@ -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) } } } diff --git a/app/src/main/java/com/tangem/tap/common/Throttling.kt b/domain/src/main/java/com/tangem/domain/common/Throttling.kt similarity index 97% rename from app/src/main/java/com/tangem/tap/common/Throttling.kt rename to domain/src/main/java/com/tangem/domain/common/Throttling.kt index e0d3c6ee86..b7ef3d7783 100644 --- a/app/src/main/java/com/tangem/tap/common/Throttling.kt +++ b/domain/src/main/java/com/tangem/domain/common/Throttling.kt @@ -1,4 +1,4 @@ -package com.tangem.tap.common +package com.tangem.domain.common /** [REDACTED_AUTHOR]