Updated on 2026-08-14
This commit is contained in:
commit
a4b0fdaa3d
8 changed files with 146 additions and 136 deletions
64
app/src/main/java/com/tangem/tap/common/Throttling.kt
Normal file
64
app/src/main/java/com/tangem/tap/common/Throttling.kt
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
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)
|
||||
}
|
||||
|
|
@ -13,7 +13,7 @@ class GlobalAnalyticsHandler(val analyticsHandlers: List<AnalyticsHandler>) :
|
|||
blockchain: String?,
|
||||
params: Map<String, String>
|
||||
) {
|
||||
analyticsHandlers.forEach { it.triggerEvent(event, card, blockchain) }
|
||||
analyticsHandlers.forEach { it.triggerEvent(event, card, blockchain, params) }
|
||||
}
|
||||
|
||||
override fun triggerEvent(event: String, params: Map<String, String>) {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package com.tangem.tap.domain
|
|||
import com.tangem.blockchain.blockchains.solana.RentProvider
|
||||
import com.tangem.blockchain.common.*
|
||||
import com.tangem.common.services.Result
|
||||
import com.tangem.tap.common.ThrottlerWithValues
|
||||
import com.tangem.tap.common.extensions.dispatchDebugErrorNotification
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.extensions.safeUpdate
|
||||
|
|
@ -42,32 +43,30 @@ class TapWalletManager {
|
|||
store.state.globalState.configManager?.config?.blockchainSdkConfig ?: BlockchainSdkConfig()
|
||||
}
|
||||
|
||||
private val walletManagersThrottler = Throttler<Blockchain>(10000)
|
||||
private val fiatRatesThrottler = Throttler<Currency>(60000)
|
||||
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
|
||||
val result = if (walletManagersThrottler.isStillThrottled(blockchain)) {
|
||||
delay(200)
|
||||
Result.Success(walletManager.wallet)
|
||||
delay(500)
|
||||
walletManagersThrottler.geValue(blockchain)!!
|
||||
} else {
|
||||
walletManagersThrottler.updateThrottlingTo(blockchain)
|
||||
walletManager.safeUpdate()
|
||||
updateThrottlingForWalletManager(walletManager)
|
||||
}
|
||||
handleUpdateWalletResult(result, walletManager)
|
||||
}
|
||||
|
||||
suspend fun updateWallet(walletManager: WalletManager, force: Boolean) {
|
||||
val result = if (force) {
|
||||
walletManager.safeUpdate()
|
||||
updateThrottlingForWalletManager(walletManager)
|
||||
} else {
|
||||
val blockchain = walletManager.wallet.blockchain
|
||||
if (walletManagersThrottler.isStillThrottled(blockchain)) {
|
||||
delay(200)
|
||||
Result.Success(walletManager.wallet)
|
||||
delay(500)
|
||||
walletManagersThrottler.geValue(blockchain)!!
|
||||
} else {
|
||||
walletManagersThrottler.updateThrottlingTo(blockchain)
|
||||
walletManager.safeUpdate()
|
||||
updateThrottlingForWalletManager(walletManager)
|
||||
}
|
||||
}
|
||||
withContext(Dispatchers.Main) {
|
||||
|
|
@ -78,7 +77,14 @@ class TapWalletManager {
|
|||
store.dispatch(WalletAction.UpdateWallet.Failure(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(fiatCurrency: FiatCurrencyName, wallet: Wallet) {
|
||||
|
|
@ -88,25 +94,25 @@ class TapWalletManager {
|
|||
loadFiatRate(fiatCurrency, currencies)
|
||||
}
|
||||
|
||||
suspend fun loadFiatRate(fiatCurrency: FiatCurrencyName, currency: Currency) {
|
||||
val currencies = listOf(currency)
|
||||
loadFiatRate(fiatCurrency, currencies)
|
||||
}
|
||||
|
||||
suspend fun loadFiatRate(fiatCurrency: FiatCurrencyName, currencies: List<Currency>) {
|
||||
val results = mutableListOf<Pair<Currency, Result<BigDecimal>?>>()
|
||||
currencies.forEach {
|
||||
if (!fiatRatesThrottler.isStillThrottled(it)) {
|
||||
fiatRatesThrottler.updateThrottlingTo(it)
|
||||
results.add(it to coinMarketCapService.getRate(it.currencySymbol, fiatCurrency))
|
||||
handleFiatRatesResult(results)
|
||||
}
|
||||
// get and submit previous result of equivalents.
|
||||
val throttledResult = currencies.filter { fiatRatesThrottler.isStillThrottled(it) }.map {
|
||||
Pair(it, fiatRatesThrottler.geValue(it))
|
||||
}
|
||||
if (throttledResult.isNotEmpty()) handleFiatRatesResult(throttledResult)
|
||||
|
||||
val toUpdate = currencies.filter { !fiatRatesThrottler.isStillThrottled(it) }
|
||||
toUpdate.forEach {
|
||||
fiatRatesThrottler.updateThrottlingTo(it)
|
||||
val result = coinMarketCapService.getRate(it.currencySymbol, fiatCurrency)
|
||||
fiatRatesThrottler.setValue(it, result)
|
||||
handleFiatRatesResult(listOf(it to result))
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun onCardScanned(data: ScanResponse) {
|
||||
walletManagersThrottler.clear()
|
||||
fiatRatesThrottler.clear()
|
||||
// fiatRatesThrottler.clear()
|
||||
store.state.globalState.feedbackManager?.infoHolder?.setCardInfo(data.card)
|
||||
updateConfigManager(data)
|
||||
|
||||
|
|
@ -323,8 +329,7 @@ class TapWalletManager {
|
|||
store.dispatch(WalletAction.LoadFiatRate.Success(rate))
|
||||
}
|
||||
is Result.Failure -> store.dispatch(WalletAction.LoadFiatRate.Failure)
|
||||
null -> {
|
||||
}
|
||||
null -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -333,31 +338,4 @@ class TapWalletManager {
|
|||
|
||||
fun Wallet.getFirstToken(): Token? {
|
||||
return getTokens().toList().getOrNull(0)
|
||||
}
|
||||
|
||||
class Throttler<T>(
|
||||
private val duration: Long,
|
||||
) {
|
||||
|
||||
private val items: MutableMap<T, Long> = mutableMapOf()
|
||||
|
||||
fun isStillThrottled(item: T): Boolean {
|
||||
val inThrottlingUpTo = items[item] ?: return false
|
||||
val diff = System.currentTimeMillis() - inThrottlingUpTo
|
||||
return diff < 0
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
items.clear()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +1,11 @@
|
|||
package com.tangem.tap.features.home
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.telephony.TelephonyManager
|
||||
import android.view.View
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.ui.platform.ComposeView
|
||||
import androidx.compose.ui.text.intl.Locale
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.google.accompanist.appcompattheme.AppCompatTheme
|
||||
import com.tangem.tap.features.home.compose.StoriesScreen
|
||||
|
|
@ -30,20 +29,25 @@ class HomeFragment : Fragment(R.layout.fragment_home), StoreSubscriber<HomeState
|
|||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
store.dispatch(BackupAction.CheckForUnfinishedBackup)
|
||||
val tm = requireContext().getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
|
||||
val countryCodeValue = tm.networkCountryIso
|
||||
|
||||
|
||||
getView()?.findViewById<ComposeView>(R.id.cv_stories)?.setContent {
|
||||
AppCompatTheme {
|
||||
StoriesScreen(
|
||||
homeState,
|
||||
onScanButtonClick = { store.dispatch(HomeAction.ReadCard) },
|
||||
onShopButtonClick = { store.dispatch(HomeAction.GoToShop(countryCodeValue)) }
|
||||
onShopButtonClick = { store.dispatch(HomeAction.GoToShop(getLocale())) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getLocale(): String {
|
||||
// val tm = requireContext().getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
|
||||
// val countryCodeValue = tm.networkCountryIso
|
||||
return Locale.current.language
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
store.subscribe(this) { state ->
|
||||
|
|
|
|||
|
|
@ -204,7 +204,7 @@ private fun sendTransaction(
|
|||
withContext(Dispatchers.Main) {
|
||||
dispatch(WalletAction.UpdateWallet(walletManager.wallet.blockchain))
|
||||
}
|
||||
delay(10000)
|
||||
delay(11000) // more than 10000 to avoid throttling
|
||||
withContext(Dispatchers.Main) {
|
||||
dispatch(WalletAction.UpdateWallet(walletManager.wallet.blockchain))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ sealed class WalletAction : Action {
|
|||
}
|
||||
|
||||
data class LoadFiatRate(
|
||||
val wallet: Wallet? = null, val currency: Currency? = null,
|
||||
val wallet: Wallet? = null, val currencyList: List<Currency>? = null,
|
||||
) : WalletAction() {
|
||||
data class Success(val fiatRate: Pair<Currency, BigDecimal?>) : WalletAction()
|
||||
object Failure : WalletAction()
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ package com.tangem.tap.features.wallet.redux.middlewares
|
|||
import com.tangem.blockchain.common.*
|
||||
import com.tangem.blockchain.extensions.Result
|
||||
import com.tangem.common.extensions.isZero
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.extensions.safeUpdate
|
||||
import com.tangem.tap.common.redux.global.GlobalState
|
||||
import com.tangem.tap.common.redux.navigation.AppScreen
|
||||
|
|
@ -47,7 +46,7 @@ class MultiWalletMiddleware {
|
|||
globalState.scanResponse?.card?.cardId?.let {
|
||||
currenciesRepository.saveAddedToken(it, action.token)
|
||||
}
|
||||
addToken(action.token, walletState, globalState)
|
||||
addTokens(listOf(action.token), walletState, globalState)
|
||||
}
|
||||
is WalletAction.MultiWallet.AddTokens -> {
|
||||
addTokens(action.tokens, walletState, globalState)
|
||||
|
|
@ -62,7 +61,9 @@ class MultiWalletMiddleware {
|
|||
}
|
||||
}
|
||||
}
|
||||
store.dispatch(WalletAction.LoadFiatRate(currency = Currency.Blockchain(action.blockchain)))
|
||||
store.dispatch(WalletAction.LoadFiatRate(
|
||||
currencyList = listOf(Currency.Blockchain(action.blockchain)))
|
||||
)
|
||||
store.dispatch(WalletAction.LoadWallet(action.blockchain)
|
||||
)
|
||||
}
|
||||
|
|
@ -172,80 +173,41 @@ class MultiWalletMiddleware {
|
|||
}
|
||||
}
|
||||
|
||||
private fun addToken(token: Token, walletState: WalletState?, globalState: GlobalState?) {
|
||||
private fun addTokens(tokens: List<Token>, walletState: WalletState?, globalState: GlobalState?) {
|
||||
val scanResponse = globalState?.scanResponse ?: return
|
||||
val wmFactory = globalState.tapWalletManager.walletManagerFactory
|
||||
|
||||
val walletManager = walletState?.getWalletManager(token)
|
||||
?: globalState.tapWalletManager.walletManagerFactory.makeWalletManagerForApp(
|
||||
scanResponse,
|
||||
token.blockchain
|
||||
)?.also { walletManager ->
|
||||
store.dispatch(WalletAction.MultiWallet.AddWalletManagers(walletManager))
|
||||
store.dispatch(WalletAction.MultiWallet.AddBlockchain(walletManager.wallet.blockchain))
|
||||
}
|
||||
|
||||
store.dispatch(WalletAction.LoadFiatRate(currency = Currency.Token(token)))
|
||||
|
||||
scope.launch {
|
||||
when (val result = walletManager?.addToken(token)) {
|
||||
is Result.Success -> {
|
||||
store.dispatchOnMain(WalletAction.MultiWallet.TokenLoaded(result.data, token))
|
||||
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))
|
||||
}
|
||||
is Result.Failure -> {
|
||||
when (val result = walletManager.safeUpdate()) {
|
||||
is com.tangem.common.services.Result.Success -> {
|
||||
val tokenAmount = result.data.getTokenAmount(token) ?: return@launch
|
||||
store.dispatchOnMain(WalletAction.MultiWallet.TokenLoaded(tokenAmount, token))
|
||||
}
|
||||
is com.tangem.common.services.Result.Failure -> {
|
||||
}
|
||||
store.dispatch(WalletAction.LoadFiatRate(currencyList = tokensList.map { Currency.Token(it) }))
|
||||
walletManager?.apply {
|
||||
scope.launch { async { addTokens(tokensList) }.await() }
|
||||
}
|
||||
}
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
is com.tangem.common.services.Result.Failure -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun addTokens(
|
||||
tokens: List<Token>,
|
||||
walletState: WalletState?,
|
||||
globalState: GlobalState?,
|
||||
) {
|
||||
val scanResponse = globalState?.scanResponse ?: return
|
||||
|
||||
val tokensWithManagers = tokens.map { token ->
|
||||
val walletManager = walletState?.getWalletManager(token)
|
||||
?: globalState.tapWalletManager.walletManagerFactory.makeWalletManagerForApp(
|
||||
scanResponse,
|
||||
token.blockchain
|
||||
)?.also { walletManager ->
|
||||
store.dispatch(WalletAction.MultiWallet.AddWalletManagers(walletManager))
|
||||
store.dispatch(WalletAction.MultiWallet.AddBlockchain(walletManager.wallet.blockchain))
|
||||
}
|
||||
store.dispatch(
|
||||
WalletAction.LoadFiatRate(currency = Currency.Token(token))
|
||||
)
|
||||
TokenWithManager(token, walletManager)
|
||||
}
|
||||
scope.launch {
|
||||
tokensWithManagers.forEach {
|
||||
when (val result = it.walletManager?.addToken(it.token)) {
|
||||
is Result.Success -> {
|
||||
store.dispatchOnMain(WalletAction.MultiWallet.TokenLoaded(result.data, it.token))
|
||||
}
|
||||
is Result.Failure -> {
|
||||
when (val result = it.walletManager.safeUpdate()) {
|
||||
is com.tangem.common.services.Result.Success -> {
|
||||
val tokenAmount = result.data.getTokenAmount(it.token) ?: return@launch
|
||||
store.dispatchOnMain(WalletAction.MultiWallet.TokenLoaded(tokenAmount, it.token))
|
||||
}
|
||||
is com.tangem.common.services.Result.Failure -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private data class TokenWithManager(val token: Token, val walletManager: WalletManager?)
|
||||
}
|
||||
|
|
@ -88,18 +88,20 @@ class WalletMiddleware {
|
|||
when {
|
||||
action.wallet != null -> {
|
||||
globalState.tapWalletManager.loadFiatRate(
|
||||
globalState.appCurrency, action.wallet
|
||||
fiatCurrency = globalState.appCurrency,
|
||||
wallet = action.wallet,
|
||||
)
|
||||
}
|
||||
action.currency != null -> {
|
||||
action.currencyList != null -> {
|
||||
globalState.tapWalletManager.loadFiatRate(
|
||||
globalState.appCurrency, action.currency
|
||||
fiatCurrency = globalState.appCurrency,
|
||||
currencies = action.currencyList,
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
globalState.tapWalletManager.loadFiatRate(
|
||||
fiatCurrency = globalState.appCurrency,
|
||||
currencies = walletState.wallets.map { it.currency }
|
||||
currencies = walletState.wallets.map { it.currency },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue