Updated on 2026-08-14
This commit is contained in:
parent
c642d211b1
commit
c66a6f7ecb
9 changed files with 128 additions and 39 deletions
|
|
@ -22,5 +22,9 @@
|
|||
{
|
||||
"name": "WC_SOLANA_TX_SIGN_ENABLED",
|
||||
"version": "5.9.0"
|
||||
},
|
||||
{
|
||||
"name": "TOKEN_LIST_LCE_ENABLED",
|
||||
"version": "5.10.0"
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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")
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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() }
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue