Updated on 2026-08-14

This commit is contained in:
Tangem 2024-10-04 10:57:07 +04:00
parent d487f796be
commit 7bd584c261
27 changed files with 379 additions and 266 deletions

View file

@ -7,7 +7,6 @@ import com.tangem.domain.demo.IsDemoCardUseCase
import com.tangem.domain.promo.PromoBanner
import com.tangem.domain.settings.IsReadyToShowRateAppUseCase
import com.tangem.domain.settings.ShouldShowRingPromoUseCase
import com.tangem.domain.tokens.GetTokenListUseCase
import com.tangem.domain.tokens.error.TokenListError
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
@ -30,7 +29,7 @@ import kotlin.collections.count
@Suppress("LongParameterList")
@ViewModelScoped
internal class GetMultiWalletWarningsFactory @Inject constructor(
private val getTokenListUseCase: GetTokenListUseCase,
private val tokenListStore: MultiWalletTokenListStore,
private val isDemoCardUseCase: IsDemoCardUseCase,
private val isReadyToShowRateAppUseCase: IsReadyToShowRateAppUseCase,
private val shouldShowRingPromoUseCase: ShouldShowRingPromoUseCase,
@ -44,7 +43,7 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
val promoFlow = flow { emit(promoRepository.getRingPromoBanner()) }
return combine(
flow = getTokenListUseCase.launch(userWallet.walletId),
flow = tokenListStore.getOrThrow(userWallet.walletId),
flow2 = isReadyToShowRateAppUseCase(),
flow3 = isNeedToBackupUseCase(userWallet.walletId),
flow4 = shouldShowRingPromoUseCase(userWalletId = userWallet.walletId),
@ -129,6 +128,7 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
clickIntents: WalletClickIntents,
) {
val currencies = maybeTokenList.getMissingAddressCurrencies()
.ifEmpty { return }
addIf(
element = WalletNotification.Informational.MissingAddresses(

View file

@ -0,0 +1,62 @@
package com.tangem.feature.wallet.presentation.wallet.domain
import com.tangem.domain.core.lce.LceFlow
import com.tangem.domain.tokens.GetTokenListUseCase
import com.tangem.domain.tokens.error.TokenListError
import com.tangem.domain.tokens.model.TokenList
import com.tangem.domain.wallets.models.UserWalletId
import dagger.hilt.android.scopes.ViewModelScoped
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.shareIn
import timber.log.Timber
import java.util.concurrent.ConcurrentHashMap
import javax.inject.Inject
@ViewModelScoped
internal class MultiWalletTokenListStore @Inject constructor(
private val getTokenListUseCase: GetTokenListUseCase,
) {
private val flows: ConcurrentHashMap<UserWalletId, LceFlow<TokenListError, TokenList>> by lazy {
ConcurrentHashMap()
}
fun addIfNot(userWalletId: UserWalletId, coroutineScope: CoroutineScope) {
if (flows[userWalletId] != null) {
Timber.d("Flow with token list for $userWalletId already exists")
return
}
coroutineScope.ensureActive()
flows[userWalletId] = getTokenListUseCase
.launch(userWalletId)
.shareIn(
scope = coroutineScope,
started = SharingStarted.WhileSubscribed(),
replay = 1,
)
Timber.d("Flow with token list for $userWalletId created")
}
fun getOrThrow(userWalletId: UserWalletId): LceFlow<TokenListError, TokenList> {
return requireNotNull(flows[userWalletId]) {
"Flow with token list for $userWalletId doesn't exist"
}
}
fun remove(userWalletId: UserWalletId) {
flows.remove(userWalletId)
Timber.d("Flow with token list for $userWalletId removed")
}
fun clear() {
flows.clear()
Timber.d("All flows with token list cleared")
}
}

View file

@ -2,12 +2,12 @@ package com.tangem.feature.wallet.presentation.wallet.loaders.implementors
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.tokens.ApplyTokenListSortingUseCase
import com.tangem.domain.tokens.GetTokenListUseCase
import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletWarningsAnalyticsSender
import com.tangem.feature.wallet.presentation.wallet.domain.GetMultiWalletWarningsFactory
import com.tangem.feature.wallet.presentation.wallet.domain.MultiWalletTokenListStore
import com.tangem.feature.wallet.presentation.wallet.domain.WalletWithFundsChecker
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
import com.tangem.feature.wallet.presentation.wallet.subscribers.MultiWalletTokenListSubscriber
@ -23,7 +23,7 @@ internal class MultiWalletContentLoader(
private val tokenListAnalyticsSender: TokenListAnalyticsSender,
private val walletWarningsAnalyticsSender: WalletWarningsAnalyticsSender,
private val walletWithFundsChecker: WalletWithFundsChecker,
private val getTokenListUseCase: GetTokenListUseCase,
private val tokenListStore: MultiWalletTokenListStore,
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
private val applyTokenListSortingUseCase: ApplyTokenListSortingUseCase,
private val getMultiWalletWarningsFactory: GetMultiWalletWarningsFactory,
@ -38,7 +38,7 @@ internal class MultiWalletContentLoader(
clickIntents = clickIntents,
tokenListAnalyticsSender = tokenListAnalyticsSender,
walletWithFundsChecker = walletWithFundsChecker,
getTokenListUseCase = getTokenListUseCase,
tokenListStore = tokenListStore,
getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase,
applyTokenListSortingUseCase = applyTokenListSortingUseCase,
runPolkadotAccountHealthCheckUseCase = runPolkadotAccountHealthCheckUseCase,

View file

@ -2,12 +2,12 @@ package com.tangem.feature.wallet.presentation.wallet.loaders.implementors
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.tokens.ApplyTokenListSortingUseCase
import com.tangem.domain.tokens.GetTokenListUseCase
import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletWarningsAnalyticsSender
import com.tangem.feature.wallet.presentation.wallet.domain.GetMultiWalletWarningsFactory
import com.tangem.feature.wallet.presentation.wallet.domain.MultiWalletTokenListStore
import com.tangem.feature.wallet.presentation.wallet.domain.WalletWithFundsChecker
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents
@ -21,7 +21,7 @@ internal class MultiWalletContentLoaderFactory @Inject constructor(
private val tokenListAnalyticsSender: TokenListAnalyticsSender,
private val walletWithFundsChecker: WalletWithFundsChecker,
private val getMultiWalletWarningsFactory: GetMultiWalletWarningsFactory,
private val getTokenListUseCase: GetTokenListUseCase,
private val tokenListStore: MultiWalletTokenListStore,
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
private val applyTokenListSortingUseCase: ApplyTokenListSortingUseCase,
private val walletWarningsAnalyticsSender: WalletWarningsAnalyticsSender,
@ -35,7 +35,7 @@ internal class MultiWalletContentLoaderFactory @Inject constructor(
stateHolder = stateHolder,
tokenListAnalyticsSender = tokenListAnalyticsSender,
walletWithFundsChecker = walletWithFundsChecker,
getTokenListUseCase = getTokenListUseCase,
tokenListStore = tokenListStore,
getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase,
getMultiWalletWarningsFactory = getMultiWalletWarningsFactory,
walletWarningsAnalyticsSender = walletWarningsAnalyticsSender,

View file

@ -41,11 +41,11 @@ internal abstract class BasicTokenListSubscriber(
private val sendAnalyticsJobHolder = JobHolder()
private val onTokenListReceivedJobHolder = JobHolder()
protected abstract fun tokenListFlow(): LceFlow<TokenListError, TokenList>
protected abstract fun tokenListFlow(coroutineScope: CoroutineScope): LceFlow<TokenListError, TokenList>
override fun create(coroutineScope: CoroutineScope): Flow<*> {
return combine(
flow = tokenListFlow()
flow = tokenListFlow(coroutineScope)
.onEach { maybeTokenList ->
coroutineScope.launch {
sendTokenListAnalytics(maybeTokenList)

View file

@ -4,7 +4,6 @@ import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.core.lce.Lce
import com.tangem.domain.core.lce.LceFlow
import com.tangem.domain.tokens.ApplyTokenListSortingUseCase
import com.tangem.domain.tokens.GetTokenListUseCase
import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase
import com.tangem.domain.tokens.error.TokenListError
import com.tangem.domain.tokens.model.CryptoCurrency
@ -12,14 +11,16 @@ import com.tangem.domain.tokens.model.TokenList
import com.tangem.domain.tokens.model.TotalFiatBalance
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender
import com.tangem.feature.wallet.presentation.wallet.domain.MultiWalletTokenListStore
import com.tangem.feature.wallet.presentation.wallet.domain.WalletWithFundsChecker
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents
import kotlinx.coroutines.CoroutineScope
@Suppress("LongParameterList")
internal class MultiWalletTokenListSubscriber(
private val userWallet: UserWallet,
private val getTokenListUseCase: GetTokenListUseCase,
private val tokenListStore: MultiWalletTokenListStore,
private val applyTokenListSortingUseCase: ApplyTokenListSortingUseCase,
stateHolder: WalletStateController,
clickIntents: WalletClickIntents,
@ -37,8 +38,10 @@ internal class MultiWalletTokenListSubscriber(
runPolkadotAccountHealthCheckUseCase = runPolkadotAccountHealthCheckUseCase,
) {
override fun tokenListFlow(): LceFlow<TokenListError, TokenList> {
return getTokenListUseCase.launch(userWallet.walletId)
override fun tokenListFlow(coroutineScope: CoroutineScope): LceFlow<TokenListError, TokenList> {
tokenListStore.addIfNot(userWallet.walletId, coroutineScope)
return tokenListStore.getOrThrow(userWallet.walletId)
}
override suspend fun onTokenListReceived(maybeTokenList: Lce<TokenListError, TokenList>) {

View file

@ -12,6 +12,7 @@ import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAn
import com.tangem.feature.wallet.presentation.wallet.domain.WalletWithFundsChecker
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.map
@Suppress("LongParameterList")
@ -34,6 +35,9 @@ internal class SingleWalletWithTokenListSubscriber(
runPolkadotAccountHealthCheckUseCase = runPolkadotAccountHealthCheckUseCase,
) {
override fun tokenListFlow(): LceFlow<TokenListError, TokenList> = getNodlTokenListUseCase(userWallet.walletId)
.map { it.toLce() }
override fun tokenListFlow(coroutineScope: CoroutineScope): LceFlow<TokenListError, TokenList> =
getNodlTokenListUseCase(
userWallet.walletId,
)
.map { it.toLce() }
}

View file

@ -16,6 +16,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.WalletImageResolver
import com.tangem.feature.wallet.presentation.wallet.domain.WalletNameMigrationUseCase
import com.tangem.feature.wallet.presentation.wallet.loaders.WalletScreenContentLoader
@ -69,6 +70,7 @@ internal class WalletViewModel @Inject constructor(
private val shouldAskPermissionUseCase: ShouldAskPermissionUseCase,
private val marketsFeatureToggles: MarketsFeatureToggles,
private val walletImageResolver: WalletImageResolver,
private val tokenListStore: MultiWalletTokenListStore,
analyticsEventsHandler: AnalyticsEventHandler,
) : ViewModel() {
@ -110,6 +112,8 @@ internal class WalletViewModel @Inject constructor(
override fun onCleared() {
super.onCleared()
tokenListStore.clear()
stateHolder.clear()
walletScreenContentLoader.cancelAll()
}
@ -280,7 +284,7 @@ internal class WalletViewModel @Inject constructor(
stateHolder.update(transformer = RenameWalletTransformer(action.selectedWalletId, action.name))
}
is WalletsUpdateActionResolver.Action.Unknown -> {
Timber.w("Unable to perfom action: $action")
Timber.w("Unable to perform action: $action")
}
}
}
@ -318,6 +322,7 @@ internal class WalletViewModel @Inject constructor(
private fun reinitializeWallet(action: WalletsUpdateActionResolver.Action.ReinitializeWallet) {
walletScreenContentLoader.cancel(action.prevWalletId)
tokenListStore.remove(action.prevWalletId)
walletScreenContentLoader.load(
userWallet = action.selectedWallet,
@ -357,6 +362,7 @@ internal class WalletViewModel @Inject constructor(
private suspend fun deleteWallet(action: WalletsUpdateActionResolver.Action.DeleteWallet) {
walletScreenContentLoader.cancel(action.deletedWalletId)
tokenListStore.remove(action.deletedWalletId)
walletScreenContentLoader.load(
userWallet = action.selectedWallet,

View file

@ -136,7 +136,9 @@ internal class WalletsUpdateActionResolver @Inject constructor(
unlockedWallets = wallets.filterNot(UserWallet::isLocked),
)
}
isSelectedWalletCardsCountChanged(state, selectedWallet) -> Action.UpdateWalletCardCount(selectedWallet)
isSelectedWalletCardsCountChanged(state, selectedWallet) -> {
Action.UpdateWalletCardCount(selectedWallet)
}
else -> Action.Unknown
}
}

View file

@ -12,6 +12,7 @@ import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.domain.wallets.usecase.*
import com.tangem.feature.wallet.impl.R
import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent.MainScreen
import com.tangem.feature.wallet.presentation.wallet.domain.MultiWalletTokenListStore
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.WalletAlertState
@ -37,6 +38,7 @@ internal interface WalletCardClickIntents {
@Suppress("LongParameterList")
internal class WalletCardClickIntentsImplementor @Inject constructor(
private val stateHolder: WalletStateController,
private val tokenListStore: MultiWalletTokenListStore,
private val walletEventSender: WalletEventSender,
private val walletScreenContentLoader: WalletScreenContentLoader,
private val renameWalletUseCase: RenameWalletUseCase,
@ -99,6 +101,7 @@ internal class WalletCardClickIntentsImplementor @Inject constructor(
override fun onDeleteAfterConfirmationClick(userWalletId: UserWalletId) {
viewModelScope.launch(dispatchers.main) {
walletScreenContentLoader.cancel(userWalletId)
tokenListStore.remove(userWalletId)
val walletToDelete = getUserWalletUseCase(userWalletId).getOrNull() ?: return@launch
val hasUserWallets = deleteWalletUseCase(userWalletId).getOrElse {
@ -117,6 +120,7 @@ internal class WalletCardClickIntentsImplementor @Inject constructor(
reduxStateHolder.onUserWalletSelected(selectedWallet)
} else {
tokenListStore.clear()
stateHolder.clear()
appRouter.replaceAll(AppRoute.Home)
}

View file

@ -8,6 +8,7 @@ import com.tangem.domain.settings.NeverToShowWalletsScrollPreview
import com.tangem.domain.tokens.FetchCardTokenListUseCase
import com.tangem.domain.tokens.FetchCurrencyStatusUseCase
import com.tangem.domain.tokens.FetchTokenListUseCase
import com.tangem.domain.tokens.FetchTokenListUseCase.RefreshMode
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
import com.tangem.domain.wallets.usecase.SelectWalletUseCase
import com.tangem.feature.wallet.presentation.router.InnerWalletRouter
@ -117,7 +118,7 @@ internal class WalletClickIntents @Inject constructor(
val maybeFetchResult = if (userWallet.scanResponse.cardTypesResolver.isSingleWalletWithToken()) {
fetchCardTokenListUseCase(userWalletId = userWallet.walletId, refresh = true)
} else {
fetchTokenListUseCase(userWalletId = userWallet.walletId, refresh = true)
fetchTokenListUseCase(userWalletId = userWallet.walletId, mode = RefreshMode.FULL)
}
maybeFetchResult.onLeft {

View file

@ -13,6 +13,7 @@ import com.tangem.domain.redux.LegacyAction
import com.tangem.domain.redux.ReduxStateHolder
import com.tangem.domain.settings.*
import com.tangem.domain.tokens.FetchTokenListUseCase
import com.tangem.domain.tokens.FetchTokenListUseCase.RefreshMode
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.analytics.TokenSwapPromoAnalyticsEvent
import com.tangem.domain.wallets.legacy.UserWalletsListManager.Lockable.UnlockType
@ -124,18 +125,19 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor(
analyticsEventHandler.send(Basic.CardWasScanned(AnalyticsParam.ScreensSources.Main))
analyticsEventHandler.send(MainScreen.NoticeScanYourCardTapped)
viewModelScope.launch(dispatchers.main) {
viewModelScope.launch {
val userWallet = getSelectedUserWallet() ?: return@launch
derivePublicKeysUseCase(
userWalletId = userWallet.walletId,
currencies = missedAddressCurrencies,
)
.onRight {
// Refresh must be set to true to ensure that yield balances are updated
fetchTokenListUseCase(userWalletId = userWallet.walletId, refresh = true)
}
.onLeft { Timber.e("Failed to derive public keys: $it") }
).onLeft {
Timber.e(it, "Failed to derive public keys")
return@launch
}
// Refresh must be set to true to ensure that yield balances are updated
fetchTokenListUseCase(userWallet.walletId, mode = RefreshMode.SKIP_CURRENCIES)
}
}