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

@ -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