Updated on 2026-08-14

This commit is contained in:
Tangem 2025-09-01 20:34:35 +07:00
parent e250ecd508
commit dfc3dbc324
30 changed files with 574 additions and 369 deletions

View file

@ -2,12 +2,11 @@ package com.tangem.features.managetokens.component
import com.tangem.core.decompose.factory.ComponentFactory
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
import com.tangem.domain.models.wallet.UserWalletId
interface AddCustomTokenComponent : ComposableBottomSheetComponent {
data class Params(
val userWalletId: UserWalletId,
val mode: AddCustomTokenMode,
val source: ManageTokensSource,
val onDismiss: () -> Unit,
val onCurrencyAdded: () -> Unit,

View file

@ -7,9 +7,16 @@ import com.tangem.domain.models.wallet.UserWalletId
interface ManageTokensComponent : ComposableContentComponent {
data class Params(
val userWalletId: UserWalletId?,
val mode: ManageTokensMode,
val source: ManageTokensSource,
)
) {
constructor(userWalletId: UserWalletId?, source: ManageTokensSource) : this(
source = source,
mode = userWalletId
?.let { ManageTokensMode.Wallet(userWalletId) }
?: ManageTokensMode.None,
)
}
interface Factory : ComponentFactory<Params, ManageTokensComponent>
}

View file

@ -1,8 +1,22 @@
package com.tangem.features.managetokens.component
import com.tangem.domain.models.account.AccountId
import com.tangem.domain.models.wallet.UserWalletId
enum class ManageTokensSource(val analyticsName: String) {
STORIES(analyticsName = "Stories"),
ONBOARDING(analyticsName = "Onboarding"),
SETTINGS(analyticsName = "Settings"),
SEND_VIA_SWAP(analyticsName = "SendViaSwap"),
}
sealed interface ManageTokensMode {
data class Wallet(val userWalletId: UserWalletId) : ManageTokensMode
data class Account(val accountId: AccountId) : ManageTokensMode
data object None : ManageTokensMode
}
sealed interface AddCustomTokenMode {
data class Wallet(val userWalletId: UserWalletId) : AddCustomTokenMode
data class Account(val accountId: AccountId) : AddCustomTokenMode
}

View file

@ -22,6 +22,7 @@ import com.tangem.features.managetokens.choosetoken.entity.ChooseManageTokensBot
import com.tangem.features.managetokens.choosetoken.entity.ChooseManagedTokenUM
import com.tangem.features.managetokens.component.ChooseManagedTokensComponent
import com.tangem.features.managetokens.component.ChooseManagedTokensComponent.Source
import com.tangem.features.managetokens.component.ManageTokensMode
import com.tangem.features.managetokens.component.ManageTokensSource
import com.tangem.features.managetokens.component.analytics.CommonManageTokensAnalyticEvents
import com.tangem.features.managetokens.entity.item.CurrencyItemUM
@ -29,6 +30,7 @@ import com.tangem.features.managetokens.entity.managetokens.ManageTokensTopBarUM
import com.tangem.features.managetokens.entity.managetokens.ManageTokensUM
import com.tangem.features.managetokens.impl.R
import com.tangem.features.managetokens.utils.list.ManageTokensListManager
import com.tangem.features.managetokens.utils.list.ManageTokensUseCasesFacade
import com.tangem.features.managetokens.utils.list.getLoadingItems
import com.tangem.pagination.BatchFetchResult
import com.tangem.pagination.PaginationStatus
@ -51,12 +53,19 @@ internal class ChooseManagedTokensModel @Inject constructor(
private val setShouldShowNotificationUseCase: SetShouldShowNotificationUseCase,
private val analyticsEventHandler: AnalyticsEventHandler,
paramsContainer: ParamsContainer,
manageTokensUseCasesFacadeFactory: ManageTokensUseCasesFacade.Factory,
manageTokensListManagerFactory: ManageTokensListManager.Factory,
) : Model() {
private val params: ChooseManagedTokensComponent.Params = paramsContainer.require()
private val useCasesFacade: ManageTokensUseCasesFacade = manageTokensUseCasesFacadeFactory
.create(mode = ManageTokensMode.None)
private val manageTokensListManager = manageTokensListManagerFactory.create(
scope = modelScope,
source = ManageTokensSource.SEND_VIA_SWAP,
mode = ManageTokensMode.None,
useCasesFacade = useCasesFacade,
onCurrencySelect = { token ->
bottomSheetNavigation.activate(
ChooseManageTokensBottomSheetConfig.SwapTokensBottomSheetConfig(
@ -86,7 +95,7 @@ internal class ChooseManagedTokensModel @Inject constructor(
observeSearchQueryChanges()
modelScope.launch {
manageTokensListManager.launchPagination(source = ManageTokensSource.SEND_VIA_SWAP, userWalletId = null)
manageTokensListManager.launchPagination()
}
}
@ -159,7 +168,7 @@ internal class ChooseManagedTokensModel @Inject constructor(
}
}
.sample(periodMillis = 1_000)
.onEach { query -> manageTokensListManager.search(userWalletId = null, query = query) }
.onEach { query -> manageTokensListManager.search(query = query) }
.launchIn(modelScope)
}
@ -295,7 +304,7 @@ internal class ChooseManagedTokensModel @Inject constructor(
if (state.readContent.isInitialBatchLoading || state.readContent.isNextBatchLoading) return false
modelScope.launch {
manageTokensListManager.loadMore(userWalletId = null, query = state.readContent.search.query)
manageTokensListManager.loadMore(query = state.readContent.search.query)
}
return true

View file

@ -2,13 +2,12 @@ package com.tangem.features.managetokens.component
import com.tangem.core.decompose.factory.ComponentFactory
import com.tangem.core.ui.decompose.ComposableDialogComponent
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.features.managetokens.entity.customtoken.SelectedDerivationPath
internal interface CustomTokenDerivationInputComponent : ComposableDialogComponent {
data class Params(
val userWalletId: UserWalletId,
val mode: AddCustomTokenMode,
val onConfirm: (SelectedDerivationPath) -> Unit,
val onDismiss: () -> Unit,
)

View file

@ -2,7 +2,6 @@ package com.tangem.features.managetokens.component
import com.tangem.core.decompose.factory.ComponentFactory
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.features.managetokens.entity.customtoken.CustomTokenFormValues
import com.tangem.features.managetokens.entity.customtoken.SelectedDerivationPath
import com.tangem.features.managetokens.entity.customtoken.SelectedNetwork
@ -10,7 +9,7 @@ import com.tangem.features.managetokens.entity.customtoken.SelectedNetwork
internal interface CustomTokenFormComponent : ComposableContentComponent {
data class Params(
val userWalletId: UserWalletId,
val mode: AddCustomTokenMode,
val network: SelectedNetwork,
val derivationPath: SelectedDerivationPath?,
val formValues: CustomTokenFormValues,

View file

@ -2,7 +2,6 @@ package com.tangem.features.managetokens.component
import com.tangem.core.decompose.factory.ComponentFactory
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.features.managetokens.entity.customtoken.SelectedDerivationPath
import com.tangem.features.managetokens.entity.customtoken.SelectedNetwork
@ -11,13 +10,13 @@ internal interface CustomTokenSelectorComponent : ComposableContentComponent {
sealed class Params {
data class NetworkSelector(
val userWalletId: UserWalletId,
val mode: AddCustomTokenMode,
val selectedNetwork: SelectedNetwork?,
val onNetworkSelected: (SelectedNetwork) -> Unit,
) : Params()
data class DerivationPathSelector(
val userWalletId: UserWalletId,
val mode: AddCustomTokenMode,
val selectedNetwork: SelectedNetwork,
val selectedDerivationPath: SelectedDerivationPath?,
val onDerivationPathSelected: (SelectedDerivationPath) -> Unit,

View file

@ -38,7 +38,7 @@ internal class DefaultAddCustomTokenComponent @AssistedInject constructor(
) : AddCustomTokenComponent, AppComponentContext by context {
private val initialConfiguration = AddCustomTokenConfig(
userWalletId = params.userWalletId,
mode = params.mode,
step = AddCustomTokenConfig.Step.INITIAL_NETWORK_SELECTOR,
)
@ -105,7 +105,7 @@ internal class DefaultAddCustomTokenComponent @AssistedInject constructor(
selectorComponentFactory.create(
context = childByContext(componentContext),
params = CustomTokenSelectorComponent.Params.NetworkSelector(
userWalletId = config.userWalletId,
mode = config.mode,
selectedNetwork = null,
onNetworkSelected = ::changeSelectedNetwork,
),
@ -115,7 +115,7 @@ internal class DefaultAddCustomTokenComponent @AssistedInject constructor(
selectorComponentFactory.create(
context = childByContext(componentContext),
params = CustomTokenSelectorComponent.Params.NetworkSelector(
userWalletId = config.userWalletId,
mode = config.mode,
selectedNetwork = config.selectedNetwork,
onNetworkSelected = ::changeSelectedNetwork,
),
@ -125,7 +125,7 @@ internal class DefaultAddCustomTokenComponent @AssistedInject constructor(
selectorComponentFactory.create(
context = childByContext(componentContext),
params = CustomTokenSelectorComponent.Params.DerivationPathSelector(
userWalletId = config.userWalletId,
mode = config.mode,
selectedNetwork = requireNotNull(config.selectedNetwork) {
"Network is not selected"
},
@ -138,7 +138,7 @@ internal class DefaultAddCustomTokenComponent @AssistedInject constructor(
formComponentFactory.create(
context = childByContext(componentContext),
params = CustomTokenFormComponent.Params(
userWalletId = config.userWalletId,
mode = config.mode,
network = requireNotNull(config.selectedNetwork) {
"Network is not selected"
},

View file

@ -41,7 +41,7 @@ internal class DefaultCustomTokenSelectorComponent @AssistedInject constructor(
is CustomTokenSelectorDialogConfig.CustomDerivationInput -> customTokenDerivationInputComponentFactory.create(
context = childByContext(context),
params = CustomTokenDerivationInputComponent.Params(
userWalletId = config.userWalletId,
mode = config.mode,
onConfirm = model::selectCustomDerivationPath,
onDismiss = model.dialogNavigation::dismiss,
),

View file

@ -13,6 +13,7 @@ import com.tangem.core.decompose.context.childByContext
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
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.model.ManageTokensModel
@ -52,18 +53,20 @@ internal class DefaultManageTokensComponent @AssistedInject constructor(
private fun bottomSheetChild(
config: ManageTokensBottomSheetConfig,
componentContext: ComponentContext,
): ComposableBottomSheetComponent = when (config) {
is ManageTokensBottomSheetConfig.AddCustomToken -> {
addCustomTokenComponentFactory.create(
context = childByContext(componentContext),
params = AddCustomTokenComponent.Params(
userWalletId = config.userWalletId,
source = params.source,
onDismiss = model.bottomSheetNavigation::dismiss,
onCurrencyAdded = model::reloadList,
),
)
): ComposableBottomSheetComponent {
val mode = when (config) {
is ManageTokensBottomSheetConfig.AddWalletCustomToken -> AddCustomTokenMode.Wallet(config.userWalletId)
is ManageTokensBottomSheetConfig.AddAccountCustomToken -> AddCustomTokenMode.Account(config.accountId)
}
return addCustomTokenComponentFactory.create(
context = childByContext(componentContext),
params = AddCustomTokenComponent.Params(
mode = mode,
source = params.source,
onDismiss = model.bottomSheetNavigation::dismiss,
onCurrencyAdded = model::reloadList,
),
)
}
@AssistedFactory

View file

@ -6,6 +6,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.features.managetokens.component.AddCustomTokenComponent
import com.tangem.features.managetokens.component.AddCustomTokenMode
import com.tangem.features.managetokens.component.CustomTokenSelectorComponent
import com.tangem.features.managetokens.entity.customtoken.AddCustomTokenConfig
import com.tangem.features.managetokens.ui.AddCustomTokenBottomSheet
@ -14,7 +15,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
internal class PreviewAddCustomTokenComponent(
initialState: AddCustomTokenConfig = AddCustomTokenConfig(
userWalletId = UserWalletId(stringValue = "321"),
mode = AddCustomTokenMode.Wallet(UserWalletId(stringValue = "321")),
step = AddCustomTokenConfig.Step.INITIAL_NETWORK_SELECTOR,
),
) : AddCustomTokenComponent {
@ -41,7 +42,7 @@ internal class PreviewAddCustomTokenComponent(
AddCustomTokenConfig.Step.INITIAL_NETWORK_SELECTOR -> {
PreviewCustomTokenSelectorComponent(
params = CustomTokenSelectorComponent.Params.NetworkSelector(
userWalletId = config.userWalletId,
mode = config.mode,
selectedNetwork = null,
onNetworkSelected = {},
),
@ -50,7 +51,7 @@ internal class PreviewAddCustomTokenComponent(
AddCustomTokenConfig.Step.NETWORK_SELECTOR -> {
PreviewCustomTokenSelectorComponent(
params = CustomTokenSelectorComponent.Params.NetworkSelector(
userWalletId = config.userWalletId,
mode = config.mode,
selectedNetwork = config.selectedNetwork,
onNetworkSelected = {},
),
@ -59,7 +60,7 @@ internal class PreviewAddCustomTokenComponent(
AddCustomTokenConfig.Step.DERIVATION_PATH_SELECTOR -> {
PreviewCustomTokenSelectorComponent(
params = CustomTokenSelectorComponent.Params.DerivationPathSelector(
userWalletId = config.userWalletId,
mode = config.mode,
selectedNetwork = config.selectedNetwork!!,
selectedDerivationPath = config.selectedDerivationPath!!,
onDerivationPathSelected = {},

View file

@ -5,6 +5,7 @@ import androidx.compose.ui.Modifier
import com.tangem.core.ui.extensions.stringReference
import com.tangem.domain.models.network.Network
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.features.managetokens.component.AddCustomTokenMode
import com.tangem.features.managetokens.component.CustomTokenSelectorComponent
import com.tangem.features.managetokens.component.CustomTokenSelectorComponent.Params
import com.tangem.features.managetokens.entity.customtoken.CustomTokenSelectorUM
@ -18,7 +19,7 @@ import kotlinx.collections.immutable.toImmutableList
internal class PreviewCustomTokenSelectorComponent(
private val params: Params = Params.NetworkSelector(
userWalletId = UserWalletId(stringValue = "321"),
mode = AddCustomTokenMode.Wallet(UserWalletId(stringValue = "321")),
selectedNetwork = null,
onNetworkSelected = {},
),

View file

@ -13,6 +13,7 @@ import com.tangem.core.ui.extensions.resourceReference
import com.tangem.domain.managetokens.model.ManagedCryptoCurrency
import com.tangem.domain.models.network.Network
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.item.CurrencyNetworkUM
import com.tangem.features.managetokens.entity.managetokens.ManageTokensTopBarUM
@ -38,8 +39,10 @@ internal class PreviewManageTokensComponent(
value = ManageTokensUM.ManageContent(
popBack = {},
items = items,
topBar = if (params.userWalletId != null) {
ManageTokensTopBarUM.ManageContent(
topBar = when (params.mode) {
is ManageTokensMode.Account,
is ManageTokensMode.Wallet,
-> ManageTokensTopBarUM.ManageContent(
title = resourceReference(id = R.string.main_manage_tokens),
onBackButtonClick = {},
endButton = TopAppBarButtonUM.Icon(
@ -47,8 +50,7 @@ internal class PreviewManageTokensComponent(
onClicked = {},
),
)
} else {
ManageTokensTopBarUM.ReadContent(
ManageTokensMode.None -> ManageTokensTopBarUM.ReadContent(
title = resourceReference(R.string.common_search_tokens),
onBackButtonClick = {},
)

View file

@ -1,13 +1,13 @@
package com.tangem.features.managetokens.entity.customtoken
import com.tangem.domain.models.network.Network
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.features.managetokens.component.AddCustomTokenMode
import kotlinx.serialization.Serializable
@Serializable
internal data class AddCustomTokenConfig(
val step: Step,
val userWalletId: UserWalletId,
val mode: AddCustomTokenMode,
val selectedNetwork: SelectedNetwork? = null,
val selectedDerivationPath: SelectedDerivationPath? = null,
val formValues: CustomTokenFormValues = CustomTokenFormValues(),

View file

@ -1,6 +1,6 @@
package com.tangem.features.managetokens.entity.customtoken
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.features.managetokens.component.AddCustomTokenMode
import kotlinx.serialization.Serializable
@Serializable
@ -8,6 +8,6 @@ internal sealed class CustomTokenSelectorDialogConfig {
@Serializable
data class CustomDerivationInput(
val userWalletId: UserWalletId,
val mode: AddCustomTokenMode,
) : CustomTokenSelectorDialogConfig()
}

View file

@ -1,5 +1,6 @@
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
@ -7,7 +8,12 @@ import kotlinx.serialization.Serializable
internal sealed class ManageTokensBottomSheetConfig {
@Serializable
data class AddCustomToken(
data class AddWalletCustomToken(
val userWalletId: UserWalletId,
) : ManageTokensBottomSheetConfig()
@Serializable
data class AddAccountCustomToken(
val accountId: AccountId,
) : ManageTokensBottomSheetConfig()
}

View file

@ -12,9 +12,6 @@ import com.tangem.core.ui.message.DialogMessage
import com.tangem.domain.managetokens.model.exceptoin.CustomTokenFormValidationException
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.network.Network
import com.tangem.domain.tokens.AddCryptoCurrenciesUseCase
import com.tangem.domain.wallets.usecase.DerivePublicKeysUseCase
import com.tangem.domain.wallets.usecase.HasMissedDerivationsUseCase
import com.tangem.features.managetokens.analytics.CustomTokenAnalyticsEvent
import com.tangem.features.managetokens.component.CustomTokenFormComponent
import com.tangem.features.managetokens.entity.customtoken.ClickableFieldUM
@ -25,6 +22,7 @@ import com.tangem.features.managetokens.entity.customtoken.TextInputFieldUM
import com.tangem.features.managetokens.impl.R
import com.tangem.features.managetokens.utils.CustomCurrencyFormBuilder
import com.tangem.features.managetokens.utils.CustomCurrencyValidator
import com.tangem.features.managetokens.utils.list.CustomTokenFormUseCasesFacade
import com.tangem.features.managetokens.utils.mapper.mapToDomainModel
import com.tangem.features.managetokens.utils.ui.*
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
@ -40,18 +38,17 @@ import javax.inject.Inject
@ModelScoped
internal class CustomTokenFormModel @Inject constructor(
override val dispatchers: CoroutineDispatcherProvider,
private val customCurrencyValidator: CustomCurrencyValidator,
private val addCryptoCurrenciesUseCase: AddCryptoCurrenciesUseCase,
private val derivePublicKeysUseCase: DerivePublicKeysUseCase,
private val hasMissedDerivationsUseCase: HasMissedDerivationsUseCase,
private val messageSender: UiMessageSender,
private val customTokenFormManager: CustomCurrencyFormBuilder,
private val analyticsEventHandler: AnalyticsEventHandler,
paramsContainer: ParamsContainer,
customTokenFormUseCasesFacadeFactory: CustomTokenFormUseCasesFacade.Factory,
) : Model() {
private val params: CustomTokenFormComponent.Params = paramsContainer.require()
private var createdCurrency: CryptoCurrency? = null
private var useCasesFacade: CustomTokenFormUseCasesFacade = customTokenFormUseCasesFacadeFactory.create(params.mode)
private val customCurrencyValidator = CustomCurrencyValidator(useCasesFacade)
val state: MutableStateFlow<CustomTokenFormUM> = MutableStateFlow(
value = getInitialState(),
@ -117,7 +114,6 @@ internal class CustomTokenFormModel @Inject constructor(
.drop(count = 1) // Skip initial state
.onEach { formValues ->
customCurrencyValidator.validateForm(
userWalletId = params.userWalletId,
networkId = params.network.id,
derivationPath = getDerivationPath(),
formValues = formValues,
@ -156,7 +152,6 @@ internal class CustomTokenFormModel @Inject constructor(
private fun validatePrefilledForm() = modelScope.launch {
customCurrencyValidator.validateForm(
userWalletId = params.userWalletId,
networkId = params.network.id,
derivationPath = getDerivationPath(),
formValues = state.value.tokenForm.mapToDomainModel(),
@ -169,8 +164,7 @@ internal class CustomTokenFormModel @Inject constructor(
isAlreadyAdded: Boolean,
isCustom: Boolean,
) = modelScope.launch {
val needToAddDerivation = hasMissedDerivationsUseCase(
userWalletId = params.userWalletId,
val needToAddDerivation = useCasesFacade.hasMissedDerivationsUseCase(
networksWithDerivationPath = mapOf(currency.network.backendId to getDerivationPath().value),
)
@ -343,13 +337,13 @@ internal class CustomTokenFormModel @Inject constructor(
)
analyticsEventHandler.send(event)
derivePublicKeysUseCase(params.userWalletId, listOf(currency)).getOrElse {
useCasesFacade.derivePublicKeysUseCase(listOf(currency)).getOrElse {
Timber.e(it, "Failed to derive public keys")
showErrorDialog()
return@resource
}
addCryptoCurrenciesUseCase(params.userWalletId, currency).getOrElse {
useCasesFacade.addCryptoCurrenciesUseCase(currency).getOrElse {
Timber.e(it, "Failed to add currency")
showErrorDialog()
return@resource

View file

@ -11,7 +11,7 @@ import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.message.SnackbarMessage
import com.tangem.domain.managetokens.GetSupportedNetworksUseCase
import com.tangem.domain.models.network.Network
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.features.managetokens.component.AddCustomTokenMode
import com.tangem.features.managetokens.component.CustomTokenSelectorComponent
import com.tangem.features.managetokens.component.CustomTokenSelectorComponent.Params.DerivationPathSelector
import com.tangem.features.managetokens.component.CustomTokenSelectorComponent.Params.NetworkSelector
@ -83,7 +83,7 @@ internal class CustomTokenSelectorModel @Inject constructor(
}
private suspend fun loadNetworks(selector: NetworkSelector): List<SelectableItemUM> {
return getSupportedNetworks(selector.userWalletId).map { network ->
return getSupportedNetworks(selector.mode).map { network ->
network.toCurrencyNetworkModel(
isSelected = network.id == selector.selectedNetwork?.id,
onSelectedStateChange = {
@ -122,7 +122,7 @@ internal class CustomTokenSelectorModel @Inject constructor(
derivationPaths.add(defaultPath)
}
getSupportedNetworks(selector.userWalletId)
getSupportedNetworks(selector.mode)
.mapNotNullTo(derivationPaths) { network ->
if (network.id == selector.selectedNetwork.id) {
return@mapNotNullTo null // Skip default path
@ -146,8 +146,9 @@ internal class CustomTokenSelectorModel @Inject constructor(
return derivationPaths
}
private suspend fun getSupportedNetworks(userWalletId: UserWalletId): List<Network> {
return getSupportedNetworksUseCase(userWalletId).getOrElse { e ->
private suspend fun getSupportedNetworks(mode: AddCustomTokenMode): List<Network> = when (mode) {
is AddCustomTokenMode.Account -> TODO("Account")
is AddCustomTokenMode.Wallet -> getSupportedNetworksUseCase(mode.userWalletId).getOrElse { e ->
val message = SnackbarMessage(message = resourceReference(R.string.common_unknown_error))
messageSender.send(message)
@ -158,7 +159,7 @@ internal class CustomTokenSelectorModel @Inject constructor(
private fun showCustomDerivationInput() {
val config = when (params) {
is NetworkSelector -> return
is DerivationPathSelector -> CustomTokenSelectorDialogConfig.CustomDerivationInput(params.userWalletId)
is DerivationPathSelector -> CustomTokenSelectorDialogConfig.CustomDerivationInput(params.mode)
}
dialogNavigation.activate(config)

View file

@ -17,12 +17,10 @@ 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.managetokens.SaveManagedTokensUseCase
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.wallets.usecase.HasMissedDerivationsUseCase
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
@ -30,6 +28,7 @@ import com.tangem.features.managetokens.entity.managetokens.ManageTokensUM
import com.tangem.features.managetokens.impl.R
import com.tangem.features.managetokens.utils.list.ChangedCurrencies
import com.tangem.features.managetokens.utils.list.ManageTokensListManager
import com.tangem.features.managetokens.utils.list.ManageTokensUseCasesFacade
import com.tangem.pagination.BatchFetchResult
import com.tangem.pagination.PaginationStatus
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
@ -47,18 +46,24 @@ internal class ManageTokensModel @Inject constructor(
override val dispatchers: CoroutineDispatcherProvider,
private val router: Router,
private val messageSender: UiMessageSender,
private val hasMissedDerivationsUseCase: HasMissedDerivationsUseCase,
private val saveManagedTokensUseCase: SaveManagedTokensUseCase,
private val analyticsEventHandler: AnalyticsEventHandler,
manageTokensListManagerFactory: ManageTokensListManager.Factory,
manageTokensUseCasesFacadeFactory: ManageTokensUseCasesFacade.Factory,
paramsContainer: ParamsContainer,
) : Model() {
private val params: ManageTokensComponent.Params = paramsContainer.require()
private val useCasesFacade: ManageTokensUseCasesFacade = manageTokensUseCasesFacadeFactory
.create(mode = params.mode)
private val manageTokensListManager = manageTokensListManagerFactory.create()
private val manageTokensListManager = manageTokensListManagerFactory.create(
scope = modelScope,
source = params.source,
mode = params.mode,
useCasesFacade = useCasesFacade,
)
val state: MutableStateFlow<ManageTokensUM> = MutableStateFlow(getInitialState(params.userWalletId))
val state: MutableStateFlow<ManageTokensUM> = MutableStateFlow(getInitialState())
val bottomSheetNavigation: SlotNavigation<ManageTokensBottomSheetConfig> = SlotNavigation()
init {
@ -79,23 +84,24 @@ internal class ManageTokensModel @Inject constructor(
observeSearchQueryChanges()
modelScope.launch {
manageTokensListManager.launchPagination(source = params.source, userWalletId = params.userWalletId)
manageTokensListManager.launchPagination()
}
}
fun reloadList() {
modelScope.launch {
manageTokensListManager.reload(params.userWalletId)
manageTokensListManager.reload()
}
}
private fun getInitialState(userWalletId: UserWalletId?): ManageTokensUM {
private fun getInitialState(): ManageTokensUM {
analyticsEventHandler.send(ManageTokensAnalyticEvent.ScreenOpened(params.source))
return if (userWalletId == null) {
createReadContentModel()
} else {
createManageContentModel()
return when (params.mode) {
is ManageTokensMode.Wallet,
is ManageTokensMode.Account,
-> createManageContentModel()
ManageTokensMode.None -> createReadContentModel()
}
}
@ -164,7 +170,7 @@ internal class ManageTokensModel @Inject constructor(
}
}
.sample(periodMillis = 1_000)
.onEach { query -> manageTokensListManager.search(userWalletId = params.userWalletId, query = query) }
.onEach { query -> manageTokensListManager.search(query = query) }
.launchIn(modelScope)
}
@ -261,19 +267,16 @@ internal class ManageTokensModel @Inject constructor(
private fun updateChangedItems(currenciesToAdd: ChangedCurrencies, currenciesToRemove: ChangedCurrencies) {
modelScope.launch {
val hasMissedDerivations = params.userWalletId?.let { walletId ->
val networks = currenciesToAdd.values
.flatten()
.toSet()
.associate { it.backendId to null }
hasMissedDerivationsUseCase(walletId, networks)
}
val networks = currenciesToAdd.values
.flatten()
.toSet()
.associate { it.backendId to null }
val hasMissedDerivations = useCasesFacade.hasMissedDerivationsUseCase(networks)
state.update { state ->
state.copySealed(
hasChanges = currenciesToAdd.isNotEmpty() || currenciesToRemove.isNotEmpty(),
needToAddDerivations = hasMissedDerivations ?: false,
needToAddDerivations = hasMissedDerivations,
)
}
}
@ -284,7 +287,7 @@ internal class ManageTokensModel @Inject constructor(
if (state.isInitialBatchLoading || state.isNextBatchLoading) return false
modelScope.launch {
manageTokensListManager.loadMore(userWalletId = params.userWalletId, query = state.search.query)
manageTokensListManager.loadMore(query = state.search.query)
}
return true
@ -292,9 +295,14 @@ internal class ManageTokensModel @Inject constructor(
private fun navigateToAddCustomToken() {
analyticsEventHandler.send(CustomTokenAnalyticsEvent.ButtonCustomToken(params.source))
params.userWalletId?.let {
bottomSheetNavigation.activate(ManageTokensBottomSheetConfig.AddCustomToken(it))
when (val portfolio = params.mode) {
is ManageTokensMode.Wallet ->
bottomSheetNavigation
.activate(ManageTokensBottomSheetConfig.AddWalletCustomToken(portfolio.userWalletId))
is ManageTokensMode.Account ->
bottomSheetNavigation
.activate(ManageTokensBottomSheetConfig.AddAccountCustomToken(portfolio.accountId))
ManageTokensMode.None -> Unit
}
}
@ -308,8 +316,7 @@ internal class ManageTokensModel @Inject constructor(
)
analyticsEventHandler.send(event)
saveManagedTokensUseCase(
userWalletId = requireNotNull(params.userWalletId),
useCasesFacade.saveManagedTokensUseCase(
currenciesToAdd = manageTokensListManager.currenciesToAdd.value,
currenciesToRemove = manageTokensListManager.currenciesToRemove.value,
).getOrElse {

View file

@ -13,11 +13,10 @@ 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.managetokens.SaveManagedTokensUseCase
import com.tangem.domain.redux.OnboardingManageTokensAction
import com.tangem.domain.redux.ReduxStateHolder
import com.tangem.domain.wallets.usecase.HasMissedDerivationsUseCase
import com.tangem.features.managetokens.analytics.ManageTokensAnalyticEvent
import com.tangem.features.managetokens.component.ManageTokensMode
import com.tangem.features.managetokens.component.ManageTokensSource
import com.tangem.features.managetokens.component.OnboardingManageTokensComponent
import com.tangem.features.managetokens.entity.item.CurrencyItemUM
@ -25,6 +24,7 @@ import com.tangem.features.managetokens.entity.managetokens.OnboardingManageToke
import com.tangem.features.managetokens.impl.R
import com.tangem.features.managetokens.utils.list.ChangedCurrencies
import com.tangem.features.managetokens.utils.list.ManageTokensListManager
import com.tangem.features.managetokens.utils.list.ManageTokensUseCasesFacade
import com.tangem.pagination.BatchFetchResult
import com.tangem.pagination.PaginationStatus
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
@ -42,15 +42,22 @@ internal class OnboardingManageTokensModel @Inject constructor(
override val dispatchers: CoroutineDispatcherProvider,
private val messageSender: UiMessageSender,
private val reduxStateHolder: ReduxStateHolder,
private val saveManagedTokensUseCase: SaveManagedTokensUseCase,
private val hasMissedDerivationsUseCase: HasMissedDerivationsUseCase,
private val analyticsEventHandler: AnalyticsEventHandler,
manageTokensListManagerFactory: ManageTokensListManager.Factory,
manageTokensUseCasesFacadeFactory: ManageTokensUseCasesFacade.Factory,
paramsContainer: ParamsContainer,
) : Model() {
private val params: OnboardingManageTokensComponent.Params = paramsContainer.require()
private val manageTokensListManager = manageTokensListManagerFactory.create()
private val portfolio = ManageTokensMode.Wallet(params.userWalletId)
private val useCasesFacade: ManageTokensUseCasesFacade = manageTokensUseCasesFacadeFactory
.create(mode = portfolio)
private val manageTokensListManager = manageTokensListManagerFactory.create(
scope = modelScope,
source = ManageTokensSource.ONBOARDING,
useCasesFacade = useCasesFacade,
mode = portfolio,
)
val state: MutableStateFlow<OnboardingManageTokensUM> = MutableStateFlow(getInitialState())
val returnToParentComponentFlow = MutableSharedFlow<Unit>()
@ -73,10 +80,7 @@ internal class OnboardingManageTokensModel @Inject constructor(
observeSearchQueryChanges()
modelScope.launch {
manageTokensListManager.launchPagination(
source = ManageTokensSource.ONBOARDING,
userWalletId = params.userWalletId,
)
manageTokensListManager.launchPagination()
}
}
@ -119,7 +123,7 @@ internal class OnboardingManageTokensModel @Inject constructor(
}
}
.sample(periodMillis = 1_000)
.onEach { query -> manageTokensListManager.search(userWalletId = params.userWalletId, query = query) }
.onEach { query -> manageTokensListManager.search(query = query) }
.launchIn(modelScope)
}
@ -208,13 +212,11 @@ internal class OnboardingManageTokensModel @Inject constructor(
)
}
} else {
val hasMissedDerivations = hasMissedDerivationsUseCase.invoke(
userWalletId = params.userWalletId,
networksWithDerivationPath = currenciesToAdd.values
.flatten()
.toSet()
.associate { it.backendId to null },
)
val network = currenciesToAdd.values
.flatten()
.toSet()
.associate { it.backendId to null }
val hasMissedDerivations = useCasesFacade.hasMissedDerivationsUseCase(network = network)
state.update { state ->
state.copy(
actionButtonConfig = OnboardingManageTokensUM.ActionButtonConfig.Continue(
@ -231,7 +233,7 @@ internal class OnboardingManageTokensModel @Inject constructor(
if (state.isInitialBatchLoading || state.isNextBatchLoading) return false
modelScope.launch {
manageTokensListManager.loadMore(userWalletId = params.userWalletId, query = state.search.query)
manageTokensListManager.loadMore(query = state.search.query)
}
return true
@ -255,8 +257,7 @@ internal class OnboardingManageTokensModel @Inject constructor(
)
analyticsEventHandler.send(event)
saveManagedTokensUseCase(
userWalletId = requireNotNull(params.userWalletId),
useCasesFacade.saveManagedTokensUseCase(
currenciesToAdd = manageTokensListManager.currenciesToAdd.value,
currenciesToRemove = manageTokensListManager.currenciesToRemove.value,
).getOrElse {
@ -281,8 +282,7 @@ internal class OnboardingManageTokensModel @Inject constructor(
) {
analyticsEventHandler.send(ManageTokensAnalyticEvent.ButtonLater)
saveManagedTokensUseCase(
userWalletId = requireNotNull(params.userWalletId),
useCasesFacade.saveManagedTokensUseCase(
currenciesToAdd = manageTokensListManager.currenciesToAdd.value,
currenciesToRemove = manageTokensListManager.currenciesToRemove.value,
).getOrElse {

View file

@ -19,6 +19,7 @@ import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.domain.models.network.Network
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.features.managetokens.component.AddCustomTokenComponent
import com.tangem.features.managetokens.component.AddCustomTokenMode
import com.tangem.features.managetokens.component.preview.PreviewAddCustomTokenComponent
import com.tangem.features.managetokens.entity.customtoken.AddCustomTokenConfig
import com.tangem.features.managetokens.entity.customtoken.AddCustomTokenUM
@ -68,12 +69,13 @@ private fun Preview_AddCustomTokenBottomSheet(
}
private class AddCustomTokenComponentPreviewProvider : PreviewParameterProvider<AddCustomTokenComponent> {
private val mode: AddCustomTokenMode get() = AddCustomTokenMode.Wallet(UserWalletId(stringValue = "321"))
override val values: Sequence<AddCustomTokenComponent>
get() = sequenceOf(
PreviewAddCustomTokenComponent(),
PreviewAddCustomTokenComponent(
initialState = AddCustomTokenConfig(
userWalletId = UserWalletId(stringValue = "321"),
mode = mode,
step = AddCustomTokenConfig.Step.FORM,
selectedNetwork = SelectedNetwork(
id = Network.ID(value = "1", derivationPath = Network.DerivationPath.None),
@ -85,7 +87,7 @@ private class AddCustomTokenComponentPreviewProvider : PreviewParameterProvider<
),
PreviewAddCustomTokenComponent(
initialState = AddCustomTokenConfig(
userWalletId = UserWalletId(stringValue = "321"),
mode = mode,
step = AddCustomTokenConfig.Step.NETWORK_SELECTOR,
selectedNetwork = SelectedNetwork(
id = Network.ID(value = "0", derivationPath = Network.DerivationPath.None),
@ -97,7 +99,7 @@ private class AddCustomTokenComponentPreviewProvider : PreviewParameterProvider<
),
PreviewAddCustomTokenComponent(
initialState = AddCustomTokenConfig(
userWalletId = UserWalletId(stringValue = "321"),
mode = mode,
step = AddCustomTokenConfig.Step.DERIVATION_PATH_SELECTOR,
selectedDerivationPath = SelectedDerivationPath(
id = Network.ID(value = "0", derivationPath = Network.DerivationPath.None),

View file

@ -32,6 +32,7 @@ import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.domain.models.network.Network
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.features.managetokens.component.AddCustomTokenMode
import com.tangem.features.managetokens.component.CustomTokenSelectorComponent
import com.tangem.features.managetokens.component.preview.PreviewCustomTokenSelectorComponent
import com.tangem.features.managetokens.entity.customtoken.CustomTokenSelectorUM
@ -268,12 +269,13 @@ 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"))
override val values: Sequence<CustomTokenSelectorComponent>
get() = sequenceOf(
PreviewCustomTokenSelectorComponent(
params = CustomTokenSelectorComponent.Params.DerivationPathSelector(
userWalletId = UserWalletId(stringValue = "321"),
mode = mode,
selectedNetwork = SelectedNetwork(
id = Network.ID(value = "0", derivationPath = derivationPath),
name = "Ethereum",
@ -291,7 +293,7 @@ private class CustomTokenNetworkSelectorComponentPreviewProvider :
),
PreviewCustomTokenSelectorComponent(
params = CustomTokenSelectorComponent.Params.NetworkSelector(
userWalletId = UserWalletId(stringValue = "321"),
mode = mode,
selectedNetwork = SelectedNetwork(
id = Network.ID(value = "0", derivationPath = derivationPath),
name = "Ethereum",

View file

@ -1,31 +1,21 @@
package com.tangem.features.managetokens.utils
import arrow.core.getOrElse
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.domain.managetokens.CheckIsCurrencyNotAddedUseCase
import com.tangem.domain.managetokens.CreateCryptoCurrencyUseCase
import com.tangem.domain.managetokens.FindTokenUseCase
import com.tangem.domain.managetokens.ValidateTokenFormUseCase
import com.tangem.domain.managetokens.model.AddCustomTokenForm
import com.tangem.domain.managetokens.model.exceptoin.CustomTokenFormValidationException
import com.tangem.domain.managetokens.model.exceptoin.FindTokenException
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.network.Network
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.features.managetokens.utils.list.CustomTokenFormUseCasesFacade
import com.tangem.utils.coroutines.JobHolder
import com.tangem.utils.coroutines.saveInAndJoin
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
@ModelScoped
internal class CustomCurrencyValidator @Inject constructor(
private val validateTokenFormUseCase: ValidateTokenFormUseCase,
private val createCryptoCurrencyUseCase: CreateCryptoCurrencyUseCase,
private val findTokenUseCase: FindTokenUseCase,
private val checkIsCurrencyNotAddedUseCase: CheckIsCurrencyNotAddedUseCase,
internal class CustomCurrencyValidator(
private val useCasesFacade: CustomTokenFormUseCasesFacade,
) {
private val validateFormJobHolder = JobHolder()
@ -45,14 +35,13 @@ internal class CustomCurrencyValidator @Inject constructor(
}
suspend fun validateForm(
userWalletId: UserWalletId,
networkId: Network.ID,
derivationPath: Network.DerivationPath,
formValues: AddCustomTokenForm.Raw,
) = coroutineScope {
updateStatus(Status.Validating)
val result = validateTokenFormUseCase(
val result = useCasesFacade.validateTokenFormUseCase(
networkId = networkId,
formValues = formValues,
)
@ -73,20 +62,19 @@ internal class CustomCurrencyValidator @Inject constructor(
launch {
when (validatedForm) {
is AddCustomTokenForm.Validated.All -> {
findOrCreateCurrency(userWalletId, networkId, derivationPath, validatedForm)
findOrCreateCurrency(networkId, derivationPath, validatedForm)
}
is AddCustomTokenForm.Validated.ContractAddressOnly -> {
findToken(userWalletId, networkId, derivationPath, validatedForm)
findToken(networkId, derivationPath, validatedForm)
}
is AddCustomTokenForm.Validated.Empty -> {
createCurrency(userWalletId, networkId, derivationPath, validatedForm = null)
createCurrency(networkId, derivationPath, validatedForm = null)
}
}
}.saveInAndJoin(validateFormJobHolder)
}
private suspend fun findOrCreateCurrency(
userWalletId: UserWalletId,
networkId: Network.ID,
derivationPath: Network.DerivationPath,
validatedForm: AddCustomTokenForm.Validated.All,
@ -96,14 +84,13 @@ internal class CustomCurrencyValidator @Inject constructor(
currentState.prevFoundOrCreatedCurrency.contractAddress == validatedForm.contractAddress
) {
// No need to search for token again if contract address is not changed
createCurrency(userWalletId, networkId, derivationPath, validatedForm)
createCurrency(networkId, derivationPath, validatedForm)
return
}
updateStatus(Status.SearchingToken)
val foundToken = findTokenUseCase(
userWalletId = userWalletId,
val foundToken = useCasesFacade.findTokenUseCase(
contractAddress = validatedForm.contractAddress,
networkId = networkId,
derivationPath = derivationPath,
@ -121,22 +108,20 @@ internal class CustomCurrencyValidator @Inject constructor(
}
if (foundToken != null) {
updateStateToValidated(userWalletId, foundToken, fillForm = true, isCustom = false)
updateStateToValidated(foundToken, fillForm = true, isCustom = false)
} else {
createCurrency(userWalletId, networkId, derivationPath, validatedForm)
createCurrency(networkId, derivationPath, validatedForm)
}
}
private suspend fun findToken(
userWalletId: UserWalletId,
networkId: Network.ID,
derivationPath: Network.DerivationPath,
validatedForm: AddCustomTokenForm.Validated.ContractAddressOnly,
) {
updateStatus(Status.SearchingToken)
val token = findTokenUseCase(
userWalletId = userWalletId,
val token = useCasesFacade.findTokenUseCase(
contractAddress = validatedForm.contractAddress,
networkId = networkId,
derivationPath = derivationPath,
@ -155,17 +140,15 @@ internal class CustomCurrencyValidator @Inject constructor(
return
}
updateStateToValidated(userWalletId, token, fillForm = true, isCustom = false)
updateStateToValidated(token, fillForm = true, isCustom = false)
}
private suspend fun createCurrency(
userWalletId: UserWalletId,
networkId: Network.ID,
derivationPath: Network.DerivationPath,
validatedForm: AddCustomTokenForm.Validated.All?,
) {
val currency = createCryptoCurrencyUseCase(
userWalletId = userWalletId,
val currency = useCasesFacade.createCryptoCurrencyUseCase(
networkId = networkId,
derivationPath = derivationPath,
formValues = validatedForm,
@ -175,20 +158,14 @@ internal class CustomCurrencyValidator @Inject constructor(
return
}
updateStateToValidated(userWalletId, currency, fillForm = false, isCustom = validatedForm != null)
updateStateToValidated(currency, fillForm = false, isCustom = validatedForm != null)
}
private suspend fun updateStateToValidated(
userWalletId: UserWalletId,
currency: CryptoCurrency,
fillForm: Boolean,
isCustom: Boolean,
) {
private suspend fun updateStateToValidated(currency: CryptoCurrency, fillForm: Boolean, isCustom: Boolean) {
val currentStatus = state.value.status
if (currentStatus is Status.Validated && currentStatus.currency == currency) return
val isNotAdded = checkIsCurrencyNotAddedUseCase(
userWalletId = userWalletId,
val isNotAdded = useCasesFacade.checkIsCurrencyNotAddedUseCase(
networkId = currency.network.id,
derivationPath = currency.network.derivationPath,
contractAddress = when (currency) {

View file

@ -0,0 +1,117 @@
package com.tangem.features.managetokens.utils.list
import arrow.core.Either
import arrow.core.NonEmptyList
import com.tangem.domain.managetokens.CheckIsCurrencyNotAddedUseCase
import com.tangem.domain.managetokens.CreateCryptoCurrencyUseCase
import com.tangem.domain.managetokens.FindTokenUseCase
import com.tangem.domain.managetokens.ValidateTokenFormUseCase
import com.tangem.domain.managetokens.model.AddCustomTokenForm
import com.tangem.domain.managetokens.model.exceptoin.CustomTokenFormValidationException
import com.tangem.domain.managetokens.model.exceptoin.FindTokenException
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.network.Network
import com.tangem.domain.tokens.AddCryptoCurrenciesUseCase
import com.tangem.domain.wallets.usecase.BackendId
import com.tangem.domain.wallets.usecase.DerivePublicKeysUseCase
import com.tangem.domain.wallets.usecase.HasMissedDerivationsUseCase
import com.tangem.features.managetokens.component.AddCustomTokenMode
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
@Suppress("LongParameterList")
internal class CustomTokenFormUseCasesFacade @AssistedInject constructor(
private val hasMissedDerivationsUseCase: HasMissedDerivationsUseCase,
private val addCryptoCurrenciesUseCase: AddCryptoCurrenciesUseCase,
private val derivePublicKeysUseCase: DerivePublicKeysUseCase,
private val validateTokenFormUseCase: ValidateTokenFormUseCase,
private val createCryptoCurrencyUseCase: CreateCryptoCurrencyUseCase,
private val findTokenUseCase: FindTokenUseCase,
private val checkIsCurrencyNotAddedUseCase: CheckIsCurrencyNotAddedUseCase,
@Assisted private val mode: AddCustomTokenMode,
) {
suspend fun hasMissedDerivationsUseCase(networksWithDerivationPath: Map<BackendId, String?>): Boolean =
when (mode) {
is AddCustomTokenMode.Account -> TODO("Account")
is AddCustomTokenMode.Wallet -> hasMissedDerivationsUseCase.invoke(
userWalletId = mode.userWalletId,
networksWithDerivationPath = networksWithDerivationPath,
)
}
suspend fun addCryptoCurrenciesUseCase(currency: CryptoCurrency): Either<Throwable, Unit> = when (mode) {
is AddCustomTokenMode.Account -> TODO("Account")
is AddCustomTokenMode.Wallet -> addCryptoCurrenciesUseCase.invoke(
userWalletId = mode.userWalletId,
currency = currency,
)
}
suspend fun derivePublicKeysUseCase(currencies: List<CryptoCurrency>): Either<Throwable, Unit> = when (mode) {
is AddCustomTokenMode.Account -> TODO("Account")
is AddCustomTokenMode.Wallet -> derivePublicKeysUseCase.invoke(
userWalletId = mode.userWalletId,
currencies = currencies,
)
}
suspend fun checkIsCurrencyNotAddedUseCase(
networkId: Network.ID,
derivationPath: Network.DerivationPath,
contractAddress: String?,
): Either<Throwable, Boolean> = when (mode) {
is AddCustomTokenMode.Account -> TODO("Account")
is AddCustomTokenMode.Wallet -> checkIsCurrencyNotAddedUseCase.invoke(
userWalletId = mode.userWalletId,
networkId = networkId,
derivationPath = derivationPath,
contractAddress = contractAddress,
)
}
suspend fun createCryptoCurrencyUseCase(
networkId: Network.ID,
derivationPath: Network.DerivationPath,
formValues: AddCustomTokenForm.Validated.All?,
): Either<Throwable, CryptoCurrency> = when (mode) {
is AddCustomTokenMode.Account -> TODO("Account")
is AddCustomTokenMode.Wallet -> createCryptoCurrencyUseCase.invoke(
userWalletId = mode.userWalletId,
networkId = networkId,
derivationPath = derivationPath,
formValues = formValues,
)
}
suspend fun findTokenUseCase(
contractAddress: String,
networkId: Network.ID,
derivationPath: Network.DerivationPath,
): Either<FindTokenException, CryptoCurrency.Token> = when (mode) {
is AddCustomTokenMode.Account -> TODO("Account")
is AddCustomTokenMode.Wallet -> findTokenUseCase.invoke(
userWalletId = mode.userWalletId,
contractAddress = contractAddress,
networkId = networkId,
derivationPath = derivationPath,
)
}
suspend fun validateTokenFormUseCase(
networkId: Network.ID,
formValues: AddCustomTokenForm.Raw,
): Either<NonEmptyList<CustomTokenFormValidationException>, AddCustomTokenForm.Validated> = when (mode) {
is AddCustomTokenMode.Account -> TODO("Account")
is AddCustomTokenMode.Wallet -> validateTokenFormUseCase.invoke(
networkId = networkId,
formValues = formValues,
)
}
@AssistedFactory
interface Factory {
fun create(mode: AddCustomTokenMode): CustomTokenFormUseCasesFacade
}
}

View file

@ -9,13 +9,14 @@ import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
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.managetokens.*
import com.tangem.domain.managetokens.model.*
import com.tangem.domain.managetokens.model.CurrencyUnsupportedState
import com.tangem.domain.managetokens.model.ManageTokensListBatchingContext
import com.tangem.domain.managetokens.model.ManageTokensUpdateAction
import com.tangem.domain.managetokens.model.ManagedCryptoCurrency
import com.tangem.domain.models.network.Network
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.features.managetokens.analytics.ManageTokensAnalyticEvent
import com.tangem.features.managetokens.component.ManageTokensMode
import com.tangem.features.managetokens.component.ManageTokensSource
import com.tangem.features.managetokens.entity.item.CurrencyItemUM
import com.tangem.features.managetokens.impl.R
@ -24,7 +25,6 @@ import com.tangem.pagination.Batch
import com.tangem.pagination.BatchAction
import com.tangem.pagination.BatchListState
import com.tangem.pagination.PaginationStatus
import com.tangem.utils.Provider
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.coroutines.JobHolder
import com.tangem.utils.coroutines.saveIn
@ -42,37 +42,36 @@ import timber.log.Timber
@Suppress("LongParameterList", "LargeClass")
internal class ManageTokensListManager @AssistedInject constructor(
private val getManagedTokensUseCase: GetManagedTokensUseCase,
private val getDistinctManagedTokensUseCase: GetDistinctManagedCurrenciesUseCase,
private val checkHasLinkedTokensUseCase: CheckHasLinkedTokensUseCase,
private val removeCustomCurrencyUseCase: RemoveCustomManagedCryptoCurrencyUseCase,
private val checkCurrencyUnsupportedUseCase: CheckCurrencyUnsupportedUseCase,
private val messageSender: UiMessageSender,
private val dispatchers: CoroutineDispatcherProvider,
private val analyticsEventHandler: AnalyticsEventHandler,
private val clipboardManager: ClipboardManager,
manageTokensWarningDelegateFactory: ManageTokensWarningDelegate.Factory,
@Assisted private val useCasesFacade: ManageTokensUseCasesFacade,
@Assisted private val source: ManageTokensSource,
@Assisted private val mode: ManageTokensMode,
@Assisted private val scope: CoroutineScope,
@Assisted private val onCurrencySelect: (ManagedCryptoCurrency.Token) -> Unit = {},
) : ManageTokensUiActions {
private lateinit var scope: CoroutineScope
private lateinit var source: ManageTokensSource
private val jobHolder = JobHolder()
private val actionsFlow: MutableSharedFlow<ManageTokensBatchAction> = MutableSharedFlow(
replay = 1,
onBufferOverflow = BufferOverflow.DROP_OLDEST,
)
private val state: MutableStateFlow<ManageTokensListState> = MutableStateFlow(ManageTokensListState())
private val state: MutableStateFlow<ManageTokensListState> =
MutableStateFlow(ManageTokensListState(mode = mode))
private val manageTokensWarningDelegate: ManageTokensWarningDelegate = manageTokensWarningDelegateFactory
.create(mode, source, this)
private val changedCurrenciesManager = ChangedCurrenciesManager()
private val uiManager = ManageTokensUiManager(
state = state,
messageSender = messageSender,
manageTokensWarningDelegate = manageTokensWarningDelegate,
dispatchers = dispatchers,
actions = this,
scopeProvider = Provider { scope },
sourceProvider = Provider { source },
scope = scope,
)
val currenciesToAdd: StateFlow<ChangedCurrencies> = changedCurrenciesManager.currenciesToAdd.asStateFlow()
@ -84,62 +83,61 @@ internal class ManageTokensListManager @AssistedInject constructor(
.distinctUntilChanged()
val uiItems: Flow<ImmutableList<CurrencyItemUM>> = uiManager.items
suspend fun launchPagination(source: ManageTokensSource, userWalletId: UserWalletId?) = coroutineScope {
scope = this
this@ManageTokensListManager.source = source
val batchFlow = getManagedTokensUseCase(
suspend fun launchPagination() = coroutineScope {
val loadUserTokensFromRemote = when (mode) {
is ManageTokensMode.Wallet -> source == ManageTokensSource.ONBOARDING
is ManageTokensMode.Account,
ManageTokensMode.None,
-> false
}
val batchFlow = useCasesFacade.getManagedTokensUseCase(
context = ManageTokensListBatchingContext(
actionsFlow = actionsFlow,
coroutineScope = this,
),
// only for onboarding case, change carefully and check repository implementation
loadUserTokensFromRemote = userWalletId != null && source == ManageTokensSource.ONBOARDING,
loadUserTokensFromRemote = loadUserTokensFromRemote,
)
batchFlow.state
.onEach { state -> updateState(state, userWalletId) }
.onEach { state -> updateState(state) }
.flowOn(dispatchers.default)
.launchIn(scope = this)
.saveIn(jobHolder)
// Initial load
reload(userWalletId)
reload()
}
suspend fun reload(userWalletId: UserWalletId?) {
state.value = ManageTokensListState()
suspend fun reload() {
state.value = ManageTokensListState(mode = mode)
actionsFlow.emit(
BatchAction.Reload(
requestParams = ManageTokensListConfig(userWalletId, searchText = null),
requestParams = useCasesFacade.manageTokensListConfig(searchText = null),
),
)
}
suspend fun loadMore(userWalletId: UserWalletId?, query: String) {
suspend fun loadMore(query: String) {
actionsFlow.emit(
BatchAction.LoadMore(
requestParams = ManageTokensListConfig(userWalletId, query),
requestParams = useCasesFacade.manageTokensListConfig(query),
),
)
}
suspend fun search(userWalletId: UserWalletId?, query: String) {
state.value = ManageTokensListState(searchQuery = query)
suspend fun search(query: String) {
state.value = ManageTokensListState(mode = mode, searchQuery = query)
actionsFlow.emit(
BatchAction.Reload(
requestParams = ManageTokensListConfig(
userWalletId = userWalletId,
requestParams = useCasesFacade.manageTokensListConfig(
searchText = query,
),
),
)
}
private fun updateState(
batchListState: BatchListState<Int, List<ManagedCryptoCurrency>>,
userWalletId: UserWalletId?,
) {
private fun updateState(batchListState: BatchListState<Int, List<ManagedCryptoCurrency>>) {
state.update { state ->
state.copy(
status = batchListState.status,
@ -154,7 +152,6 @@ internal class ManageTokensListManager @AssistedInject constructor(
) {
state.update { state ->
state.copy(
userWalletId = userWalletId,
currencyBatches = emptyList(),
uiBatches = listOf(
Batch(
@ -170,7 +167,7 @@ internal class ManageTokensListManager @AssistedInject constructor(
scope.launch {
state.update { state ->
val newBatches = getDistinctManagedTokensUseCase(batchListState.data)
val newBatches = useCasesFacade.getDistinctManagedTokensUseCase(batchListState.data)
val currentBatches = state.currencyBatches
// Distinct until changed
@ -181,9 +178,13 @@ internal class ManageTokensListManager @AssistedInject constructor(
return@launch
}
val canEditItems = userWalletId != null
val canEditItems = when (state.mode) {
is ManageTokensMode.Account,
is ManageTokensMode.Wallet,
-> true
ManageTokensMode.None -> false
}
state.copy(
userWalletId = userWalletId,
currencyBatches = newBatches,
uiBatches = uiManager.createOrUpdateUiBatches(newBatches, canEditItems),
canEditItems = canEditItems,
@ -216,10 +217,10 @@ internal class ManageTokensListManager @AssistedInject constructor(
sendSelectCurrencyAnalyticsEvent(currency, isSelected = false)
}
override fun removeCustomCurrency(userWalletId: UserWalletId, currency: ManagedCryptoCurrency.Custom) {
override fun removeCustomCurrency(currency: ManagedCryptoCurrency.Custom) {
scope.launch {
removeCustomCurrencyUseCase.invoke(userWalletId, currency)
.onRight { reload(userWalletId) }
useCasesFacade.removeCustomCurrencyUseCase(currency)
.onRight { reload() }
.onLeft { Timber.e(it) }
}
}
@ -258,9 +259,8 @@ internal class ManageTokensListManager @AssistedInject constructor(
actionsFlow.tryEmit(action)
}
override suspend fun checkHasLinkedTokens(userWalletId: UserWalletId, network: Network): Boolean {
return checkHasLinkedTokensUseCase(
userWalletId = userWalletId,
override suspend fun checkHasLinkedTokens(network: Network): Boolean {
return useCasesFacade.checkHasLinkedTokensUseCase(
network = network,
tempAddedTokens = changedCurrenciesManager.currenciesToAdd.value,
tempRemovedTokens = changedCurrenciesManager.currenciesToRemove.value,
@ -269,7 +269,7 @@ internal class ManageTokensListManager @AssistedInject constructor(
it,
"""
Failed to check linked tokens
|- User wallet ID: $userWalletId
|- Mode: $mode
|- Network ID: ${network.id}
""".trimIndent(),
)
@ -286,18 +286,16 @@ internal class ManageTokensListManager @AssistedInject constructor(
}
override suspend fun checkCurrencyUnsupportedState(
userWalletId: UserWalletId,
sourceNetwork: ManagedCryptoCurrency.SourceNetwork,
): CurrencyUnsupportedState? {
return checkCurrencyUnsupportedUseCase(
userWalletId = userWalletId,
return useCasesFacade.checkCurrencyUnsupportedUseCase(
sourceNetwork = sourceNetwork,
).getOrElse {
Timber.e(
it,
"""
Failed to check currency unsupported state
|- User wallet ID: $userWalletId
|- Mode: $mode
|- Source Network: $sourceNetwork
""".trimIndent(),
)
@ -359,8 +357,7 @@ internal class ManageTokensListManager @AssistedInject constructor(
if (currency !is ManagedCryptoCurrency.Token) return@launch
if (isSelected) {
val userWalletId = state.value.userWalletId
val unsupportedState = userWalletId?.let { checkCurrencyUnsupportedState(it, source) }
val unsupportedState = checkCurrencyUnsupportedState(source)
if (unsupportedState != null) {
showUnsupportedWarning(unsupportedState)
} else {
@ -368,7 +365,7 @@ internal class ManageTokensListManager @AssistedInject constructor(
}
} else {
if (checkNeedToShowRemoveNetworkWarning(currency, source.network)) {
showRemoveNetworkWarning(
manageTokensWarningDelegate.showRemoveNetworkWarning(
currency = currency,
network = source.network,
isCoin = source is ManagedCryptoCurrency.SourceNetwork.Main,
@ -404,67 +401,6 @@ internal class ManageTokensListManager @AssistedInject constructor(
messageSender.send(message)
}
private suspend fun showRemoveNetworkWarning(
currency: ManagedCryptoCurrency,
network: Network,
isCoin: Boolean,
onConfirm: () -> Unit,
) {
val userWalletId = state.value.userWalletId
val hasLinkedTokens = if (userWalletId == null || !isCoin) {
false
} else {
checkHasLinkedTokens(userWalletId, network)
}
val canHideWithoutConfirming = source == ManageTokensSource.ONBOARDING
if (hasLinkedTokens) {
showLinkedTokensWarning(currency, network)
} else if (canHideWithoutConfirming) {
onConfirm()
} else {
showHideTokenWarning(currency, onConfirm)
}
}
private fun showLinkedTokensWarning(currency: ManagedCryptoCurrency, network: Network) {
val message = DialogMessage(
title = resourceReference(
id = R.string.token_details_unable_hide_alert_title,
formatArgs = wrappedList(currency.name),
),
message = resourceReference(
id = R.string.token_details_unable_hide_alert_message,
formatArgs = wrappedList(
currency.name,
currency.symbol,
network.name,
),
),
)
messageSender.send(message)
}
private fun showHideTokenWarning(currency: ManagedCryptoCurrency, onConfirm: () -> Unit) {
val message = DialogMessage(
title = resourceReference(
id = R.string.token_details_hide_alert_title,
formatArgs = wrappedList(currency.name),
),
message = resourceReference(R.string.token_details_hide_alert_message),
firstActionBuilder = {
EventMessageAction(
title = resourceReference(R.string.token_details_hide_alert_hide),
warning = true,
onClick = onConfirm,
)
},
secondActionBuilder = { cancelAction() },
)
messageSender.send(message)
}
private fun Batch<Int, List<ManagedCryptoCurrency>>.currencyIndexById(id: ManagedCryptoCurrency.ID): Int {
return data
.indexOfFirst { it.id == id }
@ -474,6 +410,12 @@ internal class ManageTokensListManager @AssistedInject constructor(
@AssistedFactory
interface Factory {
fun create(onCurrencySelect: (ManagedCryptoCurrency.Token) -> Unit = {}): ManageTokensListManager
fun create(
scope: CoroutineScope,
mode: ManageTokensMode,
source: ManageTokensSource,
useCasesFacade: ManageTokensUseCasesFacade,
onCurrencySelect: (ManagedCryptoCurrency.Token) -> Unit = {},
): ManageTokensListManager
}
}

View file

@ -3,7 +3,7 @@ package com.tangem.features.managetokens.utils.list
import com.tangem.domain.managetokens.model.ManageTokensListConfig
import com.tangem.domain.managetokens.model.ManageTokensUpdateAction
import com.tangem.domain.managetokens.model.ManagedCryptoCurrency
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.features.managetokens.component.ManageTokensMode
import com.tangem.features.managetokens.entity.item.CurrencyItemUM
import com.tangem.pagination.Batch
import com.tangem.pagination.BatchAction
@ -13,7 +13,7 @@ internal typealias ManageTokensBatchAction = BatchAction<Int, ManageTokensListCo
internal data class ManageTokensListState(
val status: PaginationStatus<*> = PaginationStatus.None,
val userWalletId: UserWalletId? = null,
val mode: ManageTokensMode,
val uiBatches: List<Batch<Int, List<CurrencyItemUM>>> = mutableListOf(),
val currencyBatches: List<Batch<Int, List<ManagedCryptoCurrency>>> = mutableListOf(),
val canEditItems: Boolean = true,

View file

@ -3,7 +3,6 @@ package com.tangem.features.managetokens.utils.list
import com.tangem.domain.managetokens.model.CurrencyUnsupportedState
import com.tangem.domain.managetokens.model.ManagedCryptoCurrency
import com.tangem.domain.models.network.Network
import com.tangem.domain.models.wallet.UserWalletId
internal interface ManageTokensUiActions {
@ -13,14 +12,13 @@ internal interface ManageTokensUiActions {
fun removeCurrency(batchKey: Int, currency: ManagedCryptoCurrency.Token, network: Network)
fun removeCustomCurrency(userWalletId: UserWalletId, currency: ManagedCryptoCurrency.Custom)
fun removeCustomCurrency(currency: ManagedCryptoCurrency.Custom)
fun checkNeedToShowRemoveNetworkWarning(currency: ManagedCryptoCurrency.Token, network: Network): Boolean
suspend fun checkHasLinkedTokens(userWalletId: UserWalletId, network: Network): Boolean
suspend fun checkHasLinkedTokens(network: Network): Boolean
suspend fun checkCurrencyUnsupportedState(
userWalletId: UserWalletId,
sourceNetwork: ManagedCryptoCurrency.SourceNetwork,
): CurrencyUnsupportedState?
}

View file

@ -1,19 +1,10 @@
package com.tangem.features.managetokens.utils.list
import com.tangem.core.decompose.ui.UiMessageSender
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.core.ui.message.DialogMessage
import com.tangem.core.ui.message.EventMessageAction
import com.tangem.domain.managetokens.model.ManagedCryptoCurrency
import com.tangem.domain.models.network.Network
import com.tangem.features.managetokens.component.ManageTokensSource
import com.tangem.features.managetokens.entity.item.CurrencyItemUM
import com.tangem.features.managetokens.impl.R
import com.tangem.features.managetokens.utils.mapper.toUiModel
import com.tangem.features.managetokens.utils.ui.update
import com.tangem.pagination.Batch
import com.tangem.utils.Provider
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.extensions.addOrReplace
import kotlinx.collections.immutable.ImmutableList
@ -29,19 +20,12 @@ import kotlinx.coroutines.launch
@Suppress("LongParameterList")
internal class ManageTokensUiManager(
private val state: MutableStateFlow<ManageTokensListState>,
private val messageSender: UiMessageSender,
private val dispatchers: CoroutineDispatcherProvider,
private val scopeProvider: Provider<CoroutineScope>,
private val sourceProvider: Provider<ManageTokensSource>,
private val scope: CoroutineScope,
private val actions: ManageTokensUiActions,
private val manageTokensWarningDelegate: ManageTokensWarningDelegate,
) {
private val scope: CoroutineScope
get() = scopeProvider()
private val source: ManageTokensSource
get() = sourceProvider()
@OptIn(ExperimentalCoroutinesApi::class)
val items: Flow<ImmutableList<CurrencyItemUM>> = state
.mapLatest { state ->
@ -109,75 +93,11 @@ internal class ManageTokensUiManager(
}
private fun removeCustomCurrency(currency: ManagedCryptoCurrency.Custom) = scope.launch(dispatchers.default) {
showRemoveNetworkWarning(
manageTokensWarningDelegate.showRemoveNetworkWarning(
currency = currency,
network = currency.network,
isCoin = currency is ManagedCryptoCurrency.Custom.Coin,
onConfirm = {
val userWalletId = requireNotNull(state.value.userWalletId) { "UserWalletId is null. Can not remove" }
actions.removeCustomCurrency(userWalletId = userWalletId, currency = currency)
},
onConfirm = { actions.removeCustomCurrency(currency = currency) },
)
}
private suspend fun showRemoveNetworkWarning(
currency: ManagedCryptoCurrency,
network: Network,
isCoin: Boolean,
onConfirm: () -> Unit,
) {
val userWalletId = state.value.userWalletId
val hasLinkedTokens = if (userWalletId == null || !isCoin) {
false
} else {
actions.checkHasLinkedTokens(userWalletId, network)
}
val canHideWithoutConfirming = source == ManageTokensSource.ONBOARDING
if (hasLinkedTokens) {
showLinkedTokensWarning(currency, network)
} else if (canHideWithoutConfirming) {
onConfirm()
} else {
showHideTokenWarning(currency, onConfirm)
}
}
private fun showLinkedTokensWarning(currency: ManagedCryptoCurrency, network: Network) {
val message = DialogMessage(
title = resourceReference(
id = R.string.token_details_unable_hide_alert_title,
formatArgs = wrappedList(currency.name),
),
message = resourceReference(
id = R.string.token_details_unable_hide_alert_message,
formatArgs = wrappedList(
currency.name,
currency.symbol,
network.name,
),
),
)
messageSender.send(message)
}
private fun showHideTokenWarning(currency: ManagedCryptoCurrency, onConfirm: () -> Unit) {
val message = DialogMessage(
title = resourceReference(
id = R.string.token_details_hide_alert_title,
formatArgs = wrappedList(currency.name),
),
message = resourceReference(R.string.token_details_hide_alert_message),
firstActionBuilder = {
EventMessageAction(
title = resourceReference(R.string.token_details_hide_alert_hide),
warning = true,
onClick = onConfirm,
)
},
secondActionBuilder = { cancelAction() },
)
messageSender.send(message)
}
}

View file

@ -0,0 +1,108 @@
package com.tangem.features.managetokens.utils.list
import arrow.core.Either
import arrow.core.left
import com.tangem.domain.managetokens.*
import com.tangem.domain.managetokens.model.CurrencyUnsupportedState
import com.tangem.domain.managetokens.model.ManageTokensListConfig
import com.tangem.domain.managetokens.model.ManagedCryptoCurrency
import com.tangem.domain.models.network.Network
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.wallets.usecase.HasMissedDerivationsUseCase
import com.tangem.features.managetokens.component.ManageTokensMode
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
@Suppress("LongParameterList")
internal class ManageTokensUseCasesFacade @AssistedInject constructor(
val getManagedTokensUseCase: GetManagedTokensUseCase,
val getDistinctManagedTokensUseCase: GetDistinctManagedCurrenciesUseCase,
private val checkHasLinkedTokensUseCase: CheckHasLinkedTokensUseCase,
private val removeCustomCurrencyUseCase: RemoveCustomManagedCryptoCurrencyUseCase,
private val checkCurrencyUnsupportedUseCase: CheckCurrencyUnsupportedUseCase,
private val hasMissedDerivationsUseCase: HasMissedDerivationsUseCase,
private val saveManagedTokensUseCase: SaveManagedTokensUseCase,
@Assisted private val mode: ManageTokensMode,
) {
private val nonePortfolioError: IllegalStateException
get() = IllegalStateException("Unsupported")
fun manageTokensListConfig(searchText: String?): ManageTokensListConfig {
val userWalletId: UserWalletId? = when (mode) {
is ManageTokensMode.Account -> TODO("Account")
ManageTokensMode.None -> null
is ManageTokensMode.Wallet -> mode.userWalletId
}
return ManageTokensListConfig(userWalletId, searchText)
}
suspend fun removeCustomCurrencyUseCase(customCurrency: ManagedCryptoCurrency.Custom): Either<Throwable, Unit> {
return when (mode) {
is ManageTokensMode.Account -> TODO("Account")
is ManageTokensMode.Wallet -> removeCustomCurrencyUseCase.invoke(
userWalletId = mode.userWalletId,
customCurrency = customCurrency,
)
ManageTokensMode.None -> nonePortfolioError.left()
}
}
suspend fun checkHasLinkedTokensUseCase(
network: Network,
tempAddedTokens: Map<ManagedCryptoCurrency.Token, Set<Network>>,
tempRemovedTokens: Map<ManagedCryptoCurrency.Token, Set<Network>>,
): Either<Throwable, Boolean> {
return when (mode) {
is ManageTokensMode.Account -> TODO("Account")
is ManageTokensMode.Wallet -> checkHasLinkedTokensUseCase.invoke(
userWalletId = mode.userWalletId,
network = network,
tempAddedTokens = tempAddedTokens,
tempRemovedTokens = tempRemovedTokens,
)
ManageTokensMode.None -> nonePortfolioError.left()
}
}
suspend fun checkCurrencyUnsupportedUseCase(
sourceNetwork: ManagedCryptoCurrency.SourceNetwork,
): Either<Throwable, CurrencyUnsupportedState?> {
return when (mode) {
is ManageTokensMode.Account -> TODO("Account")
is ManageTokensMode.Wallet -> checkCurrencyUnsupportedUseCase.invoke(
userWalletId = mode.userWalletId,
sourceNetwork = sourceNetwork,
)
ManageTokensMode.None -> nonePortfolioError.left()
}
}
suspend fun hasMissedDerivationsUseCase(network: Map<String, Nothing?>): Boolean = when (mode) {
is ManageTokensMode.Account -> TODO("Account")
is ManageTokensMode.Wallet -> hasMissedDerivationsUseCase.invoke(
userWalletId = mode.userWalletId,
networksWithDerivationPath = network,
)
ManageTokensMode.None -> false
}
suspend fun saveManagedTokensUseCase(
currenciesToAdd: Map<ManagedCryptoCurrency.Token, Set<Network>>,
currenciesToRemove: Map<ManagedCryptoCurrency.Token, Set<Network>>,
): Either<Throwable, Unit> = when (mode) {
is ManageTokensMode.Account -> TODO("Account")
is ManageTokensMode.Wallet -> saveManagedTokensUseCase.invoke(
userWalletId = mode.userWalletId,
currenciesToAdd = currenciesToAdd,
currenciesToRemove = currenciesToRemove,
)
ManageTokensMode.None -> nonePortfolioError.left()
}
@AssistedFactory
interface Factory {
fun create(mode: ManageTokensMode): ManageTokensUseCasesFacade
}
}

View file

@ -0,0 +1,98 @@
package com.tangem.features.managetokens.utils.list
import com.tangem.core.decompose.ui.UiMessageSender
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.core.ui.message.DialogMessage
import com.tangem.core.ui.message.EventMessageAction
import com.tangem.domain.managetokens.model.ManagedCryptoCurrency
import com.tangem.domain.models.network.Network
import com.tangem.features.managetokens.component.ManageTokensMode
import com.tangem.features.managetokens.component.ManageTokensSource
import com.tangem.features.managetokens.impl.R
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
internal class ManageTokensWarningDelegate @AssistedInject constructor(
private val messageSender: UiMessageSender,
@Assisted private val mode: ManageTokensMode,
@Assisted private val source: ManageTokensSource,
@Assisted private val uiActions: ManageTokensUiActions,
) {
suspend fun showRemoveNetworkWarning(
currency: ManagedCryptoCurrency,
network: Network,
isCoin: Boolean,
onConfirm: () -> Unit,
) {
val isNonePortfolio = when (mode) {
ManageTokensMode.None -> true
is ManageTokensMode.Account,
is ManageTokensMode.Wallet,
-> false
}
val hasLinkedTokens = if (isNonePortfolio || !isCoin) {
false
} else {
uiActions.checkHasLinkedTokens(network)
}
val canHideWithoutConfirming = source == ManageTokensSource.ONBOARDING
if (hasLinkedTokens) {
showLinkedTokensWarning(currency, network)
} else if (canHideWithoutConfirming) {
onConfirm()
} else {
showHideTokenWarning(currency, onConfirm)
}
}
private fun showLinkedTokensWarning(currency: ManagedCryptoCurrency, network: Network) {
val message = DialogMessage(
title = resourceReference(
id = R.string.token_details_unable_hide_alert_title,
formatArgs = wrappedList(currency.name),
),
message = resourceReference(
id = R.string.token_details_unable_hide_alert_message,
formatArgs = wrappedList(
currency.name,
currency.symbol,
network.name,
),
),
)
messageSender.send(message)
}
private fun showHideTokenWarning(currency: ManagedCryptoCurrency, onConfirm: () -> Unit) {
val message = DialogMessage(
title = resourceReference(
id = R.string.token_details_hide_alert_title,
formatArgs = wrappedList(currency.name),
),
message = resourceReference(R.string.token_details_hide_alert_message),
firstActionBuilder = {
EventMessageAction(
title = resourceReference(R.string.token_details_hide_alert_hide),
warning = true,
onClick = onConfirm,
)
},
secondActionBuilder = { cancelAction() },
)
messageSender.send(message)
}
@AssistedFactory
interface Factory {
fun create(
mode: ManageTokensMode,
source: ManageTokensSource,
uiActions: ManageTokensUiActions,
): ManageTokensWarningDelegate
}
}