Updated on 2026-08-14

This commit is contained in:
Tangem 2026-06-16 18:10:00 +04:00
parent 2e8d0bc03b
commit 298e7a5565
29 changed files with 687 additions and 43 deletions

View file

@ -18,6 +18,7 @@ dependencies {
/* Project - API */
implementation(projects.features.manageTokens.api)
implementation(projects.features.swapV2.api)
implementation(projects.features.commonFeatures.api)
/* Project - Core */
implementation(projects.core.decompose)
@ -39,6 +40,7 @@ dependencies {
implementation(projects.domain.wallets)
implementation(projects.domain.wallets.models)
implementation(projects.domain.swap.models)
implementation(projects.domain.markets.models)
implementation(projects.domain.notifications)
implementation(projects.domain.dynamicAddresses)

View file

@ -12,6 +12,7 @@ import com.tangem.core.decompose.context.AppComponentContext
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.commonfeatures.api.addtoportfolio.AddToPortfolioComponent
import com.tangem.features.managetokens.choosetoken.entity.ChooseManageTokensBottomSheetConfig
import com.tangem.features.managetokens.choosetoken.model.ChooseManagedTokensModel
import com.tangem.features.managetokens.choosetoken.ui.ChooseManagedTokenContent
@ -28,6 +29,7 @@ internal class DefaultChooseManagedTokensComponent @AssistedInject constructor(
@Assisted private val params: ChooseManagedTokensComponent.Params,
private val swapChooseTokenNetworkFactory: SwapChooseTokenNetworkComponent.Factory,
private val swapChooseTokenNetworkTrigger: SwapChooseTokenNetworkTrigger,
private val addToPortfolioComponentFactory: AddToPortfolioComponent.Factory,
) : ChooseManagedTokensComponent, AppComponentContext by context {
private val model: ChooseManagedTokensModel = getOrCreateModel(params)
@ -76,8 +78,15 @@ internal class DefaultChooseManagedTokensComponent @AssistedInject constructor(
params.callback?.onResult() ?: router.pop()
}
},
onSwapClick = { toCryptoCurrency ->
model.onSwapTokenClick(token = config.token, targetCurrency = toCryptoCurrency)
},
),
)
ChooseManageTokensBottomSheetConfig.AddToPortfolioBottomSheetConfig -> addToPortfolioComponentFactory.create(
context = childByContext(componentContext),
params = AddToPortfolioComponent.Params(addToPortfolioManager = model.addToPortfolioManager),
)
}
@AssistedFactory

View file

@ -16,4 +16,8 @@ internal sealed class ChooseManageTokensBottomSheetConfig {
val token: ManagedCryptoCurrency.Token,
val isSearchedToken: Boolean,
) : ChooseManageTokensBottomSheetConfig()
/** Add the swap target token to the portfolio before opening the regular Swap screen. */
@Serializable
data object AddToPortfolioBottomSheetConfig : ChooseManageTokensBottomSheetConfig()
}

View file

@ -3,6 +3,8 @@ package com.tangem.features.managetokens.choosetoken.model
import androidx.annotation.StringRes
import com.arkivanov.decompose.router.slot.SlotNavigation
import com.arkivanov.decompose.router.slot.activate
import com.arkivanov.decompose.router.slot.dismiss
import com.tangem.common.routing.AppRoute
import com.tangem.common.ui.notifications.NotificationId
import com.tangem.common.ui.notifications.NotificationUM
import com.tangem.core.analytics.api.AnalyticsEventHandler
@ -11,7 +13,13 @@ import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.core.decompose.navigation.Router
import com.tangem.core.decompose.ui.UiMessageSender
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.ui.components.fields.entity.SearchBarUM
import com.tangem.domain.managetokens.model.ManagedCryptoCurrency
import com.tangem.domain.markets.RawMarketToken
import com.tangem.domain.markets.TokenMarketInfo
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.features.commonfeatures.api.addtoportfolio.AddToPortfolioManager
import com.tangem.core.ui.event.consumedEvent
import com.tangem.core.ui.event.triggeredEvent
import com.tangem.core.ui.extensions.resourceReference
@ -55,10 +63,19 @@ internal class ChooseManagedTokensModel @Inject constructor(
paramsContainer: ParamsContainer,
manageTokensUseCasesFacadeFactory: ManageTokensUseCasesFacade.Factory,
manageTokensListManagerFactory: ManageTokensListManager.Factory,
addToPortfolioManagerFactory: AddToPortfolioManager.Factory,
) : Model() {
private val params: ChooseManagedTokensComponent.Params = paramsContainer.require()
val addToPortfolioManager: AddToPortfolioManager = addToPortfolioManagerFactory.create(
scope = modelScope,
settings = AddToPortfolioManager.Settings.ChooseToken,
analyticsParams = AddToPortfolioManager.AnalyticsParams(
source = AnalyticsParam.ScreensSources.Send.value,
),
)
private val manageTokensMode = ManageTokensMode.Account(params.userWalletId)
private val useCasesFacade: ManageTokensUseCasesFacade = manageTokensUseCasesFacadeFactory
@ -97,11 +114,63 @@ internal class ChooseManagedTokensModel @Inject constructor(
observeSearchQueryChanges()
addToPortfolioManager.onSuccessAdded.receiveAsFlow()
.onEach { result -> navigateToSwap(toCryptoCurrency = result.addedCurrency.currency) }
.launchIn(modelScope)
addToPortfolioManager.onDismiss.receiveAsFlow()
.onEach { bottomSheetNavigation.dismiss() }
.launchIn(modelScope)
modelScope.launch {
manageTokensListManager.launchPagination(isCollapsed = false)
}
}
/**
* Invoked from the Send-with-Swap "Swap token" notice when the token is only swappable in the regular
* Swap flow. If the token isn't added to the wallet yet, shows the add-to-portfolio bottom sheet first;
* otherwise opens the Swap screen directly with [targetCurrency] pre-selected as TO.
*/
fun onSwapTokenClick(token: ManagedCryptoCurrency.Token, targetCurrency: CryptoCurrency) {
if (token.isAdded) {
navigateToSwap(toCryptoCurrency = targetCurrency)
} else {
addToPortfolioManager.setTokenNetworks(token.toMarketNetworks())
addToPortfolioManager.setTokenParams(token.toRawMarketToken())
bottomSheetNavigation.activate(ChooseManageTokensBottomSheetConfig.AddToPortfolioBottomSheetConfig)
}
}
private fun navigateToSwap(toCryptoCurrency: CryptoCurrency) {
bottomSheetNavigation.dismiss()
router.push(
AppRoute.Swap(
userWalletId = params.userWalletId,
fromCryptoCurrency = params.initialCurrency,
toCryptoCurrency = toCryptoCurrency,
screenSource = AnalyticsParam.ScreensSources.Send.value,
fromCurrencyPosition = AppRoute.Swap.CurrencyPosition.FROM,
),
)
}
private fun ManagedCryptoCurrency.Token.toRawMarketToken(): RawMarketToken = RawMarketToken(
id = CryptoCurrency.RawID(id.value),
name = name,
symbol = symbol,
)
private fun ManagedCryptoCurrency.Token.toMarketNetworks(): List<TokenMarketInfo.Network> {
return availableNetworks.map { sourceNetwork ->
TokenMarketInfo.Network(
networkId = sourceNetwork.network.rawId,
isExchangeable = false,
contractAddress = (sourceNetwork as? ManagedCryptoCurrency.SourceNetwork.Default)?.contractAddress,
decimalCount = sourceNetwork.decimals,
)
}
}
private fun createReadContentModel(): ChooseManagedTokenUM {
return ChooseManagedTokenUM(
notificationUM = getNotification(),