Updated on 2026-08-14

This commit is contained in:
Tangem 2024-04-01 12:46:03 +04:00
parent c642d211b1
commit c66a6f7ecb
9 changed files with 128 additions and 39 deletions

View file

@ -22,5 +22,9 @@
{
"name": "WC_SOLANA_TX_SIGN_ENABLED",
"version": "5.9.0"
},
{
"name": "TOKEN_LIST_LCE_ENABLED",
"version": "5.10.0"
}
]

View file

@ -0,0 +1,20 @@
package com.tangem.feature.wallet.di
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
import com.tangem.feature.wallet.featuretoggle.WalletFeatureToggles
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
internal object FeatureTogglesModule {
@Provides
@Singleton
fun provideWalletFeatureToggles(featureTogglesManager: FeatureTogglesManager): WalletFeatureToggles {
return WalletFeatureToggles(featureTogglesManager)
}
}

View file

@ -0,0 +1,11 @@
package com.tangem.feature.wallet.featuretoggle
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
internal class WalletFeatureToggles(
private val featureTogglesManager: FeatureTogglesManager,
) {
val isTokenListLceFlowEnabled: Boolean
get() = featureTogglesManager.isFeatureEnabled("TOKEN_LIST_LCE_ENABLED")
}

View file

@ -7,12 +7,14 @@ import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
import com.tangem.domain.core.utils.getOrElse
import com.tangem.domain.tokens.ApplyTokenListSortingUseCase
import com.tangem.domain.tokens.GetTokenListUseCase
import com.tangem.domain.tokens.ToggleTokenListGroupingUseCase
import com.tangem.domain.tokens.ToggleTokenListSortingUseCase
import com.tangem.domain.tokens.model.TokenList
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.feature.wallet.featuretoggle.WalletFeatureToggles
import com.tangem.feature.wallet.presentation.organizetokens.analytics.PortfolioOrganizeTokensAnalyticsEvent
import com.tangem.feature.wallet.presentation.organizetokens.model.OrganizeTokensListState
import com.tangem.feature.wallet.presentation.organizetokens.model.OrganizeTokensState
@ -39,6 +41,7 @@ internal class OrganizeTokensViewModel @Inject constructor(
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase,
private val analyticsEventsHandler: AnalyticsEventHandler,
private val walletFeatureToggles: WalletFeatureToggles,
private val dispatchers: CoroutineDispatcherProvider,
savedStateHandle: SavedStateHandle,
) : ViewModel(), DefaultLifecycleObserver, OrganizeTokensIntents {
@ -65,7 +68,7 @@ internal class OrganizeTokensViewModel @Inject constructor(
UserWalletId(userWalletIdValue)
}
private var tokenList: TokenList? = null
private var cachedTokenList: TokenList? = null
val uiState: StateFlow<OrganizeTokensState> = stateHolder.stateFlow
@ -89,7 +92,7 @@ internal class OrganizeTokensViewModel @Inject constructor(
}
override fun onSortClick() {
val list = tokenList ?: return
val list = cachedTokenList ?: return
if (list.sortedBy == TokenList.SortType.BALANCE) return
analyticsEventsHandler.send(PortfolioOrganizeTokensAnalyticsEvent.ByBalance)
@ -99,14 +102,14 @@ internal class OrganizeTokensViewModel @Inject constructor(
ifLeft = stateHolder::updateStateWithError,
ifRight = {
stateHolder.updateStateAfterTokenListSorting(it)
tokenList = it
cachedTokenList = it
},
)
}
}
override fun onGroupClick() {
val list = tokenList ?: return
val list = cachedTokenList ?: return
analyticsEventsHandler.send(PortfolioOrganizeTokensAnalyticsEvent.Group)
@ -115,7 +118,7 @@ internal class OrganizeTokensViewModel @Inject constructor(
ifLeft = stateHolder::updateStateWithError,
ifRight = {
stateHolder.updateStateAfterTokenListSorting(it)
tokenList = it
cachedTokenList = it
},
)
}
@ -138,7 +141,7 @@ internal class OrganizeTokensViewModel @Inject constructor(
val result = applyTokenListSortingUseCase(
userWalletId = userWalletId,
sortedTokensIds = resolver.resolve(listState, tokenList),
sortedTokensIds = resolver.resolve(listState, cachedTokenList),
isGroupedByNetwork = isGroupedByNetwork,
isSortedByBalance = isSortedByBalance,
)
@ -161,16 +164,41 @@ internal class OrganizeTokensViewModel @Inject constructor(
private fun bootstrapTokenList() {
viewModelScope.launch(dispatchers.default) {
val maybeTokenList = getTokenListUseCase.launch(userWalletId)
.first { it.getOrNull()?.totalFiatBalance !is TokenList.FiatBalance.Loading }
val tokenList = getTokenList() ?: return@launch
maybeTokenList.fold(
ifLeft = stateHolder::updateStateWithError,
ifRight = {
stateHolder.updateStateWithTokenList(it)
tokenList = it
},
)
stateHolder.updateStateWithTokenList(tokenList)
cachedTokenList = tokenList
}
}
private suspend fun getTokenList(): TokenList? {
return if (walletFeatureToggles.isTokenListLceFlowEnabled) {
val tokenList = getTokenListUseCase.launchLce(userWalletId)
.transform { maybeTokenList ->
val tokenList = maybeTokenList.getOrElse(
ifLoading = { return@transform },
ifError = { error ->
stateHolder.updateStateWithError(error)
return@transform
},
)
emit(tokenList)
}
tokenList.firstOrNull()
} else {
val maybeTokenList = getTokenListUseCase.launch(userWalletId)
.first { maybeTokenList ->
maybeTokenList.getOrNull()?.totalFiatBalance !is TokenList.FiatBalance.Loading
}
maybeTokenList.getOrElse { error ->
stateHolder.updateStateWithError(error)
null
}
}
}
@ -189,7 +217,7 @@ internal class OrganizeTokensViewModel @Inject constructor(
if (dragOperationType !is DragAndDropAdapter.DragOperation.Type.End) return
if (uiState.value.header.isSortedByBalance && dragOperationType.isItemsOrderChanged) {
tokenList = tokenList?.disableSortingByBalance()
cachedTokenList = cachedTokenList?.disableSortingByBalance()
stateHolder.disableSortingByBalance()
}
}

View file

@ -4,6 +4,7 @@ import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.tokens.ApplyTokenListSortingUseCase
import com.tangem.domain.tokens.GetTokenListUseCase
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.feature.wallet.featuretoggle.WalletFeatureToggles
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
@ -26,6 +27,7 @@ internal class MultiWalletContentLoader(
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
private val applyTokenListSortingUseCase: ApplyTokenListSortingUseCase,
private val getMultiWalletWarningsFactory: GetMultiWalletWarningsFactory,
private val walletFeatureToggles: WalletFeatureToggles,
) : WalletContentLoader(id = userWallet.walletId) {
override fun create(): List<WalletSubscriber> {
@ -38,6 +40,7 @@ internal class MultiWalletContentLoader(
walletWithFundsChecker = walletWithFundsChecker,
getTokenListUseCase = getTokenListUseCase,
getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase,
walletFeatureToggles = walletFeatureToggles,
applyTokenListSortingUseCase = applyTokenListSortingUseCase,
),
MultiWalletWarningsSubscriber(

View file

@ -4,6 +4,7 @@ import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.tokens.ApplyTokenListSortingUseCase
import com.tangem.domain.tokens.GetTokenListUseCase
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.feature.wallet.featuretoggle.WalletFeatureToggles
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
@ -24,6 +25,7 @@ internal class MultiWalletContentLoaderFactory @Inject constructor(
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
private val applyTokenListSortingUseCase: ApplyTokenListSortingUseCase,
private val walletWarningsAnalyticsSender: WalletWarningsAnalyticsSender,
private val walletFeatureToggles: WalletFeatureToggles,
) {
fun create(userWallet: UserWallet, clickIntents: WalletClickIntents): WalletContentLoader {
@ -38,6 +40,7 @@ internal class MultiWalletContentLoaderFactory @Inject constructor(
getMultiWalletWarningsFactory = getMultiWalletWarningsFactory,
walletWarningsAnalyticsSender = walletWarningsAnalyticsSender,
applyTokenListSortingUseCase = applyTokenListSortingUseCase,
walletFeatureToggles = walletFeatureToggles,
)
}
}

View file

@ -1,9 +1,11 @@
package com.tangem.feature.wallet.presentation.wallet.subscribers
import arrow.core.Either
import arrow.core.getOrElse
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.core.lce.Lce
import com.tangem.domain.core.lce.LceFlow
import com.tangem.domain.core.utils.getOrElse
import com.tangem.domain.tokens.error.TokenListError
import com.tangem.domain.tokens.model.TokenList
import com.tangem.domain.wallets.models.UserWallet
@ -23,8 +25,6 @@ import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import timber.log.Timber
internal typealias MaybeTokenListFlow = Flow<Either<TokenListError, TokenList>>
@Suppress("LongParameterList")
internal abstract class BasicTokenListSubscriber(
private val userWallet: UserWallet,
@ -38,7 +38,7 @@ internal abstract class BasicTokenListSubscriber(
private val sendAnalyticsJobHolder = JobHolder()
private val onTokenListReceivedJobHolder = JobHolder()
protected abstract fun tokenListFlow(): MaybeTokenListFlow
protected abstract fun tokenListFlow(): LceFlow<TokenListError, TokenList>
override fun create(coroutineScope: CoroutineScope): Flow<*> {
return combine(
@ -56,11 +56,17 @@ internal abstract class BasicTokenListSubscriber(
},
flow2 = getSelectedAppCurrencyUseCase().distinctUntilChanged(),
transform = { maybeTokenList, maybeAppCurrency ->
val tokenList = maybeTokenList.getOrElse { e ->
Timber.e("Failed to load token list: $e")
SetTokenListErrorTransformer(userWallet.walletId, e)
return@combine
}
val tokenList = maybeTokenList.getOrElse(
ifLoading = { maybeContent ->
maybeContent ?: return@combine
},
ifError = { e ->
Timber.e("Failed to load token list: $e")
SetTokenListErrorTransformer(userWallet.walletId, e)
return@combine
},
)
val appCurrency = maybeAppCurrency.getOrElse { e ->
Timber.e("Failed to load app currency: $e")
AppCurrency.Default
@ -72,17 +78,17 @@ internal abstract class BasicTokenListSubscriber(
)
}
protected open suspend fun onTokenListReceived(maybeTokenList: Either<TokenListError, TokenList>) {
protected open suspend fun onTokenListReceived(maybeTokenList: Lce<TokenListError, TokenList>) {
/* no-op */
}
private suspend fun sendTokenListAnalytics(maybeTokenList: Either<TokenListError, TokenList>) {
private suspend fun sendTokenListAnalytics(maybeTokenList: Lce<TokenListError, TokenList>) {
val displayedState = stateHolder.getWalletStateIfSelected(userWallet.walletId)
tokenListAnalyticsSender.send(
displayedUiState = displayedState,
userWallet = userWallet,
tokenList = maybeTokenList.getOrElse { return },
tokenList = maybeTokenList.getOrNull() ?: return,
)
}

View file

@ -1,24 +1,28 @@
package com.tangem.feature.wallet.presentation.wallet.subscribers
import arrow.core.Either
import arrow.core.getOrElse
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.core.lce.Lce
import com.tangem.domain.core.lce.LceFlow
import com.tangem.domain.core.utils.toLce
import com.tangem.domain.tokens.ApplyTokenListSortingUseCase
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.TokenList
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.feature.wallet.featuretoggle.WalletFeatureToggles
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender
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.flow.map
@Suppress("LongParameterList")
internal class MultiWalletTokenListSubscriber(
private val userWallet: UserWallet,
private val getTokenListUseCase: GetTokenListUseCase,
private val applyTokenListSortingUseCase: ApplyTokenListSortingUseCase,
private val walletFeatureToggles: WalletFeatureToggles,
stateHolder: WalletStateController,
clickIntents: WalletClickIntents,
tokenListAnalyticsSender: TokenListAnalyticsSender,
@ -33,16 +37,20 @@ internal class MultiWalletTokenListSubscriber(
getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase,
) {
override fun tokenListFlow(): MaybeTokenListFlow = getTokenListUseCase.launch(userWallet.walletId)
override suspend fun onTokenListReceived(maybeTokenList: Either<TokenListError, TokenList>) {
// TODO disabled for 5.7.2 because of potential critical
// updateSortingIfNeeded(maybeTokenList)
override fun tokenListFlow(): LceFlow<TokenListError, TokenList> {
return if (walletFeatureToggles.isTokenListLceFlowEnabled) {
getTokenListUseCase.launchLce(userWallet.walletId)
} else {
getTokenListUseCase.launch(userWallet.walletId).map { it.toLce() }
}
}
@Suppress("UnusedPrivateMember")
private suspend fun updateSortingIfNeeded(maybeTokenList: Either<TokenListError, TokenList>) {
val tokenList = maybeTokenList.getOrElse { return }
override suspend fun onTokenListReceived(maybeTokenList: Lce<TokenListError, TokenList>) {
updateSortingIfNeeded(maybeTokenList)
}
private suspend fun updateSortingIfNeeded(maybeTokenList: Lce<TokenListError, TokenList>) {
val tokenList = maybeTokenList.getOrNull() ?: return
if (!checkNeedSorting(tokenList)) return
applyTokenListSortingUseCase(

View file

@ -1,12 +1,17 @@
package com.tangem.feature.wallet.presentation.wallet.subscribers
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.core.lce.LceFlow
import com.tangem.domain.core.utils.toLce
import com.tangem.domain.tokens.GetCardTokensListUseCase
import com.tangem.domain.tokens.error.TokenListError
import com.tangem.domain.tokens.model.TokenList
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.WalletWithFundsChecker
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents
import kotlinx.coroutines.flow.map
@Suppress("LongParameterList")
internal class SingleWalletWithTokenListSubscriber(
@ -26,5 +31,6 @@ internal class SingleWalletWithTokenListSubscriber(
getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase,
) {
override fun tokenListFlow(): MaybeTokenListFlow = getCardTokensListUseCase(userWallet.walletId)
override fun tokenListFlow(): LceFlow<TokenListError, TokenList> = getCardTokensListUseCase(userWallet.walletId)
.map { it.toLce() }
}