Updated on 2026-08-14
This commit is contained in:
parent
76987714e2
commit
74c09ab3ba
6 changed files with 154 additions and 59 deletions
|
|
@ -77,6 +77,6 @@
|
|||
},
|
||||
{
|
||||
"name": "WALLET_BALANCE_FETCHER_ENABLED",
|
||||
"version": "undefined"
|
||||
"version": "5.27.0"
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -12,6 +12,9 @@ import kotlinx.coroutines.launch
|
|||
*/
|
||||
class JobHolder {
|
||||
|
||||
val isActive: Boolean
|
||||
get() = job?.isActive ?: false
|
||||
|
||||
private var job: Job? = null
|
||||
|
||||
/** Update current [JobHolder.job] and return new [job] */
|
||||
|
|
|
|||
|
|
@ -85,7 +85,9 @@ class CachedCurrenciesStatusesOperations(
|
|||
|
||||
val nonEmptyCurrencies = currenciesFlow.mapNotNull { it.getOrNull() }.firstOrNull()?.toNonEmptyListOrNull()
|
||||
|
||||
if (!isFetchingStarted(userWalletId) && nonEmptyCurrencies != null) {
|
||||
if (!tokensFeatureToggles.isWalletBalanceFetcherEnabled && !isFetchingStarted(userWalletId) &&
|
||||
nonEmptyCurrencies != null
|
||||
) {
|
||||
launch {
|
||||
setFetchStarted(userWalletId)
|
||||
|
||||
|
|
@ -145,7 +147,7 @@ class CachedCurrenciesStatusesOperations(
|
|||
)
|
||||
}
|
||||
|
||||
if (!isFetchingStarted(userWalletId)) {
|
||||
if (!tokensFeatureToggles.isWalletBalanceFetcherEnabled && !isFetchingStarted(userWalletId)) {
|
||||
launch {
|
||||
setFetchStarted(userWalletId)
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ import com.tangem.domain.settings.*
|
|||
import com.tangem.domain.tokens.FetchCurrencyStatusUseCase
|
||||
import com.tangem.domain.tokens.RefreshMultiCurrencyWalletQuotesUseCase
|
||||
import com.tangem.domain.tokens.TokensFeatureToggles
|
||||
import com.tangem.domain.tokens.wallet.WalletBalanceFetcher
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.domain.wallets.models.isMultiCurrency
|
||||
|
|
@ -32,10 +31,7 @@ import com.tangem.feature.wallet.presentation.deeplink.WalletDeepLinksHandler
|
|||
import com.tangem.feature.wallet.presentation.router.InnerWalletRouter
|
||||
import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent
|
||||
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.SelectedWalletAnalyticsSender
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.MultiWalletTokenListStore
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.OnrampStatusFactory
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.WalletImageResolver
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.WalletNameMigrationUseCase
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.*
|
||||
import com.tangem.feature.wallet.presentation.wallet.loaders.WalletScreenContentLoader
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.PushNotificationsBottomSheetConfig
|
||||
|
|
@ -85,7 +81,7 @@ internal class WalletModel @Inject constructor(
|
|||
private val analyticsEventsHandler: AnalyticsEventHandler,
|
||||
private val deepLinksRegistry: DeepLinksRegistry,
|
||||
private val fetchCurrencyStatusUseCase: FetchCurrencyStatusUseCase,
|
||||
private val walletBalanceFetcher: WalletBalanceFetcher,
|
||||
private val walletContentFetcher: WalletContentFetcher,
|
||||
private val tokensFeatureToggles: TokensFeatureToggles,
|
||||
private val appRouter: AppRouter,
|
||||
private val routingFeatureToggle: RoutingFeatureToggle,
|
||||
|
|
@ -182,7 +178,7 @@ internal class WalletModel @Inject constructor(
|
|||
)
|
||||
}
|
||||
.onEach(::updateWallets)
|
||||
.flowOn(dispatchers.main)
|
||||
.flowOn(dispatchers.default)
|
||||
.launchIn(modelScope)
|
||||
.saveIn(walletsUpdateJobHolder)
|
||||
}
|
||||
|
|
@ -370,41 +366,43 @@ internal class WalletModel @Inject constructor(
|
|||
),
|
||||
)
|
||||
|
||||
fetchWalletContent(userWallet = action.selectedWallet)
|
||||
|
||||
walletScreenContentLoader.load(
|
||||
userWallet = action.selectedWallet,
|
||||
clickIntents = clickIntents,
|
||||
coroutineScope = modelScope,
|
||||
)
|
||||
|
||||
fetchIfSingleWallet(action.selectedWallet)
|
||||
val otherWallets = action.wallets.minus(action.selectedWallet)
|
||||
|
||||
otherWallets.onEach { userWallet ->
|
||||
modelScope.launch { walletContentFetcher(userWalletId = userWallet.walletId) }
|
||||
}
|
||||
|
||||
if (action.wallets.size > 1 && isWalletsScrollPreviewEnabled()) {
|
||||
withContext(dispatchers.io) { delay(timeMillis = 1_800) }
|
||||
val direction = if (action.selectedWalletIndex == action.wallets.lastIndex) {
|
||||
Direction.RIGHT
|
||||
} else {
|
||||
Direction.LEFT
|
||||
}
|
||||
|
||||
walletEventSender.send(
|
||||
event = WalletEvent.DemonstrateWalletsScrollPreview(
|
||||
direction = if (action.selectedWalletIndex == action.wallets.lastIndex) {
|
||||
Direction.RIGHT
|
||||
} else {
|
||||
Direction.LEFT
|
||||
},
|
||||
),
|
||||
)
|
||||
demonstrateWalletsScrollPreview(direction = direction)
|
||||
}
|
||||
}
|
||||
|
||||
private fun reinitializeWallet(action: WalletsUpdateActionResolver.Action.ReinitializeWallet) {
|
||||
private suspend fun reinitializeWallet(action: WalletsUpdateActionResolver.Action.ReinitializeWallet) {
|
||||
walletScreenContentLoader.cancel(action.prevWalletId)
|
||||
tokenListStore.remove(action.prevWalletId)
|
||||
|
||||
fetchWalletContent(userWallet = action.selectedWallet)
|
||||
|
||||
walletScreenContentLoader.load(
|
||||
userWallet = action.selectedWallet,
|
||||
clickIntents = clickIntents,
|
||||
coroutineScope = modelScope,
|
||||
)
|
||||
|
||||
fetchIfSingleWallet(userWallet = action.selectedWallet)
|
||||
|
||||
stateHolder.update(
|
||||
ReinitializeWalletTransformer(
|
||||
prevWalletId = action.prevWalletId,
|
||||
|
|
@ -415,15 +413,15 @@ internal class WalletModel @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
private fun addWallet(action: WalletsUpdateActionResolver.Action.AddWallet) {
|
||||
private suspend fun addWallet(action: WalletsUpdateActionResolver.Action.AddWallet) {
|
||||
fetchWalletContent(userWallet = action.selectedWallet)
|
||||
|
||||
walletScreenContentLoader.load(
|
||||
userWallet = action.selectedWallet,
|
||||
clickIntents = clickIntents,
|
||||
coroutineScope = modelScope,
|
||||
)
|
||||
|
||||
fetchIfSingleWallet(userWallet = action.selectedWallet)
|
||||
|
||||
stateHolder.update(
|
||||
AddWalletTransformer(
|
||||
userWallet = action.selectedWallet,
|
||||
|
|
@ -494,6 +492,16 @@ internal class WalletModel @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
private fun demonstrateWalletsScrollPreview(direction: Direction) {
|
||||
modelScope.launch(dispatchers.mainImmediate) {
|
||||
delay(timeMillis = 1_800)
|
||||
|
||||
walletEventSender.send(
|
||||
event = WalletEvent.DemonstrateWalletsScrollPreview(direction = direction),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun scrollToWallet(prevIndex: Int, newIndex: Int, onConsume: () -> Unit = {}) {
|
||||
// Should not show scroll animation if WalletScreen isn't in the background.
|
||||
if (screenLifecycleProvider.isBackgroundState.value) {
|
||||
|
|
@ -521,16 +529,25 @@ internal class WalletModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun fetchWalletContent(userWallet: UserWallet) {
|
||||
if (tokensFeatureToggles.isWalletBalanceFetcherEnabled) {
|
||||
/*
|
||||
* Updating the balance of the current wallet is an essential part of InitializationWallets,
|
||||
* so the coroutine is launched in the current context
|
||||
*/
|
||||
supervisorScope {
|
||||
launch { walletContentFetcher(userWalletId = userWallet.walletId) }
|
||||
}
|
||||
} else {
|
||||
fetchIfSingleWallet(userWallet = userWallet)
|
||||
}
|
||||
}
|
||||
|
||||
private fun fetchIfSingleWallet(userWallet: UserWallet) {
|
||||
modelScope.launch {
|
||||
if (tokensFeatureToggles.isWalletBalanceFetcherEnabled) {
|
||||
walletBalanceFetcher(params = WalletBalanceFetcher.Params(userWalletId = userWallet.walletId))
|
||||
.onLeft(Timber::e)
|
||||
} else {
|
||||
if (userWallet is UserWallet.Cold && userWallet.scanResponse.cardTypesResolver.isSingleWallet()) {
|
||||
fetchCurrencyStatusUseCase(userWalletId = userWallet.walletId)
|
||||
.onLeft { Timber.e(it.toString()) }
|
||||
}
|
||||
if (userWallet is UserWallet.Cold && userWallet.scanResponse.cardTypesResolver.isSingleWallet()) {
|
||||
modelScope.launch {
|
||||
fetchCurrencyStatusUseCase(userWalletId = userWallet.walletId)
|
||||
.onLeft { Timber.e(it.toString()) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,13 +12,12 @@ import com.tangem.domain.tokens.FetchCurrencyStatusUseCase
|
|||
import com.tangem.domain.tokens.FetchTokenListUseCase
|
||||
import com.tangem.domain.tokens.FetchTokenListUseCase.RefreshMode
|
||||
import com.tangem.domain.tokens.TokensFeatureToggles
|
||||
import com.tangem.domain.tokens.error.TokenListError
|
||||
import com.tangem.domain.tokens.wallet.WalletBalanceFetcher
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
|
||||
import com.tangem.domain.wallets.usecase.SelectWalletUseCase
|
||||
import com.tangem.feature.wallet.presentation.router.InnerWalletRouter
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.OnrampStatusFactory
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.WalletContentFetcher
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.unwrap
|
||||
import com.tangem.feature.wallet.presentation.wallet.loaders.WalletScreenContentLoader
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
|
||||
|
|
@ -30,7 +29,6 @@ import kotlinx.coroutines.CoroutineScope
|
|||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
|
|
@ -47,7 +45,7 @@ internal class WalletClickIntents @Inject constructor(
|
|||
private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase,
|
||||
private val selectWalletUseCase: SelectWalletUseCase,
|
||||
private val fetchTokenListUseCase: FetchTokenListUseCase,
|
||||
private val walletBalanceFetcher: WalletBalanceFetcher,
|
||||
private val walletContentFetcher: WalletContentFetcher,
|
||||
private val tokensFeatureToggles: TokensFeatureToggles,
|
||||
private val fetchCardTokenListUseCase: FetchCardTokenListUseCase,
|
||||
private val fetchCurrencyStatusUseCase: FetchCurrencyStatusUseCase,
|
||||
|
|
@ -92,6 +90,10 @@ internal class WalletClickIntents @Inject constructor(
|
|||
stateHolder.update { it.copy(selectedWalletIndex = index) }
|
||||
|
||||
maybeUserWallet.onRight {
|
||||
if (tokensFeatureToggles.isWalletBalanceFetcherEnabled) {
|
||||
launch { walletContentFetcher(userWalletId = it.walletId) }
|
||||
}
|
||||
|
||||
walletScreenContentLoader.load(
|
||||
userWallet = it,
|
||||
clickIntents = this@WalletClickIntents,
|
||||
|
|
@ -131,21 +133,27 @@ internal class WalletClickIntents @Inject constructor(
|
|||
)
|
||||
|
||||
modelScope.launch {
|
||||
val maybeFetchResult = if (tokensFeatureToggles.isWalletBalanceFetcherEnabled) {
|
||||
walletBalanceFetcher.invoke(params = WalletBalanceFetcher.Params(userWalletId = userWallet.walletId))
|
||||
.mapLeft {
|
||||
Timber.e(it)
|
||||
TokenListError.DataError(it)
|
||||
}
|
||||
if (tokensFeatureToggles.isWalletBalanceFetcherEnabled) {
|
||||
walletContentFetcher(userWalletId = userWallet.walletId, forceUpdate = true)
|
||||
} else {
|
||||
val isSingleWalletWithToken = userWallet is UserWallet.Cold &&
|
||||
userWallet.cardTypesResolver.isSingleWalletWithToken()
|
||||
|
||||
if (isSingleWalletWithToken) {
|
||||
val maybeFetchResult = if (isSingleWalletWithToken) {
|
||||
fetchCardTokenListUseCase(userWalletId = userWallet.walletId, refresh = true)
|
||||
} else {
|
||||
fetchTokenListUseCase(userWalletId = userWallet.walletId, mode = RefreshMode.FULL)
|
||||
}
|
||||
|
||||
maybeFetchResult.onLeft {
|
||||
stateHolder.update(
|
||||
SetTokenListErrorTransformer(
|
||||
selectedWallet = userWallet,
|
||||
error = it,
|
||||
appCurrency = getSelectedAppCurrencyUseCase.unwrap(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
buildList {
|
||||
|
|
@ -159,16 +167,6 @@ internal class WalletClickIntents @Inject constructor(
|
|||
}
|
||||
.awaitAll()
|
||||
|
||||
maybeFetchResult.onLeft {
|
||||
stateHolder.update(
|
||||
SetTokenListErrorTransformer(
|
||||
selectedWallet = userWallet,
|
||||
error = it,
|
||||
appCurrency = getSelectedAppCurrencyUseCase.unwrap(),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
stateHolder.update(
|
||||
SetRefreshStateTransformer(userWalletId = userWallet.walletId, isRefreshing = false),
|
||||
)
|
||||
|
|
@ -186,11 +184,11 @@ internal class WalletClickIntents @Inject constructor(
|
|||
|
||||
modelScope.launch {
|
||||
if (tokensFeatureToggles.isWalletBalanceFetcherEnabled) {
|
||||
walletBalanceFetcher(params = WalletBalanceFetcher.Params(userWalletId = userWallet.walletId))
|
||||
.onLeft(Timber::e)
|
||||
walletContentFetcher(userWalletId = userWallet.walletId, forceUpdate = true)
|
||||
} else {
|
||||
fetchCurrencyStatusUseCase(userWallet.walletId, refresh = true)
|
||||
}
|
||||
|
||||
onrampStatusFactory.updateOnrmapTransactionStatuses(userWallet)
|
||||
walletScreenContentLoader.load(
|
||||
userWallet = userWallet,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,75 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.domain
|
||||
|
||||
import com.tangem.domain.tokens.wallet.WalletBalanceFetcher
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.coroutines.JobHolder
|
||||
import com.tangem.utils.coroutines.saveInAndJoin
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.supervisorScope
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
/**
|
||||
* Wallet content fetcher
|
||||
*
|
||||
* @property walletBalanceFetcher fetcher for wallet balance
|
||||
* @property dispatchers dispatchers
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Singleton
|
||||
internal class WalletContentFetcher @Inject constructor(
|
||||
private val walletBalanceFetcher: WalletBalanceFetcher,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) {
|
||||
|
||||
private val fetchingJobMap = ConcurrentHashMap<UserWalletId, JobHolder>()
|
||||
private val mutex = Mutex()
|
||||
|
||||
suspend operator fun invoke(userWalletId: UserWalletId, forceUpdate: Boolean = false) = supervisorScope {
|
||||
withContext(dispatchers.default) {
|
||||
// Use mutex to ensure thread safety
|
||||
val jobHolder = mutex.withLock {
|
||||
val savedJobHolder = fetchingJobMap.getOrPut(key = userWalletId, defaultValue = ::JobHolder)
|
||||
|
||||
/*
|
||||
* If this is not a forced update and there is a saved job in the cache
|
||||
* (doesn't matter if it is active or not), then skip the update process.
|
||||
*/
|
||||
if (!forceUpdate && savedJobHolder != null && !savedJobHolder.isEmpty()) {
|
||||
Timber.d("Skip fetching for $userWalletId")
|
||||
|
||||
return@withContext
|
||||
}
|
||||
|
||||
/*
|
||||
* If this is a forced update and there is already a job in the cache that is updating the balance,
|
||||
* then cancel the previous update.
|
||||
*/
|
||||
if (forceUpdate && savedJobHolder?.isActive == true) {
|
||||
Timber.d("Cancel old fetching for $userWalletId")
|
||||
|
||||
savedJobHolder.cancel()
|
||||
}
|
||||
|
||||
JobHolder().also { fetchingJobMap[userWalletId] = it }
|
||||
}
|
||||
|
||||
Timber.d("Start fetching for $userWalletId")
|
||||
|
||||
val maybeResult = launch {
|
||||
walletBalanceFetcher(params = WalletBalanceFetcher.Params(userWalletId = userWalletId))
|
||||
.onLeft(Timber::e)
|
||||
}
|
||||
.saveInAndJoin(jobHolder)
|
||||
|
||||
Timber.d("Finish fetching with result $maybeResult for $userWalletId")
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue