Updated on 2026-08-14

This commit is contained in:
Tangem 2026-03-02 13:35:44 +04:00
parent 81ef606a43
commit 665d56c9e8
30 changed files with 135 additions and 452 deletions

View file

@ -2,7 +2,6 @@ package com.tangem.tap.routing.utils
import com.tangem.common.routing.AppRoute
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.domain.models.PortfolioId
import com.tangem.domain.qrscanning.models.SourceType
import com.tangem.feature.qrscanning.QrScanningComponent
import com.tangem.feature.referral.api.ReferralComponent
@ -143,11 +142,7 @@ internal class ChildFactory @Inject constructor(
AppRoute.ManageTokens.Source.ACCOUNT -> ManageTokensSource.ACCOUNT
}
val mode = when (val portfolio = route.portfolioId) {
is PortfolioId.Account -> ManageTokensMode.Account(portfolio.accountId)
is PortfolioId.Wallet -> ManageTokensMode.Wallet(portfolio.userWalletId)
null -> ManageTokensMode.None
}
val mode = route.accountId?.let { ManageTokensMode.Account(it) } ?: ManageTokensMode.None
createComponentChild(
context = context,

View file

@ -7,13 +7,12 @@ import android.os.Bundle
import com.tangem.common.routing.bundle.RouteBundleParams
import com.tangem.common.routing.bundle.bundle
import com.tangem.common.routing.entity.InitScreenLaunchMode
import com.tangem.domain.staking.model.StakingIntegrationID
import com.tangem.core.decompose.navigation.Route
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.feedback.models.WalletMetaInfo
import com.tangem.domain.markets.TokenMarketParams
import com.tangem.domain.models.PortfolioId
import com.tangem.domain.models.account.Account
import com.tangem.domain.models.account.AccountId
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.models.serialization.SerializedBigDecimal
@ -21,6 +20,7 @@ import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.nft.models.NFTAsset
import com.tangem.domain.onramp.model.OnrampSource
import com.tangem.domain.pay.TangemPayDetailsConfig
import com.tangem.domain.staking.model.StakingIntegrationID
import com.tangem.domain.tokens.model.details.NavigationAction
import kotlinx.serialization.Serializable
@ -128,8 +128,8 @@ sealed class AppRoute(val path: String) : Route {
@Serializable
data class ManageTokens(
val source: Source,
val portfolioId: PortfolioId? = null,
) : AppRoute(path = "${source.name.lowercase()}/manage_tokens/${portfolioId?.stringValue}") {
val accountId: AccountId? = null,
) : AppRoute(path = "${source.name.lowercase()}/manage_tokens/${accountId?.value}") {
/**
* Source of launching the screen.

View file

@ -7,10 +7,6 @@ import com.tangem.blockchainsdk.utils.fromNetworkId
import com.tangem.blockchainsdk.utils.toBlockchain
import com.tangem.blockchainsdk.utils.toNetworkId
import com.tangem.data.common.account.WalletAccountsFetcher
import com.tangem.data.common.api.safeApiCall
import com.tangem.data.common.currency.CardCryptoCurrencyFactory
import com.tangem.data.common.currency.UserTokensResponseFactory
import com.tangem.data.common.currency.UserTokensSaver
import com.tangem.data.common.network.NetworkFactory
import com.tangem.data.common.utils.retryOnError
import com.tangem.data.managetokens.utils.ManageTokensUpdateFetcher
@ -22,7 +18,6 @@ import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
import com.tangem.datasource.api.tangemTech.models.orDefault
import com.tangem.datasource.local.config.testnet.TestnetTokensStorage
import com.tangem.datasource.local.token.UserTokensResponseStore
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
import com.tangem.domain.card.common.TapWorkarounds.isTestCard
import com.tangem.domain.card.common.extensions.*
import com.tangem.domain.card.common.util.cardTypesResolver
@ -48,24 +43,19 @@ import com.tangem.utils.coroutines.runSuspendCatching
internal class DefaultManageTokensRepository(
private val tangemTechApi: TangemTechApi,
private val userWalletsListRepository: UserWalletsListRepository,
private val userTokenSaver: UserTokensSaver,
private val manageTokensUpdateFetcher: ManageTokensUpdateFetcher,
private val userTokensResponseStore: UserTokensResponseStore,
private val testnetTokensStorage: TestnetTokensStorage,
private val excludedBlockchains: ExcludedBlockchains,
private val cardCryptoCurrencyFactory: CardCryptoCurrencyFactory,
private val dispatchers: CoroutineDispatcherProvider,
private val walletAccountsFetcher: WalletAccountsFetcher,
private val accountsFeatureToggles: AccountsFeatureToggles,
networkFactory: NetworkFactory,
) : ManageTokensRepository {
private val managedCryptoCurrencyFactory = ManagedCryptoCurrencyFactory(
networkFactory = networkFactory,
excludedBlockchains = excludedBlockchains,
accountsFeatureToggles = accountsFeatureToggles,
)
private val userTokensResponseFactory = UserTokensResponseFactory()
// region getTokenListBatchFlow
override fun getTokenListBatchFlow(
@ -92,10 +82,7 @@ internal class DefaultManageTokensRepository(
val userWallet = request.params.userWalletId?.let(userWalletsListRepository::getSyncStrict)
if (userWallet is UserWallet.Cold && userWallet.scanResponse.card.isTestCard) {
when (val params = request.params) {
is ManageTokensListConfig.Account -> fetchTestnetCurrencies(userWallet, params)
is ManageTokensListConfig.Wallet -> fetchTestnetCurrenciesLegacy(userWallet, params)
}
fetchTestnetCurrencies(userWallet, request.params)
} else {
fetchCurrencies(
userWallet = userWallet,
@ -139,24 +126,14 @@ internal class DefaultManageTokensRepository(
coins = coinsResponse.coins.filterNot { l2BlockchainsCoinIds.contains(it.id) },
)
val items = when (val params = request.params) {
is ManageTokensListConfig.Account -> createManagedCryptoCurrencyList(
params = params,
userWallet = userWallet,
isFirstBatchFetching = isFirstBatchFetching,
loadUserTokensFromRemote = loadUserTokensFromRemote,
query = query,
updatedCoinsResponse = updatedCoinsResponse,
)
is ManageTokensListConfig.Wallet -> createManagedCryptoCurrencyListLegacy(
params = params,
userWallet = userWallet,
isFirstBatchFetching = isFirstBatchFetching,
loadUserTokensFromRemote = loadUserTokensFromRemote,
query = query,
updatedCoinsResponse = updatedCoinsResponse,
)
}
val items = createManagedCryptoCurrencyList(
params = request.params,
userWallet = userWallet,
isFirstBatchFetching = isFirstBatchFetching,
loadUserTokensFromRemote = loadUserTokensFromRemote,
query = query,
updatedCoinsResponse = updatedCoinsResponse,
)
return BatchFetchResult.Success(
data = items,
@ -167,7 +144,7 @@ internal class DefaultManageTokensRepository(
@Suppress("CyclomaticComplexMethod")
private suspend fun createManagedCryptoCurrencyList(
params: ManageTokensListConfig.Account,
params: ManageTokensListConfig,
userWallet: UserWallet?,
isFirstBatchFetching: Boolean,
loadUserTokensFromRemote: Boolean,
@ -238,56 +215,9 @@ internal class DefaultManageTokensRepository(
return items
}
private suspend fun createManagedCryptoCurrencyListLegacy(
params: ManageTokensListConfig.Wallet,
userWallet: UserWallet?,
isFirstBatchFetching: Boolean,
loadUserTokensFromRemote: Boolean,
query: String?,
updatedCoinsResponse: CoinsResponse,
): List<ManagedCryptoCurrency> {
val tokensResponse = params.userWalletId?.let { userWalletId ->
if (loadUserTokensFromRemote && userWallet != null) {
safeApiCall({ tangemTechApi.getUserTokens(userWalletId.stringValue).bind() }) {
// save tokens response only if loadUserTokensFromRemote is true and it means onboarding call
createAndSaveDefaultUserTokensResponse(userWallet = userWallet)
}
} else {
getSavedUserTokensResponseSync(userWalletId)
}
}
val isCreateWithCustom = isFirstBatchFetching &&
tokensResponse != null &&
userWallet != null &&
query == null
return if (isCreateWithCustom) {
managedCryptoCurrencyFactory.createWithCustomTokens(
coinsResponse = updatedCoinsResponse,
tokensResponse = tokensResponse,
userWallet = userWallet,
accountIndex = DerivationIndex.Main,
)
} else {
managedCryptoCurrencyFactory.create(
coinsResponse = updatedCoinsResponse,
tokensResponse = tokensResponse,
userWallet = userWallet,
accountIndex = DerivationIndex.Main,
)
}
}
private suspend fun createAndSaveDefaultUserTokensResponse(userWallet: UserWallet): UserTokensResponse {
val userTokensResponse = createDefaultUserTokensResponse(userWallet)
userTokenSaver.store(userWallet.walletId, userTokensResponse, useEnricher = false)
return userTokensResponse
}
private suspend fun fetchTestnetCurrencies(
userWallet: UserWallet,
params: ManageTokensListConfig.Account,
params: ManageTokensListConfig,
): BatchFetchResult.Success<List<ManagedCryptoCurrency>> {
val searchText = params.searchText
val testnetTokensConfig = testnetTokensStorage.getConfig()
@ -342,45 +272,6 @@ internal class DefaultManageTokensRepository(
)
}
private suspend fun fetchTestnetCurrenciesLegacy(
userWallet: UserWallet,
params: ManageTokensListConfig.Wallet,
): BatchFetchResult.Success<List<ManagedCryptoCurrency>> {
val searchText = params.searchText
val testnetTokensConfig = testnetTokensStorage.getConfig()
val items = managedCryptoCurrencyFactory.createTestnetWithCustomTokens(
testnetTokensConfig = if (!searchText.isNullOrBlank()) {
testnetTokensConfig.copy(
tokens = testnetTokensConfig.tokens.filter { token ->
token.symbol.contains(other = searchText, ignoreCase = true) ||
token.name.contains(other = searchText, ignoreCase = true)
},
)
} else {
testnetTokensConfig
},
tokensResponse = getSavedUserTokensResponseSync(userWallet.walletId),
userWallet = userWallet,
accountIndex = DerivationIndex.Main,
)
return BatchFetchResult.Success(
data = items,
empty = items.isEmpty(),
last = true,
)
}
private fun createDefaultUserTokensResponse(userWallet: UserWallet) =
userTokensResponseFactory.createUserTokensResponse(
currencies = cardCryptoCurrencyFactory.createDefaultCoinsForMultiCurrencyWallet(
userWallet = userWallet,
),
isGroupedByNetwork = false,
isSortedByBalance = false,
)
private fun getSupportedBlockchains(userWallet: UserWallet?): List<Blockchain> {
return userWallet?.supportedBlockchains(excludedBlockchains)
?: Blockchain.entries.filter {

View file

@ -2,8 +2,6 @@ package com.tangem.data.managetokens.di
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
import com.tangem.data.common.account.WalletAccountsFetcher
import com.tangem.data.common.currency.CardCryptoCurrencyFactory
import com.tangem.data.common.currency.UserTokensSaver
import com.tangem.data.common.network.NetworkFactory
import com.tangem.data.managetokens.DefaultCustomTokensRepository
import com.tangem.data.managetokens.DefaultManageTokensRepository
@ -11,7 +9,6 @@ import com.tangem.data.managetokens.utils.ManageTokensUpdateFetcher
import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.local.config.testnet.TestnetTokensStorage
import com.tangem.datasource.local.token.UserTokensResponseStore
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
import com.tangem.domain.common.wallets.UserWalletsListRepository
import com.tangem.domain.managetokens.repository.CustomTokensRepository
import com.tangem.domain.managetokens.repository.ManageTokensRepository
@ -33,13 +30,10 @@ internal object ManageTokensDataModule {
userWalletsListRepository: UserWalletsListRepository,
manageTokensUpdateFetcher: ManageTokensUpdateFetcher,
userTokensResponseStore: UserTokensResponseStore,
userTokensSaver: UserTokensSaver,
testnetTokensStorage: TestnetTokensStorage,
dispatchers: CoroutineDispatcherProvider,
excludedBlockchains: ExcludedBlockchains,
cardCryptoCurrencyFactory: CardCryptoCurrencyFactory,
networkFactory: NetworkFactory,
accountsFeatureToggles: AccountsFeatureToggles,
walletAccountsFetcher: WalletAccountsFetcher,
): ManageTokensRepository {
return DefaultManageTokensRepository(
@ -47,13 +41,10 @@ internal object ManageTokensDataModule {
userWalletsListRepository = userWalletsListRepository,
manageTokensUpdateFetcher = manageTokensUpdateFetcher,
userTokensResponseStore = userTokensResponseStore,
userTokenSaver = userTokensSaver,
testnetTokensStorage = testnetTokensStorage,
excludedBlockchains = excludedBlockchains,
cardCryptoCurrencyFactory = cardCryptoCurrencyFactory,
networkFactory = networkFactory,
dispatchers = dispatchers,
accountsFeatureToggles = accountsFeatureToggles,
walletAccountsFetcher = walletAccountsFetcher,
)
}

View file

@ -13,7 +13,6 @@ import com.tangem.data.common.network.NetworkFactory
import com.tangem.datasource.api.tangemTech.models.CoinsResponse
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
import com.tangem.datasource.local.config.testnet.models.TestnetTokensConfig
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
import com.tangem.domain.card.common.extensions.canHandleToken
import com.tangem.domain.managetokens.model.ManagedCryptoCurrency
import com.tangem.domain.managetokens.model.ManagedCryptoCurrency.SourceNetwork
@ -21,7 +20,6 @@ import com.tangem.domain.models.account.DerivationIndex
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.network.Network
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.wallets.derivations.DerivationStyleProvider
import com.tangem.domain.wallets.derivations.derivationStyleProvider
import com.tangem.lib.crypto.BlockchainUtils
import timber.log.Timber
@ -29,7 +27,6 @@ import timber.log.Timber
internal class ManagedCryptoCurrencyFactory(
private val networkFactory: NetworkFactory,
private val excludedBlockchains: ExcludedBlockchains,
private val accountsFeatureToggles: AccountsFeatureToggles,
) {
fun create(
@ -120,30 +117,15 @@ internal class ManagedCryptoCurrencyFactory(
?.takeUnless { it in excludedBlockchains }
?: return null
val network = if (accountsFeatureToggles.isFeatureEnabled) {
val network = networkFactory.create(
blockchain = blockchain,
extraDerivationPath = token.derivationPath,
userWallet = userWallet,
accountIndex = accountIndex,
) ?: return null
val network = networkFactory.create(
blockchain = blockchain,
extraDerivationPath = token.derivationPath,
userWallet = userWallet,
accountIndex = accountIndex,
) ?: return null
if (!checkIsCustomToken(token, network.derivationPath)) {
return null
}
network
} else {
if (!checkIsCustomToken(token, blockchain, userWallet.derivationStyleProvider)) {
return null
}
networkFactory.create(
blockchain = blockchain,
extraDerivationPath = token.derivationPath,
userWallet = userWallet,
accountIndex = accountIndex,
) ?: return null
if (!checkIsCustomToken(token, network.derivationPath)) {
return null
}
val contractAddress = token.contractAddress
@ -285,23 +267,10 @@ internal class ManagedCryptoCurrencyFactory(
return "${imageHost ?: DEFAULT_IMAGE_HOST}large/$id.png"
}
private fun checkIsCustomToken(
token: UserTokensResponse.Token,
blockchain: Blockchain,
derivationStyleProvider: DerivationStyleProvider,
): Boolean = token.id.isNullOrBlank() ||
checkIsCustomDerivationPath(token.derivationPath, blockchain, derivationStyleProvider)
private fun checkIsCustomToken(token: UserTokensResponse.Token, derivationPath: Network.DerivationPath): Boolean {
return token.id.isNullOrBlank() || derivationPath is Network.DerivationPath.Custom
}
private fun checkIsCustomDerivationPath(
derivationPath: String?,
blockchain: Blockchain,
derivationStyleProvider: DerivationStyleProvider,
): Boolean = derivationPath != blockchain.derivationPath(derivationStyleProvider.getDerivationStyle())?.rawPath
/**
* Filter tokens for TerraV1 (Terra Classic) network.
* Only native coin (LUNC) and TerraClassicUSD (USTC) are allowed.

View file

@ -3,24 +3,10 @@ package com.tangem.domain.managetokens.model
import com.tangem.domain.models.account.AccountId
import com.tangem.domain.models.wallet.UserWalletId
sealed interface ManageTokensListConfig {
data class ManageTokensListConfig(
val accountId: AccountId?,
val searchText: String?,
) {
val userWalletId: UserWalletId?
val searchText: String?
// old way
data class Wallet(
override val userWalletId: UserWalletId?,
override val searchText: String?,
) : ManageTokensListConfig
// new way
data class Account(
val accountId: AccountId?,
override val searchText: String?,
) : ManageTokensListConfig {
override val userWalletId: UserWalletId?
get() = accountId?.userWalletId
}
get() = accountId?.userWalletId
}

View file

@ -24,7 +24,6 @@ import com.tangem.domain.account.models.AccountList
import com.tangem.domain.account.usecase.AddCryptoPortfolioUseCase
import com.tangem.domain.account.usecase.GetUnoccupiedAccountIndexUseCase
import com.tangem.domain.account.usecase.UpdateCryptoPortfolioUseCase
import com.tangem.domain.models.PortfolioId
import com.tangem.domain.models.account.CryptoPortfolioIcon
import com.tangem.domain.models.account.DerivationIndex
import com.tangem.domain.models.wallet.UserWalletId
@ -141,7 +140,7 @@ internal class AccountCreateEditModel @Inject constructor(
showMessage(R.string.account_create_success_message)
val route = AppRoute.ManageTokens(
source = AppRoute.ManageTokens.Source.ACCOUNT,
portfolioId = PortfolioId(account.accountId),
accountId = account.accountId,
)
router.replaceCurrent(route)
}

View file

@ -16,7 +16,6 @@ import com.tangem.core.ui.message.EventMessageAction
import com.tangem.core.ui.message.ToastMessage
import com.tangem.domain.account.status.usecase.ArchiveCryptoPortfolioUseCase
import com.tangem.domain.account.supplier.SingleAccountSupplier
import com.tangem.domain.models.PortfolioId
import com.tangem.domain.models.account.Account
import com.tangem.domain.models.wallet.isMultiCurrency
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
@ -65,7 +64,7 @@ internal class AccountDetailsModel @Inject constructor(
private fun onManageTokensClick(account: Account.CryptoPortfolio) {
val route = AppRoute.ManageTokens(
source = AppRoute.ManageTokens.Source.ACCOUNT,
portfolioId = PortfolioId(account.accountId),
accountId = account.accountId,
)
analyticsEventHandler.send(
AccountSettingsAnalyticEvents.ButtonManageTokens(account.derivationIndex.value),

View file

@ -1,6 +1,7 @@
plugins {
alias(deps.plugins.android.library)
alias(deps.plugins.kotlin.android)
alias(deps.plugins.kotlin.serialization)
id("configuration")
}

View file

@ -2,6 +2,7 @@ package com.tangem.features.managetokens.component
import com.tangem.domain.models.account.AccountId
import com.tangem.domain.models.wallet.UserWalletId
import kotlinx.serialization.Serializable
enum class ManageTokensSource(val analyticsName: String) {
STORIES(analyticsName = "Stories"),
@ -12,18 +13,15 @@ enum class ManageTokensSource(val analyticsName: String) {
}
sealed interface ManageTokensMode {
data class Wallet(val userWalletId: UserWalletId) : ManageTokensMode
data class Account(val accountId: AccountId) : ManageTokensMode
data class Account(val accountId: AccountId) : ManageTokensMode {
constructor(userWalletId: UserWalletId) : this(AccountId.forMainCryptoPortfolio(userWalletId))
}
data object None : ManageTokensMode
}
sealed interface AddCustomTokenMode {
@Serializable
data class AddCustomTokenMode(val accountId: AccountId) {
val userWalletId: UserWalletId = accountId.userWalletId
val userWalletId: UserWalletId
data class Wallet(override val userWalletId: UserWalletId) : AddCustomTokenMode
data class Account(val accountId: AccountId) : AddCustomTokenMode {
override val userWalletId: UserWalletId = accountId.userWalletId
}
constructor(userWalletId: UserWalletId) : this(AccountId.forMainCryptoPortfolio(userWalletId))
}

View file

@ -4,47 +4,21 @@
<CurrentIssues>
<ID>BooleanPropertyNaming:AddCustomTokenUM.kt$AddCustomTokenUM$val showBackButton: Boolean</ID>
<ID>BooleanPropertyNaming:CustomCurrencyValidator.kt$CustomCurrencyValidator.Status.Validated$val fillForm: Boolean</ID>
<ID>BooleanPropertyNaming:CustomTokenFormModel.kt$CustomTokenFormModel$val needColdWalletInteraction = coldWalletAndHasMissedDerivationsUseCase.invoke( userWalletId = params.mode.userWalletId, networksWithDerivationPath = mapOf(currency.network.backendId to getDerivationPath().value), )</ID>
<ID>BooleanPropertyNaming:ManageTokensListManager.kt$ManageTokensListManager$val loadUserTokensFromRemote = when (mode) { is ManageTokensMode.Wallet, is ManageTokensMode.Account, -&gt; source == ManageTokensSource.ONBOARDING ManageTokensMode.None, -&gt; false }</ID>
<ID>BooleanPropertyNaming:ManageTokensModel.kt$ManageTokensModel$val needToInteractWithColdWallet = useCasesFacade.needColdWalletInteraction(networks)</ID>
<ID>BooleanPropertyNaming:ManageTokensUM.kt$ManageTokensUM.ManageContent$val needToInteractWithColdWallet: Boolean</ID>
<ID>BooleanPropertyNaming:OnboardingManageTokensModel.kt$OnboardingManageTokensModel$val showTangemIcon = useCasesFacade.needColdWalletInteraction(network = network)</ID>
<ID>BooleanPropertyNaming:OnboardingManageTokensUM.kt$OnboardingManageTokensUM.ActionButtonConfig$abstract val showProgress: Boolean</ID>
<ID>BooleanPropertyNaming:OnboardingManageTokensUM.kt$OnboardingManageTokensUM.ActionButtonConfig.Continue$val showTangemIcon: Boolean</ID>
<ID>MaxChainedCallsOnSameLine:ChooseManagedTokensModel.kt$ChooseManagedTokensModel$params.initialCurrency.network.id.rawId.value</ID>
<ID>MultilineLambdaItParameter:ChooseManagedTokenContent.kt${ add( CurrencyItemUM.Basic( id = ManagedCryptoCurrency.ID( value = "ID+$it", ), name = "Bitcoin", symbol = "BTC", icon = CurrencyIconState.Loading, networks = CurrencyItemUM.Basic.NetworksUM.Collapsed, onExpandClick = {}, ), ) }</ID>
<ID>MultilineLambdaItParameter:ChooseManagedTokensModel.kt$ChooseManagedTokensModel${ it.copy( notificationUM = null, ) }</ID>
<ID>MultilineLambdaItParameter:CurrencyItemMapper.kt${ it.toCurrencyNetworkModel( isSelected = it.network in addedIn, isEditable = false, onSelectedStateChange = { _, _ -&gt; }, onLongTap = { _ -&gt; }, ) }</ID>
<ID>MultilineLambdaItParameter:CustomCurrencyFormOperations.kt${ it[Field.CONTRACT_ADDRESS] = it.getValue(Field.CONTRACT_ADDRESS).copy( error = when (exception) { CustomTokenFormValidationException.ContractAddress.Invalid -&gt; { resourceReference(R.string.custom_token_creation_error_invalid_contract_address) } }, ) }</ID>
<ID>MultilineLambdaItParameter:CustomCurrencyFormOperations.kt${ it[Field.DECIMALS] = it.getValue(Field.DECIMALS).copy( error = when (exception) { is CustomTokenFormValidationException.Decimals.Empty -&gt; { null // Should not display this error } is CustomTokenFormValidationException.Decimals.Invalid -&gt; { resourceReference( R.string.custom_token_creation_error_wrong_decimals, wrappedList(ValidateTokenFormUseCase.MAX_DECIMALS), ) } }, ) }</ID>
<ID>MultilineLambdaItParameter:CustomTokenFormContent.kt$PreviewCustomTokenFormComponentProvider${ it[Field.CONTRACT_ADDRESS] = it[Field.CONTRACT_ADDRESS]!!.copy( label = stringReference("Contract address"), value = "0x1234567890", error = stringReference("Contract address is invalid"), placeholder = stringReference("0x1234567890"), ) }</ID>
<ID>MultilineLambdaItParameter:CustomTokenFormContent.kt$PreviewCustomTokenFormComponentProvider${ it[Field.CONTRACT_ADDRESS] = it[Field.CONTRACT_ADDRESS]!!.copy( label = stringReference("Contract address"), value = "0x1234567890", placeholder = stringReference("0x1234567890"), ) }</ID>
<ID>MultilineLambdaItParameter:CustomTokenFormModel.kt$CustomTokenFormModel${ Timber.e(it, "Failed to add currency") showErrorDialog() return@resource }</ID>
<ID>MultilineLambdaItParameter:CustomTokenFormModel.kt$CustomTokenFormModel${ Timber.e(it, "Failed to derive public keys") showErrorDialog() return@resource }</ID>
<ID>MultilineLambdaItParameter:CustomTokenSelectorModel.kt$CustomTokenSelectorModel${ when (it) { is AccountStatus.CryptoPortfolio -&gt; it.sameNodeAndNotMain() } }</ID>
<ID>MultilineLambdaItParameter:ManageTokensListManager.kt$ManageTokensListManager${ Timber.e( it, """ Failed to check currency unsupported state |- Mode: $mode |- Source Network: $sourceNetwork """.trimIndent(), ) val message = SnackbarMessage( message = it.localizedMessage ?.let(::stringReference) ?: resourceReference(R.string.common_error), ) messageSender.send(message) null }</ID>
<ID>MultilineLambdaItParameter:ManageTokensListManager.kt$ManageTokensListManager${ Timber.e( it, """ Failed to check linked tokens |- Mode: $mode |- Network ID: ${network.id} """.trimIndent(), ) val message = SnackbarMessage( message = it.localizedMessage ?.let(::stringReference) ?: resourceReference(R.string.common_error), ) messageSender.send(message) false }</ID>
<ID>MultilineLambdaItParameter:ManageTokensModel.kt$ManageTokensModel${ Timber.e(it, "Failed to save changes") return@resource }</ID>
<ID>MultilineLambdaItParameter:ManageTokensUseCasesFacade.kt$ManageTokensUseCasesFacade${ it is CryptoCurrency.Token &amp;&amp; it.network.backendId == network.backendId &amp;&amp; it.network.derivationPath == network.derivationPath }</ID>
<ID>MultilineLambdaItParameter:OnboardingManageTokensModel.kt$OnboardingManageTokensModel${ Timber.e(it, "Failed to save changes") return@resource }</ID>
<ID>MultilineLambdaItParameter:PreviewManageTokensComponent.kt$PreviewManageTokensComponent${ it.fastForEachIndexed { index, network -&gt; if (index == networkIndex) { it[index] = network.copy( iconResId = if (isSelected) { R.drawable.img_eth_22 } else { R.drawable.ic_eth_16 }, isSelected = isSelected, ) } } }</ID>
<ID>NamedArguments:ChangedCurrenciesManager.kt$ChangedCurrenciesManager$updateChangedItems(currency, network, currenciesToAdd, currenciesToRemove)</ID>
<ID>NamedArguments:ChangedCurrenciesManager.kt$ChangedCurrenciesManager$updateChangedItems(currency, network, currenciesToRemove, currenciesToAdd)</ID>
<ID>NamedArguments:ChooseManagedTokensModel.kt$ChooseManagedTokensModel$TokenSearched( params.analyticsCategoryName, token = null, blockchain = null, isTokenChosen = false, )</ID>
<ID>NamedArguments:ManageTokensListManager.kt$ManageTokensListManager$selectNetwork(currencyBatch.key, currency, networkId, isSelected)</ID>
<ID>NamedArguments:ManageTokensListManager.kt$ManageTokensListManager$sendSelectCurrencyAction(batchKey, currency.id, network, isSelected = false)</ID>
<ID>NamedArguments:ManageTokensListManager.kt$ManageTokensListManager$sendSelectCurrencyAction(batchKey, currency.id, network, isSelected = true)</ID>
<ID>NestedScopeFunctions:CustomTokenSelectorModel.kt$CustomTokenSelectorModel$let { recognizer.recognize(it) }</ID>
<ID>NullableBooleanCheck:CustomCurrencyFormOperations.kt$this.tokenForm?.wasFilled ?: false</ID>
<ID>PropertyUsedBeforeDeclaration:ChooseManagedTokensModel.kt$ChooseManagedTokensModel$bottomSheetNavigation</ID>
<ID>PropertyUsedBeforeDeclaration:ChooseManagedTokensModel.kt$ChooseManagedTokensModel$uiState</ID>
<ID>UnsafeCallOnNullableType:CustomTokenFormContent.kt$PreviewCustomTokenFormComponentProvider$it[Field.CONTRACT_ADDRESS]!!</ID>
<ID>UnsafeCallOnNullableType:PreviewAddCustomTokenComponent.kt$PreviewAddCustomTokenComponent$config.selectedDerivationPath!!</ID>
<ID>UnsafeCallOnNullableType:PreviewAddCustomTokenComponent.kt$PreviewAddCustomTokenComponent$config.selectedNetwork!!</ID>
<ID>UseEmptyCounterpart:CustomTokenAnalyticsEvent.kt$CustomTokenAnalyticsEvent$mapOf()</ID>
<ID>UseEmptyCounterpart:ManageTokensAnalyticEvent.kt$ManageTokensAnalyticEvent$mapOf()</ID>
<ID>UseOrEmpty:ChangedCurrenciesManager.kt$ChangedCurrenciesManager$items[currency] ?: emptySet()</ID>
<ID>UseOrEmpty:PreviewCustomTokenSelectorComponent.kt$PreviewCustomTokenSelectorComponent$d.id?.rawId?.value ?: ""</ID>
<ID>VarCouldBeVal:CustomTokenFormModel.kt$CustomTokenFormModel$private var useCasesFacade: CustomTokenFormUseCasesFacade = customTokenFormUseCasesFacadeFactory.create(params.mode)</ID>
</CurrentIssues>
</SmellBaseline>

View file

@ -17,8 +17,6 @@ import com.tangem.core.ui.event.triggeredEvent
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.message.SnackbarMessage
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
import com.tangem.domain.models.account.AccountId
import com.tangem.domain.notifications.SetShouldShowNotificationUseCase
import com.tangem.features.managetokens.choosetoken.entity.ChooseManageTokensBottomSheetConfig
import com.tangem.features.managetokens.choosetoken.entity.ChooseManagedTokenUM
@ -45,7 +43,6 @@ import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
import kotlin.collections.isNotEmpty
@Suppress("LongParameterList")
@ModelScoped
@ -55,7 +52,6 @@ internal class ChooseManagedTokensModel @Inject constructor(
private val uiMessageSender: UiMessageSender,
private val setShouldShowNotificationUseCase: SetShouldShowNotificationUseCase,
private val analyticsEventHandler: AnalyticsEventHandler,
accountsFeatureToggles: AccountsFeatureToggles,
paramsContainer: ParamsContainer,
manageTokensUseCasesFacadeFactory: ManageTokensUseCasesFacade.Factory,
manageTokensListManagerFactory: ManageTokensListManager.Factory,
@ -63,16 +59,15 @@ internal class ChooseManagedTokensModel @Inject constructor(
private val params: ChooseManagedTokensComponent.Params = paramsContainer.require()
private val manageTokensMode = if (accountsFeatureToggles.isFeatureEnabled) {
val accountId = AccountId.forMainCryptoPortfolio(userWalletId = params.userWalletId)
ManageTokensMode.Account(accountId = accountId)
} else {
ManageTokensMode.Wallet(params.userWalletId)
}
private val manageTokensMode = ManageTokensMode.Account(params.userWalletId)
private val useCasesFacade: ManageTokensUseCasesFacade = manageTokensUseCasesFacadeFactory
.create(mode = manageTokensMode)
val bottomSheetNavigation: SlotNavigation<ChooseManageTokensBottomSheetConfig> = SlotNavigation()
val uiState: StateFlow<ChooseManagedTokenUM>
field = MutableStateFlow<ChooseManagedTokenUM>(createReadContentModel())
private val manageTokensListManager = manageTokensListManagerFactory.create(
scope = modelScope,
source = ManageTokensSource.SEND_VIA_SWAP,
@ -91,10 +86,6 @@ internal class ChooseManagedTokensModel @Inject constructor(
},
)
val bottomSheetNavigation: SlotNavigation<ChooseManageTokensBottomSheetConfig> = SlotNavigation()
val uiState: StateFlow<ChooseManagedTokenUM>
field = MutableStateFlow<ChooseManagedTokenUM>(createReadContentModel())
init {
manageTokensListManager.uiItems
.onEach { items -> updateItems(items) }
@ -146,11 +137,7 @@ internal class ChooseManagedTokensModel @Inject constructor(
private fun removeNotification() {
modelScope.launch {
setShouldShowNotificationUseCase(NotificationId.SendViaSwapTokenSelectorNotification.key, false)
uiState.update {
it.copy(
notificationUM = null,
)
}
uiState.update { it.copy(notificationUM = null) }
}
}
@ -161,7 +148,7 @@ internal class ChooseManagedTokensModel @Inject constructor(
if (!new.readContent.search.isActive && old.readContent.search.isActive) {
analyticsEventHandler.send(
CommonManageTokensAnalyticEvents.TokenSearched(
params.analyticsCategoryName,
categoryName = params.analyticsCategoryName,
token = null,
blockchain = null,
isTokenChosen = false,
@ -195,8 +182,9 @@ internal class ChooseManagedTokensModel @Inject constructor(
val isToken = currency.id.value == params.initialCurrency.id.rawCurrencyId?.value
// Ensure that initial token network is filtered out and network list is empty
val paramsRawId = params.initialCurrency.network.id.rawId
val isEmptyNetworks = availableNetworks?.networks?.filterNot { network ->
network.id == params.initialCurrency.network.id.rawId.value
network.id == paramsRawId.value
}.isNullOrEmpty()
// Filter out currency from display

View file

@ -16,11 +16,11 @@ import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.core.ui.message.SnackbarMessage
import com.tangem.domain.models.account.Account
import com.tangem.domain.models.account.AccountId
import com.tangem.domain.models.account.AccountName
import com.tangem.features.managetokens.component.AddCustomTokenComponent
import com.tangem.features.managetokens.component.AddCustomTokenMode
import com.tangem.features.managetokens.component.ManageTokensComponent
import com.tangem.features.managetokens.entity.managetokens.ManageTokensBottomSheetConfig
import com.tangem.features.managetokens.impl.R
import com.tangem.features.managetokens.model.ManageTokensModel
import com.tangem.features.managetokens.ui.ManageTokensScreen
@ -38,7 +38,7 @@ internal class DefaultManageTokensComponent @AssistedInject constructor(
private val bottomSheetSlot = childSlot(
source = model.bottomSheetNavigation,
serializer = ManageTokensBottomSheetConfig.serializer(),
serializer = AccountId.serializer(),
handleBackButton = false,
childFactory = ::bottomSheetChild,
)
@ -57,13 +57,10 @@ internal class DefaultManageTokensComponent @AssistedInject constructor(
}
private fun bottomSheetChild(
config: ManageTokensBottomSheetConfig,
accountId: AccountId,
componentContext: ComponentContext,
): ComposableBottomSheetComponent {
val mode = when (config) {
is ManageTokensBottomSheetConfig.AddWalletCustomToken -> AddCustomTokenMode.Wallet(config.userWalletId)
is ManageTokensBottomSheetConfig.AddAccountCustomToken -> AddCustomTokenMode.Account(config.accountId)
}
val mode = AddCustomTokenMode(accountId)
return addCustomTokenComponentFactory.create(
context = childByContext(componentContext),
params = AddCustomTokenComponent.Params(

View file

@ -15,7 +15,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
internal class PreviewAddCustomTokenComponent(
initialState: AddCustomTokenConfig = AddCustomTokenConfig(
mode = AddCustomTokenMode.Wallet(UserWalletId(stringValue = "321")),
mode = AddCustomTokenMode(UserWalletId(stringValue = "321")),
step = AddCustomTokenConfig.Step.INITIAL_NETWORK_SELECTOR,
),
) : AddCustomTokenComponent {
@ -61,8 +61,8 @@ internal class PreviewAddCustomTokenComponent(
PreviewCustomTokenSelectorComponent(
params = CustomTokenSelectorComponent.Params.DerivationPathSelector(
mode = config.mode,
selectedNetwork = config.selectedNetwork!!,
selectedDerivationPath = config.selectedDerivationPath!!,
selectedNetwork = requireNotNull(config.selectedNetwork),
selectedDerivationPath = requireNotNull(config.selectedDerivationPath),
onDerivationPathSelected = { _, _ -> },
),
).Content(modifier)

View file

@ -19,7 +19,7 @@ import kotlinx.collections.immutable.toImmutableList
internal class PreviewCustomTokenSelectorComponent(
private val params: Params = Params.NetworkSelector(
mode = AddCustomTokenMode.Wallet(UserWalletId(stringValue = "321")),
mode = AddCustomTokenMode(UserWalletId(stringValue = "321")),
selectedNetwork = null,
onNetworkSelected = {},
),
@ -39,7 +39,7 @@ internal class PreviewCustomTokenSelectorComponent(
)
DerivationPathUM(
id = d.id?.rawId?.value ?: "",
id = d.id?.rawId?.value.orEmpty(),
value = d.value.value.orEmpty(),
networkName = stringReference(d.name),
isSelected = d.value == params.selectedDerivationPath?.value,

View file

@ -40,9 +40,7 @@ internal class PreviewManageTokensComponent(
popBack = {},
items = items,
topBar = when (params.mode) {
is ManageTokensMode.Account,
is ManageTokensMode.Wallet,
-> ManageTokensTopBarUM.ManageContent(
is ManageTokensMode.Account -> ManageTokensTopBarUM.ManageContent(
title = resourceReference(id = R.string.main_manage_tokens),
onBackButtonClick = {},
endButton = TopAppBarButtonUM.Icon(
@ -206,10 +204,10 @@ internal class PreviewManageTokensComponent(
is CurrencyItemUM.Basic -> {
val updatedNetworks = (item.networks as? CurrencyItemUM.Basic.NetworksUM.Expanded)
?.copy(
networks = item.networks.networks.toPersistentList().mutate {
it.fastForEachIndexed { index, network ->
networks = item.networks.networks.toPersistentList().mutate { networks ->
networks.fastForEachIndexed { index, network ->
if (index == networkIndex) {
it[index] = network.copy(
networks[index] = network.copy(
iconResId = if (isSelected) {
R.drawable.img_eth_22
} else {

View file

@ -1,19 +0,0 @@
package com.tangem.features.managetokens.entity.managetokens
import com.tangem.domain.models.account.AccountId
import com.tangem.domain.models.wallet.UserWalletId
import kotlinx.serialization.Serializable
@Serializable
internal sealed class ManageTokensBottomSheetConfig {
@Serializable
data class AddWalletCustomToken(
val userWalletId: UserWalletId,
) : ManageTokensBottomSheetConfig()
@Serializable
data class AddAccountCustomToken(
val accountId: AccountId,
) : ManageTokensBottomSheetConfig()
}

View file

@ -179,7 +179,7 @@ internal class CustomTokenFormModel @Inject constructor(
isAlreadyAdded: Boolean,
isCustom: Boolean,
) = modelScope.launch {
val needColdWalletInteraction = coldWalletAndHasMissedDerivationsUseCase.invoke(
val isNeedColdWalletInteraction = coldWalletAndHasMissedDerivationsUseCase.invoke(
userWalletId = params.mode.userWalletId,
networksWithDerivationPath = mapOf(currency.network.backendId to getDerivationPath().value),
)
@ -193,7 +193,7 @@ internal class CustomTokenFormModel @Inject constructor(
clearNotifications = true,
clearFieldErrors = true,
disableSecondaryFields = !isCustom,
walletInteractionIcon = R.drawable.ic_tangem_24.takeIf { needColdWalletInteraction },
walletInteractionIcon = R.drawable.ic_tangem_24.takeIf { isNeedColdWalletInteraction },
)
if (fillForm) {
@ -353,14 +353,8 @@ internal class CustomTokenFormModel @Inject constructor(
)
analyticsEventHandler.send(event)
useCasesFacade.derivePublicKeysUseCase(listOf(currency)).getOrElse {
Timber.e(it, "Failed to derive public keys")
showErrorDialog()
return@resource
}
useCasesFacade.addCryptoCurrenciesUseCase(currency).getOrElse {
Timber.e(it, "Failed to add currency")
useCasesFacade.addCryptoCurrenciesUseCase(currency).getOrElse { throwable ->
Timber.e(throwable, "Failed to add currency")
showErrorDialog()
return@resource
}

View file

@ -15,7 +15,6 @@ import com.tangem.core.ui.extensions.wrappedList
import com.tangem.core.ui.message.DialogMessage
import com.tangem.core.ui.message.EventMessageAction
import com.tangem.core.ui.message.SnackbarMessage
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier
import com.tangem.domain.managetokens.GetSupportedNetworksUseCase
import com.tangem.domain.models.account.Account
@ -52,7 +51,6 @@ internal class CustomTokenSelectorModel @Inject constructor(
private val getSupportedNetworksUseCase: GetSupportedNetworksUseCase,
private val messageSender: UiMessageSender,
private val singleAccountStatusListSupplier: SingleAccountStatusListSupplier,
private val accountsFeatureToggles: AccountsFeatureToggles,
paramsContainer: ParamsContainer,
) : Model() {
@ -171,7 +169,7 @@ internal class CustomTokenSelectorModel @Inject constructor(
}
private suspend fun getSupportedNetworks(mode: AddCustomTokenMode): List<Network> {
return getSupportedNetworksUseCase(mode.userWalletId).getOrElse { e ->
return getSupportedNetworksUseCase(mode.userWalletId).getOrElse { _ ->
val message = SnackbarMessage(message = resourceReference(R.string.common_unknown_error))
messageSender.send(message)
@ -194,19 +192,23 @@ internal class CustomTokenSelectorModel @Inject constructor(
fun selectCustomDerivationPath(value: SelectedDerivationPath) {
when (params) {
is NetworkSelector -> return
is DerivationPathSelector -> if (accountsFeatureToggles.isFeatureEnabled) {
params.checkAccountDerivation(value)
} else {
params.onDerivationPathSelected(value, null)
}
is DerivationPathSelector -> params.checkAccountDerivation(value)
}
}
private fun DerivationPathSelector.checkAccountDerivation(derivationPath: SelectedDerivationPath) =
modelScope.launch {
val account = derivationPath.id
?.let { Blockchain.fromId(it.rawId.value) }?.let(::AccountNodeRecognizer)
?.let { recognizer -> derivationPath.value.value?.let { recognizer.recognize(it) } }
?.let { Blockchain.fromId(it.rawId.value) }
?.let(::AccountNodeRecognizer)
?.let { recognizer ->
val derivationPathValue = derivationPath.value.value
if (derivationPathValue != null) {
recognizer.recognize(derivationPathValue)
} else {
null
}
}
?.let { accountNode ->
fun AccountStatus.CryptoPortfolio.sameNodeAndNotMain() = !this.account.isMainAccount &&
this.account.derivationIndex.value.toLong() == accountNode

View file

@ -18,12 +18,12 @@ import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.message.SnackbarMessage
import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier
import com.tangem.domain.models.account.AccountId
import com.tangem.features.managetokens.analytics.CustomTokenAnalyticsEvent
import com.tangem.features.managetokens.analytics.ManageTokensAnalyticEvent
import com.tangem.features.managetokens.component.ManageTokensComponent
import com.tangem.features.managetokens.component.ManageTokensMode
import com.tangem.features.managetokens.entity.item.CurrencyItemUM
import com.tangem.features.managetokens.entity.managetokens.ManageTokensBottomSheetConfig
import com.tangem.features.managetokens.entity.managetokens.ManageTokensTopBarUM
import com.tangem.features.managetokens.entity.managetokens.ManageTokensUM
import com.tangem.features.managetokens.impl.R
@ -66,7 +66,7 @@ internal class ManageTokensModel @Inject constructor(
)
val state: MutableStateFlow<ManageTokensUM> = MutableStateFlow(getInitialState())
val bottomSheetNavigation: SlotNavigation<ManageTokensBottomSheetConfig> = SlotNavigation()
val bottomSheetNavigation: SlotNavigation<AccountId> = SlotNavigation()
init {
manageTokensListManager.uiItems
@ -101,15 +101,12 @@ internal class ManageTokensModel @Inject constructor(
analyticsEventHandler.send(ManageTokensAnalyticEvent.ScreenOpened(params.source))
return when (params.mode) {
is ManageTokensMode.Wallet,
is ManageTokensMode.Account,
-> createManageContentModel()
is ManageTokensMode.Account -> createManageContentModel()
ManageTokensMode.None -> createReadContentModel()
}
}
private fun getTopBarInitialState(): ManageTokensTopBarUM = when (params.mode) {
is ManageTokensMode.Wallet -> manageContentTopBar()
is ManageTokensMode.Account -> ManageTokensTopBarUM.ReadContent(
title = resourceReference(id = R.string.main_manage_tokens),
onBackButtonClick = router::pop,
@ -196,9 +193,7 @@ internal class ManageTokensModel @Inject constructor(
state.update { it.copySealed(topBar = manageContentTopBar()) }
}
}
ManageTokensMode.None,
is ManageTokensMode.Wallet,
-> Unit // use init state
ManageTokensMode.None -> Unit // use init state
}
}
@ -299,12 +294,12 @@ internal class ManageTokensModel @Inject constructor(
.flatten()
.toSet()
.associate { network -> network.backendId to network.derivationPath.value }
val needToInteractWithColdWallet = useCasesFacade.needColdWalletInteraction(networks)
val isNeedToInteractWithColdWallet = useCasesFacade.needColdWalletInteraction(networks)
state.update { state ->
state.copySealed(
hasChanges = currenciesToAdd.isNotEmpty() || currenciesToRemove.isNotEmpty(),
needToInteractWithColdWallet = needToInteractWithColdWallet,
needToInteractWithColdWallet = isNeedToInteractWithColdWallet,
)
}
}
@ -324,12 +319,8 @@ internal class ManageTokensModel @Inject constructor(
private fun navigateToAddCustomToken() {
analyticsEventHandler.send(CustomTokenAnalyticsEvent.ButtonCustomToken(params.source))
when (val portfolio = params.mode) {
is ManageTokensMode.Wallet ->
bottomSheetNavigation
.activate(ManageTokensBottomSheetConfig.AddWalletCustomToken(portfolio.userWalletId))
is ManageTokensMode.Account ->
bottomSheetNavigation
.activate(ManageTokensBottomSheetConfig.AddAccountCustomToken(portfolio.accountId))
bottomSheetNavigation.activate(portfolio.accountId)
ManageTokensMode.None -> Unit
}
}
@ -347,8 +338,8 @@ internal class ManageTokensModel @Inject constructor(
useCasesFacade.saveManagedTokensUseCase(
currenciesToAdd = manageTokensListManager.currenciesToAdd.value,
currenciesToRemove = manageTokensListManager.currenciesToRemove.value,
).getOrElse {
Timber.e(it, "Failed to save changes")
).getOrElse { throwable ->
Timber.e(throwable, "Failed to save changes")
return@resource
}

View file

@ -13,8 +13,6 @@ import com.tangem.core.ui.event.triggeredEvent
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.message.SnackbarMessage
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
import com.tangem.domain.models.account.AccountId
import com.tangem.domain.redux.OnboardingManageTokensAction
import com.tangem.domain.redux.ReduxStateHolder
import com.tangem.features.managetokens.analytics.ManageTokensAnalyticEvent
@ -45,18 +43,13 @@ internal class OnboardingManageTokensModel @Inject constructor(
private val messageSender: UiMessageSender,
private val reduxStateHolder: ReduxStateHolder,
private val analyticsEventHandler: AnalyticsEventHandler,
accountsFeatureToggles: AccountsFeatureToggles,
manageTokensListManagerFactory: ManageTokensListManager.Factory,
manageTokensUseCasesFacadeFactory: ManageTokensUseCasesFacade.Factory,
paramsContainer: ParamsContainer,
) : Model() {
private val params: OnboardingManageTokensComponent.Params = paramsContainer.require()
private val portfolio = if (accountsFeatureToggles.isFeatureEnabled) {
ManageTokensMode.Account(accountId = AccountId.forMainCryptoPortfolio(params.userWalletId))
} else {
ManageTokensMode.Wallet(params.userWalletId)
}
private val portfolio = ManageTokensMode.Account(params.userWalletId)
private val useCasesFacade: ManageTokensUseCasesFacade = manageTokensUseCasesFacadeFactory
.create(mode = portfolio)
private val manageTokensListManager = manageTokensListManagerFactory.create(
@ -223,12 +216,12 @@ internal class OnboardingManageTokensModel @Inject constructor(
.flatten()
.toSet()
.associate { network -> network.backendId to network.derivationPath.value }
val showTangemIcon = useCasesFacade.needColdWalletInteraction(network = network)
val shouldShowTangemIcon = useCasesFacade.needColdWalletInteraction(network = network)
state.update { state ->
state.copy(
actionButtonConfig = OnboardingManageTokensUM.ActionButtonConfig.Continue(
onClick = ::saveChanges,
showTangemIcon = showTangemIcon,
showTangemIcon = shouldShowTangemIcon,
),
)
}
@ -267,8 +260,8 @@ internal class OnboardingManageTokensModel @Inject constructor(
useCasesFacade.saveManagedTokensUseCase(
currenciesToAdd = manageTokensListManager.currenciesToAdd.value,
currenciesToRemove = manageTokensListManager.currenciesToRemove.value,
).getOrElse {
Timber.e(it, "Failed to save changes")
).getOrElse { throwable ->
Timber.e(throwable, "Failed to save changes")
return@resource
}
@ -292,8 +285,8 @@ internal class OnboardingManageTokensModel @Inject constructor(
useCasesFacade.saveManagedTokensUseCase(
currenciesToAdd = manageTokensListManager.currenciesToAdd.value,
currenciesToRemove = manageTokensListManager.currenciesToRemove.value,
).getOrElse {
Timber.e(it, "Failed to save changes")
).getOrElse { throwable ->
Timber.e(throwable, "Failed to save changes")
return@resource
}

View file

@ -69,7 +69,7 @@ private fun Preview_AddCustomTokenBottomSheet(
}
private class AddCustomTokenComponentPreviewProvider : PreviewParameterProvider<AddCustomTokenComponent> {
private val mode: AddCustomTokenMode get() = AddCustomTokenMode.Wallet(UserWalletId(stringValue = "321"))
private val mode: AddCustomTokenMode get() = AddCustomTokenMode(UserWalletId(stringValue = "321"))
override val values: Sequence<AddCustomTokenComponent>
get() = sequenceOf(
PreviewAddCustomTokenComponent(),

View file

@ -269,7 +269,7 @@ private class CustomTokenNetworkSelectorComponentPreviewProvider :
PreviewParameterProvider<CustomTokenSelectorComponent> {
private val derivationPath = Network.DerivationPath.Card("m/44'/0'/0'/0/0")
private val mode: AddCustomTokenMode get() = AddCustomTokenMode.Wallet(UserWalletId(stringValue = "321"))
private val mode: AddCustomTokenMode get() = AddCustomTokenMode(UserWalletId(stringValue = "321"))
override val values: Sequence<CustomTokenSelectorComponent>
get() = sequenceOf(

View file

@ -443,7 +443,7 @@ private class PreviewManageTokensComponentProvider : PreviewParameterProvider<Ma
showTangemIcon = true,
params = ManageTokensComponent.Params(
source = ManageTokensSource.ONBOARDING,
mode = ManageTokensMode.Wallet(UserWalletId("0x")),
mode = ManageTokensMode.Account(UserWalletId("0x")),
),
),
PreviewManageTokensComponent(
@ -459,7 +459,7 @@ private class PreviewManageTokensComponentProvider : PreviewParameterProvider<Ma
showTangemIcon = false,
params = ManageTokensComponent.Params(
source = ManageTokensSource.ONBOARDING,
mode = ManageTokensMode.Wallet(UserWalletId("0x")),
mode = ManageTokensMode.Account(UserWalletId("0x")),
),
),
)

View file

@ -7,34 +7,27 @@ import arrow.core.raise.ensureNotNull
import arrow.core.right
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchainsdk.utils.fromNetworkId
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
import com.tangem.domain.account.producer.SingleAccountListProducer
import com.tangem.domain.account.status.usecase.GetAccountCurrencyStatusUseCase
import com.tangem.domain.account.status.usecase.ManageCryptoCurrenciesUseCase
import com.tangem.domain.account.supplier.SingleAccountListSupplier
import com.tangem.domain.managetokens.CheckIsCurrencyNotAddedUseCase
import com.tangem.domain.models.account.Account
import com.tangem.domain.models.account.AccountId
import com.tangem.domain.models.account.DerivationIndex
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.network.Network
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.wallets.usecase.DerivePublicKeysUseCase
import com.tangem.lib.crypto.derivation.AccountNodeRecognizer
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import timber.log.Timber
@Suppress("LongParameterList")
internal class CustomTokenFormUseCasesFacade @AssistedInject constructor(
@Assisted private val userWalletId: UserWalletId,
private val derivePublicKeysUseCase: DerivePublicKeysUseCase,
private val checkIsCurrencyNotAddedUseCase: CheckIsCurrencyNotAddedUseCase,
private val manageCryptoCurrenciesUseCase: ManageCryptoCurrenciesUseCase,
private val singleAccountListSupplier: SingleAccountListSupplier,
private val getAccountCurrencyStatusUseCase: GetAccountCurrencyStatusUseCase,
private val accountsFeatureToggles: AccountsFeatureToggles,
) {
suspend fun addCryptoCurrenciesUseCase(currency: CryptoCurrency): Either<Throwable, Unit> {
@ -45,35 +38,18 @@ internal class CustomTokenFormUseCasesFacade @AssistedInject constructor(
}
}
suspend fun derivePublicKeysUseCase(currencies: List<CryptoCurrency>): Either<Throwable, Unit> {
return if (accountsFeatureToggles.isFeatureEnabled) {
Unit.right()
} else {
derivePublicKeysUseCase.invoke(userWalletId = userWalletId, currencies = currencies)
}
}
suspend fun checkIsCurrencyNotAddedUseCase(
networkId: Network.ID,
derivationPath: Network.DerivationPath,
contractAddress: String?,
): Either<Throwable, Boolean> = if (accountsFeatureToggles.isFeatureEnabled) {
getAccountCurrencyStatusUseCase.invokeSync(
userWalletId = userWalletId,
networkId = networkId,
derivationPath = derivationPath,
contractAddress = contractAddress,
)
.fold(ifEmpty = { true }, ifSome = { false })
.right()
} else {
checkIsCurrencyNotAddedUseCase.invoke(
userWalletId = userWalletId,
networkId = networkId,
derivationPath = derivationPath,
contractAddress = contractAddress,
)
}
): Either<Throwable, Boolean> = getAccountCurrencyStatusUseCase.invokeSync(
userWalletId = userWalletId,
networkId = networkId,
derivationPath = derivationPath,
contractAddress = contractAddress,
)
.fold(ifEmpty = { true }, ifSome = { false })
.right()
private suspend fun Raise<Throwable>.getAccountId(currency: CryptoCurrency): AccountId {
val accountList = singleAccountListSupplier.getSyncOrNull(

View file

@ -89,12 +89,9 @@ internal class ManageTokensListManager @AssistedInject constructor(
* @param isCollapsed set initial display state of networks. !!! WARNING !!! Use `false` flag with cation
*/
suspend fun launchPagination(isCollapsed: Boolean) = coroutineScope {
val loadUserTokensFromRemote = when (mode) {
is ManageTokensMode.Wallet,
is ManageTokensMode.Account,
-> source == ManageTokensSource.ONBOARDING
ManageTokensMode.None,
-> false
val shouldLoadTokensFromRemote = when (mode) {
is ManageTokensMode.Account -> source == ManageTokensSource.ONBOARDING
ManageTokensMode.None -> false
}
val batchFlow = useCasesFacade.getManagedTokensUseCase(
context = ManageTokensListBatchingContext(
@ -102,7 +99,7 @@ internal class ManageTokensListManager @AssistedInject constructor(
coroutineScope = this,
),
// only for onboarding case, change carefully and check repository implementation
loadUserTokensFromRemote = loadUserTokensFromRemote,
loadUserTokensFromRemote = shouldLoadTokensFromRemote,
)
batchFlow.state
@ -185,9 +182,7 @@ internal class ManageTokensListManager @AssistedInject constructor(
}
val canEditItems = when (state.mode) {
is ManageTokensMode.Account,
is ManageTokensMode.Wallet,
-> true
is ManageTokensMode.Account -> true
ManageTokensMode.None -> false
}
state.copy(
@ -210,7 +205,7 @@ internal class ManageTokensListManager @AssistedInject constructor(
override fun addCurrency(batchKey: Int, currency: ManagedCryptoCurrency.Token, network: Network) {
changedCurrenciesManager.addCurrency(currency, network)
sendSelectCurrencyAction(batchKey, currency.id, network, isSelected = true)
sendSelectCurrencyAction(batchKey = batchKey, currencyId = currency.id, network = network, isSelected = true)
sendSelectCurrencyAnalyticsEvent(currency, isSelected = true)
}
@ -218,7 +213,7 @@ internal class ManageTokensListManager @AssistedInject constructor(
override fun removeCurrency(batchKey: Int, currency: ManagedCryptoCurrency.Token, network: Network) {
changedCurrenciesManager.removeCurrency(currency, network)
sendSelectCurrencyAction(batchKey, currency.id, network, isSelected = false)
sendSelectCurrencyAction(batchKey = batchKey, currencyId = currency.id, network = network, isSelected = false)
sendSelectCurrencyAnalyticsEvent(currency, isSelected = false)
}
@ -270,9 +265,9 @@ internal class ManageTokensListManager @AssistedInject constructor(
network = network,
tempAddedTokens = changedCurrenciesManager.currenciesToAdd.value,
tempRemovedTokens = changedCurrenciesManager.currenciesToRemove.value,
).getOrElse {
).getOrElse { throwable ->
Timber.e(
it,
throwable,
"""
Failed to check linked tokens
|- Mode: $mode
@ -281,7 +276,7 @@ internal class ManageTokensListManager @AssistedInject constructor(
)
val message = SnackbarMessage(
message = it.localizedMessage
message = throwable.localizedMessage
?.let(::stringReference)
?: resourceReference(R.string.common_error),
)
@ -296,9 +291,9 @@ internal class ManageTokensListManager @AssistedInject constructor(
): CurrencyUnsupportedState? {
return useCasesFacade.checkCurrencyUnsupportedUseCase(
sourceNetwork = sourceNetwork,
).getOrElse {
).getOrElse { throwable ->
Timber.e(
it,
throwable,
"""
Failed to check currency unsupported state
|- Mode: $mode
@ -307,7 +302,7 @@ internal class ManageTokensListManager @AssistedInject constructor(
)
val message = SnackbarMessage(
message = it.localizedMessage
message = throwable.localizedMessage
?.let(::stringReference)
?: resourceReference(R.string.common_error),
)
@ -334,7 +329,12 @@ internal class ManageTokensListManager @AssistedInject constructor(
toRemove = currenciesToRemove.value,
),
onSelectCurrencyNetwork = { networkId, isSelected ->
selectNetwork(currencyBatch.key, currency, networkId, isSelected)
selectNetwork(
batchKey = currencyBatch.key,
currency = currency,
source = networkId,
isSelected = isSelected,
)
},
onLongTap = ::copyContractAddress,
)

View file

@ -3,12 +3,10 @@ package com.tangem.features.managetokens.utils.list
import arrow.core.Either
import arrow.core.left
import arrow.core.right
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
import com.tangem.domain.account.producer.SingleAccountProducer
import com.tangem.domain.account.status.usecase.ManageCryptoCurrenciesUseCase
import com.tangem.domain.account.supplier.SingleAccountSupplier
import com.tangem.domain.managetokens.CheckCurrencyUnsupportedUseCase
import com.tangem.domain.managetokens.CheckHasLinkedTokensUseCase
import com.tangem.domain.managetokens.GetDistinctManagedCurrenciesUseCase
import com.tangem.domain.managetokens.GetManagedTokensUseCase
import com.tangem.domain.managetokens.model.CurrencyUnsupportedState
@ -29,12 +27,10 @@ import dagger.assisted.AssistedInject
internal class ManageTokensUseCasesFacade @AssistedInject constructor(
val getManagedTokensUseCase: GetManagedTokensUseCase,
val getDistinctManagedTokensUseCase: GetDistinctManagedCurrenciesUseCase,
private val checkHasLinkedTokensUseCase: CheckHasLinkedTokensUseCase,
private val checkCurrencyUnsupportedUseCase: CheckCurrencyUnsupportedUseCase,
private val coldWalletAndHasMissedDerivationsUseCase: ColdWalletAndHasMissedDerivationsUseCase,
private val manageCryptoCurrenciesUseCase: ManageCryptoCurrenciesUseCase,
private val customTokensRepository: CustomTokensRepository,
private val accountsFeatureToggles: AccountsFeatureToggles,
private val singleAccountSupplier: SingleAccountSupplier,
@Assisted private val mode: ManageTokensMode,
) {
@ -45,17 +41,10 @@ internal class ManageTokensUseCasesFacade @AssistedInject constructor(
fun manageTokensListConfig(searchText: String?): ManageTokensListConfig {
return when (mode) {
is ManageTokensMode.Account -> {
ManageTokensListConfig.Account(accountId = mode.accountId, searchText = searchText)
}
is ManageTokensMode.Wallet -> {
ManageTokensListConfig.Wallet(userWalletId = mode.userWalletId, searchText = searchText)
ManageTokensListConfig(accountId = mode.accountId, searchText = searchText)
}
ManageTokensMode.None -> {
if (accountsFeatureToggles.isFeatureEnabled) {
ManageTokensListConfig.Account(accountId = null, searchText = searchText)
} else {
ManageTokensListConfig.Wallet(userWalletId = null, searchText = searchText)
}
ManageTokensListConfig(accountId = null, searchText = searchText)
}
}
}
@ -70,7 +59,6 @@ internal class ManageTokensUseCasesFacade @AssistedInject constructor(
manageCryptoCurrenciesUseCase(accountId = mode.accountId, remove = currency)
}
is ManageTokensMode.Wallet -> error("Unsupported")
ManageTokensMode.None -> nonePortfolioError.left()
}
}
@ -90,18 +78,12 @@ internal class ManageTokensUseCasesFacade @AssistedInject constructor(
) as? Account.CryptoPortfolio
?: return IllegalStateException("Account not found").left()
(account.cryptoCurrencies + added - removed).any {
it is CryptoCurrency.Token && it.network.backendId == network.backendId &&
it.network.derivationPath == network.derivationPath
(account.cryptoCurrencies + added - removed).any { currency ->
currency is CryptoCurrency.Token && currency.network.backendId == network.backendId &&
currency.network.derivationPath == network.derivationPath
}
.right()
}
is ManageTokensMode.Wallet -> checkHasLinkedTokensUseCase.invoke(
userWalletId = mode.userWalletId,
network = network,
tempAddedTokens = tempAddedTokens,
tempRemovedTokens = tempRemovedTokens,
)
ManageTokensMode.None -> nonePortfolioError.left()
}
}
@ -114,10 +96,6 @@ internal class ManageTokensUseCasesFacade @AssistedInject constructor(
userWalletId = mode.accountId.userWalletId,
sourceNetwork = sourceNetwork,
)
is ManageTokensMode.Wallet -> checkCurrencyUnsupportedUseCase.invoke(
userWalletId = mode.userWalletId,
sourceNetwork = sourceNetwork,
)
ManageTokensMode.None -> nonePortfolioError.left()
}
}
@ -127,10 +105,6 @@ internal class ManageTokensUseCasesFacade @AssistedInject constructor(
userWalletId = mode.accountId.userWalletId,
networksWithDerivationPath = network,
)
is ManageTokensMode.Wallet -> coldWalletAndHasMissedDerivationsUseCase.invoke(
userWalletId = mode.userWalletId,
networksWithDerivationPath = network,
)
ManageTokensMode.None -> false
}
@ -145,7 +119,6 @@ internal class ManageTokensUseCasesFacade @AssistedInject constructor(
remove = currenciesToRemove.mapToCryptoCurrencies(userWalletId = mode.accountId.userWalletId),
)
}
is ManageTokensMode.Wallet -> error("Unsupported")
ManageTokensMode.None -> nonePortfolioError.left()
}

View file

@ -29,9 +29,7 @@ internal class ManageTokensWarningDelegate @AssistedInject constructor(
) {
val isNonePortfolio = when (mode) {
ManageTokensMode.None -> true
is ManageTokensMode.Account,
is ManageTokensMode.Wallet,
-> false
is ManageTokensMode.Account -> false
}
val hasLinkedTokens = if (isNonePortfolio || !isCoin) {
false

View file

@ -24,12 +24,10 @@ import com.tangem.core.ui.message.DialogMessage
import com.tangem.core.ui.message.EventMessageAction
import com.tangem.core.ui.message.SnackbarMessage
import com.tangem.core.ui.message.bottomSheetMessage
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
import com.tangem.domain.account.supplier.SingleAccountListSupplier
import com.tangem.domain.account.usecase.IsAccountsModeEnabledUseCase
import com.tangem.domain.card.common.util.cardTypesResolver
import com.tangem.domain.demo.IsDemoCardUseCase
import com.tangem.domain.models.PortfolioId
import com.tangem.domain.models.account.AccountId
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.models.scan.ScanResponse
@ -87,7 +85,6 @@ internal class WalletSettingsModel @Inject constructor(
private val permissionsRepository: PermissionRepository,
private val notificationsRepository: NotificationsRepository,
private val unlockHotWalletContextualUseCase: UnlockHotWalletContextualUseCase,
private val accountsFeatureToggles: AccountsFeatureToggles,
private val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase,
private val singleAccountListSupplier: SingleAccountListSupplier,
private val accountListSortingSaver: AccountListSortingSaver,
@ -159,8 +156,7 @@ internal class WalletSettingsModel @Inject constructor(
accountList = accountList,
),
accountReorderUM = AccountReorderUM(
isDragEnabled = accountsFeatureToggles.isFeatureEnabled &&
accountList.count { it is WalletSettingsAccountsUM.Account } > 1,
isDragEnabled = accountList.count { it is WalletSettingsAccountsUM.Account } > 1,
onMove = ::onAccountReorder,
onDragStopped = ::onAccountDragStopped,
),
@ -237,13 +233,7 @@ internal class WalletSettingsModel @Inject constructor(
router.push(
AppRoute.ManageTokens(
source = Source.SETTINGS,
portfolioId = if (accountsFeatureToggles.isFeatureEnabled) {
PortfolioId(
accountId = AccountId.forMainCryptoPortfolio(userWalletId = userWallet.walletId),
)
} else {
PortfolioId(userWalletId = userWallet.walletId)
},
accountId = AccountId.forMainCryptoPortfolio(userWalletId = userWallet.walletId),
),
)
},
@ -378,7 +368,7 @@ internal class WalletSettingsModel @Inject constructor(
if (!state.value.isWalletBackedUp) {
showMakeBackupAtFirstAlertBS()
} else {
unlockWalletIfNeedAndProceed { authorizationRequired ->
unlockWalletIfNeedAndProceed { _ ->
router.push(
route = AppRoute.UpdateAccessCode(
userWalletId = params.userWalletId,

View file

@ -6,7 +6,6 @@ import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.AppRouter
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.navigation.url.UrlOpener
import com.tangem.domain.models.PortfolioId
import com.tangem.domain.models.TokenReceiveConfig
import com.tangem.domain.models.account.AccountId
import com.tangem.domain.models.currency.CryptoCurrency
@ -55,7 +54,7 @@ internal class DefaultWalletRouter @Inject constructor(
override fun openManageTokensScreen(accountId: AccountId) {
val route = AppRoute.ManageTokens(
source = AppRoute.ManageTokens.Source.ACCOUNT,
portfolioId = PortfolioId(accountId),
accountId = accountId,
)
router.push(route)
}