Updated on 2026-08-14

This commit is contained in:
Tangem 2026-04-28 14:11:23 +03:00
commit 0470e1f010
1144 changed files with 48463 additions and 11166 deletions

View file

@ -12,8 +12,9 @@ android {
}
dependencies {
/** Project - API */
implementation(projects.features.account.api)
implementation(projects.features.commonFeatures.api)
implementation(projects.features.onramp.api)
implementation(projects.features.swap.api)
implementation(projects.features.swap.domain)
@ -81,5 +82,4 @@ dependencies {
/** Other */
implementation(deps.decompose.ext.compose)
implementation(deps.kotlin.immutable.collections)
implementation(deps.reKotlin)
}

View file

@ -2,10 +2,11 @@ package com.tangem.features.onramp.deeplink
import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.AppRouter
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
import com.tangem.utils.logging.TangemLogger
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import com.tangem.utils.logging.TangemLogger
internal class DefaultSwapDeepLinkHandler @AssistedInject constructor(
router: AppRouter,
@ -19,7 +20,12 @@ internal class DefaultSwapDeepLinkHandler @AssistedInject constructor(
TangemLogger.e("Error on getting user wallet: $it")
},
ifRight = { userWallet ->
router.push(AppRoute.SwapCrypto(userWallet.walletId))
router.push(
AppRoute.Swap(
userWalletId = userWallet.walletId,
screenSource = AnalyticsParam.ScreensSources.Main.value,
),
)
},
)
}

View file

@ -31,7 +31,7 @@ import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.features.account.PortfolioSelectorComponent
import com.tangem.features.commonfeatures.api.portfolioselector.PortfolioSelectorComponent
import com.tangem.features.onramp.hottokens.model.HotCryptoModel
import com.tangem.features.onramp.hottokens.portfolio.OnrampAddToPortfolioComponent
import com.tangem.features.onramp.hottokens.portfolio.OnrampAddTokenComponent

View file

@ -1,6 +1,6 @@
package com.tangem.features.onramp.hottokens.converter
import com.tangem.core.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter
import com.tangem.common.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter
import com.tangem.core.ui.components.marketprice.PriceChangeType
import com.tangem.core.ui.components.marketprice.utils.PriceChangeConverter
import com.tangem.core.ui.components.token.state.TokenItemState

View file

@ -22,8 +22,8 @@ import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.onramp.model.HotCryptoCurrency
import com.tangem.features.account.PortfolioFetcher
import com.tangem.features.account.PortfolioSelectorController
import com.tangem.features.commonfeatures.api.portfolioselector.PortfolioFetcher
import com.tangem.features.commonfeatures.api.portfolioselector.PortfolioSelectorController
import com.tangem.features.onramp.hottokens.HotCryptoComponent
import com.tangem.features.onramp.hottokens.converter.HotTokenItemStateConverter
import com.tangem.features.onramp.hottokens.entity.HotCryptoUM

View file

@ -7,10 +7,10 @@ import com.tangem.common.ui.account.toUM
import com.tangem.common.ui.addtoken.AddTokenUM
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.core.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter
import com.tangem.common.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter
import com.tangem.core.ui.components.token.state.TokenItemState
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.iconResId
import com.tangem.common.ui.extensions.iconResId
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.domain.account.status.usecase.IsAccountsModeEnabledUseCase

View file

@ -50,7 +50,7 @@ internal class OnrampAddTokenModel @Inject constructor(
.distinctUntilChanged()
.mapLatest { tokenToAdd: AddHotCryptoData ->
addTokenJob.join()
val backendId = tokenToAdd.cryptoCurrency.network.backendId
val backendId = tokenToAdd.cryptoCurrency.network.rawId
val userWalletId = tokenToAdd.account.accountId.userWalletId
val isTangemIconVisible = needColdWalletInteraction(
walletId = userWalletId,

View file

@ -15,7 +15,7 @@ import com.tangem.core.decompose.context.child
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.feed.components.market.details.portfolio.add.AddToPortfolioComponent
import com.tangem.features.commonfeatures.api.addtoportfolio.AddToPortfolioComponent
import com.tangem.features.onramp.component.SwapSelectTokensComponent
import com.tangem.features.onramp.swap.availablepairs.AvailableSwapPairsComponent
import com.tangem.features.onramp.swap.availablepairs.model.AddToPortfolioRoute
@ -74,9 +74,7 @@ internal class DefaultSwapSelectTokensComponent @AssistedInject constructor(
return addToPortfolioComponentFactory.create(
context = childByContext(componentContext),
params = AddToPortfolioComponent.Params(
addToPortfolioManager = selectToTokenListComponent.addToPortfolioManager!!,
callback = selectToTokenListComponent.addToPortfolioCallback,
shouldSkipTokenActionsScreen = true,
addToPortfolioManager = selectToTokenListComponent.addToPortfolioManager,
),
)
}

View file

@ -7,8 +7,7 @@ import com.tangem.core.ui.components.token.state.TokenItemState
import com.tangem.core.ui.decompose.ComposableListContentComponent
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.features.feed.components.market.details.portfolio.add.AddToPortfolioComponent
import com.tangem.features.feed.components.market.details.portfolio.add.AddToPortfolioManager
import com.tangem.features.commonfeatures.api.addtoportfolio.AddToPortfolioManager
import com.tangem.features.onramp.swap.availablepairs.model.AddToPortfolioRoute
import com.tangem.features.onramp.tokenlist.entity.TokenListUM
import kotlinx.coroutines.flow.StateFlow
@ -18,8 +17,7 @@ import kotlinx.coroutines.flow.StateFlow
internal interface AvailableSwapPairsComponent : ComposableListContentComponent<TokenListUM> {
val bottomSheetNavigation: SlotNavigation<AddToPortfolioRoute>
val addToPortfolioManager: AddToPortfolioManager?
val addToPortfolioCallback: AddToPortfolioComponent.Callback
val addToPortfolioManager: AddToPortfolioManager
/** Component factory */
interface Factory : ComponentFactory<Params, AvailableSwapPairsComponent>

View file

@ -6,8 +6,7 @@ import androidx.compose.ui.Modifier
import com.arkivanov.decompose.router.slot.SlotNavigation
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.features.feed.components.market.details.portfolio.add.AddToPortfolioComponent
import com.tangem.features.feed.components.market.details.portfolio.add.AddToPortfolioManager
import com.tangem.features.commonfeatures.api.addtoportfolio.AddToPortfolioManager
import com.tangem.features.onramp.swap.availablepairs.model.AddToPortfolioRoute
import com.tangem.features.onramp.swap.availablepairs.model.AvailableSwapPairsModel
import com.tangem.features.onramp.tokenlist.entity.TokenListUM
@ -26,8 +25,7 @@ internal class DefaultAvailableSwapPairsComponent @AssistedInject constructor(
private val model: AvailableSwapPairsModel = getOrCreateModel(params)
override val bottomSheetNavigation: SlotNavigation<AddToPortfolioRoute> get() = model.bottomSheetNavigation
override val addToPortfolioManager: AddToPortfolioManager? get() = model.addToPortfolioManager
override val addToPortfolioCallback: AddToPortfolioComponent.Callback get() = model.addToPortfolioCallback
override val addToPortfolioManager: AddToPortfolioManager get() = model.addToPortfolioManager
override val uiState: StateFlow<TokenListUM>
get() = model.state

View file

@ -41,8 +41,7 @@ import com.tangem.domain.wallets.usecase.GetWalletsUseCase
import com.tangem.feature.swap.domain.GetAvailablePairsUseCase
import com.tangem.feature.swap.domain.models.domain.LeastTokenInfo
import com.tangem.feature.swap.domain.models.domain.SwapPairLeast
import com.tangem.features.feed.components.market.details.portfolio.add.AddToPortfolioComponent
import com.tangem.features.feed.components.market.details.portfolio.add.AddToPortfolioManager
import com.tangem.features.commonfeatures.api.addtoportfolio.AddToPortfolioManager
import com.tangem.features.onramp.impl.R
import com.tangem.features.onramp.swap.availablepairs.AvailableSwapPairsComponent
import com.tangem.features.onramp.swap.availablepairs.entity.transformers.SetErrorWarningTransformer
@ -62,13 +61,10 @@ import com.tangem.features.onramp.utils.ClearSearchBarTransformer
import com.tangem.features.onramp.utils.UpdateSearchBarActiveStateTransformer
import com.tangem.features.onramp.utils.UpdateSearchBarCallbacksTransformer
import com.tangem.features.onramp.utils.UpdateSearchQueryTransformer
import com.tangem.features.swap.SwapFeatureToggles
import com.tangem.lib.crypto.BlockchainUtils
import com.tangem.utils.Provider
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.coroutines.JobHolder
import com.tangem.utils.coroutines.runSuspendCatching
import com.tangem.utils.coroutines.saveIn
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
@ -93,7 +89,6 @@ internal class AvailableSwapPairsModel @Inject constructor(
private val addToPortfolioManagerFactory: AddToPortfolioManager.Factory,
private val excludedBlockchains: ExcludedBlockchains,
private val getAccountCurrencyStatusUseCase: GetAccountCurrencyStatusUseCase,
swapFeatureToggles: SwapFeatureToggles,
getWalletsUseCase: GetWalletsUseCase,
) : Model() {
@ -104,14 +99,12 @@ internal class AvailableSwapPairsModel @Inject constructor(
private val allUserWallets = getWalletsUseCase.invokeSync()
val bottomSheetNavigation: SlotNavigation<AddToPortfolioRoute> = SlotNavigation()
var addToPortfolioManager: AddToPortfolioManager? = null
val addToPortfolioCallback: AddToPortfolioComponent.Callback = object : AddToPortfolioComponent.Callback {
override fun onDismiss() = bottomSheetNavigation.dismiss()
override fun onSuccess(addedToken: CryptoCurrency) {
onTokenAddedToPortfolio(addedToken)
}
}
private val addToPortfolioJobHolder = JobHolder()
val addToPortfolioManager: AddToPortfolioManager = addToPortfolioManagerFactory
.create(
scope = modelScope,
analyticsParams = AddToPortfolioManager.AnalyticsParams(source = ScreensSources.Swap.value),
settings = AddToPortfolioManager.Settings.ChooseToken,
)
private val accountListFlow = getAccountListUseCaseFlow()
private val availablePairsByNetworkFlow = MutableStateFlow<Map<LeastTokenInfo, AvailablePairsState>>(emptyMap())
@ -155,10 +148,14 @@ internal class AvailableSwapPairsModel @Inject constructor(
subscribeOnSelectedStatusChange()
subscribeOnAvailablePairsUpdates()
if (swapFeatureToggles.isMarketListFeatureEnabled) {
subscribeOnMarketsUpdates()
subscribeOnVisibleMarketItems()
}
subscribeOnMarketsUpdates()
subscribeOnVisibleMarketItems()
addToPortfolioManager.onDismiss.receiveAsFlow()
.onEach { bottomSheetNavigation.dismiss() }
.launchIn(modelScope)
addToPortfolioManager.onSuccessAdded.receiveAsFlow()
.onEach { result -> onTokenAddedToPortfolio(result.addedCurrency.currency) }
.launchIn(modelScope)
}
private fun getAccountListUseCaseFlow(): SharedFlow<List<AccountStatus>> {
@ -251,7 +248,7 @@ internal class AvailableSwapPairsModel @Inject constructor(
.associate { accountStatus ->
val statuses = accountStatus.tokenList.flattenCurrencies()
.filterNot { status ->
status.currency.network.backendId == selectedStatus?.currency?.network?.backendId &&
status.currency.network.rawId == selectedStatus?.currency?.network?.rawId &&
status.currency.id.contractAddress == selectedStatus.currency.id.contractAddress
}
.filterByQuery(query = query)
@ -458,7 +455,7 @@ internal class AvailableSwapPairsModel @Inject constructor(
private fun CryptoCurrencyStatus.toLeastTokenInfo(): LeastTokenInfo {
return LeastTokenInfo(
contractAddress = (currency as? CryptoCurrency.Token)?.contractAddress ?: "0",
network = currency.network.backendId,
network = currency.network.rawId,
)
}
@ -593,45 +590,35 @@ internal class AvailableSwapPairsModel @Inject constructor(
}
private fun addToPortfolioItem(item: MarketsListItemUM) {
modelScope.launch {
val tokenMarket = defaultMarketsListManager.getTokenMarketById(item.id)
?: searchMarketsListManager.getTokenMarketById(item.id)
?: return@launch
val tokenMarket = defaultMarketsListManager.getTokenMarketById(item.id)
?: searchMarketsListManager.getTokenMarketById(item.id)
?: return
val param = tokenMarket.toSerializableParam()
val hasOnlyHotWallets = allUserWallets.all { it is UserWallet.Hot }
val param = tokenMarket.toSerializableParam()
val hasOnlyHotWallets = allUserWallets.all { it is UserWallet.Hot }
val networks = tokenMarket.networks?.filter { network ->
BlockchainUtils.isSupportedNetworkId(
blockchainId = network.networkId,
coinId = tokenMarket.id.value,
contractAddress = network.contractAddress,
excludedBlockchains = excludedBlockchains,
hotExcludedBlockchains = hotWalletExcludedBlockchains,
hasOnlyHotWallets = hasOnlyHotWallets,
)
}?.map { network ->
TokenMarketInfo.Network(
networkId = network.networkId,
isExchangeable = false,
contractAddress = network.contractAddress,
decimalCount = network.decimalCount,
)
}.orEmpty()
val networks = tokenMarket.networks?.filter { network ->
BlockchainUtils.isSupportedNetworkId(
networkId = network.networkId,
coinId = tokenMarket.id.value,
contractAddress = network.contractAddress,
excludedBlockchains = excludedBlockchains,
hotExcludedBlockchains = hotWalletExcludedBlockchains,
hasOnlyHotWallets = hasOnlyHotWallets,
)
}?.map { network ->
TokenMarketInfo.Network(
networkId = network.networkId,
isExchangeable = false,
contractAddress = network.contractAddress,
decimalCount = network.decimalCount,
)
}.orEmpty()
addToPortfolioManager = addToPortfolioManagerFactory
.create(
scope = modelScope,
token = param,
analyticsParams = AddToPortfolioManager.AnalyticsParams(source = ScreensSources.Swap.value),
).apply {
setTokenNetworks(networks)
}
addToPortfolioManager.setTokenNetworks(networks)
addToPortfolioManager.setTokenParams(param)
addToPortfolioManager?.state
?.firstOrNull { it is AddToPortfolioManager.State.AvailableToAdd }
?.run { bottomSheetNavigation.activate(AddToPortfolioRoute) }
}.saveIn(addToPortfolioJobHolder)
bottomSheetNavigation.activate(AddToPortfolioRoute)
}
private fun subscribeOnVisibleMarketItems() {

View file

@ -114,8 +114,7 @@ internal class SwapSelectTokensModel @Inject constructor(
router.push(
route = AppRoute.Swap(
currencyFrom = requireNotNull(fromCurrencyStatus.value).currency,
currencyTo = status.currency,
cryptoCurrency = requireNotNull(fromCurrencyStatus.value).currency,
userWalletId = params.userWalletId,
screenSource = AnalyticsParam.ScreensSources.Main.value,
),

View file

@ -4,7 +4,7 @@ import com.tangem.common.getTotalCryptoAmount
import com.tangem.common.getTotalFiatAmount
import com.tangem.common.ui.tokens.TokenItemStateConverter
import com.tangem.common.ui.tokens.TokenItemStateConverter.Companion.isFlickering
import com.tangem.core.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter
import com.tangem.common.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter
import com.tangem.core.ui.components.token.state.TokenItemState
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.stringReference

View file

@ -309,10 +309,7 @@ internal class OnrampTokenListModel @Inject constructor(
).isRight()
}
OnrampOperation.SWAP -> {
val isAvailable = rampStateManager.availableForSwap(
userWalletId = params.userWalletId,
cryptoCurrency = status.currency,
).isAvailable() && !status.currency.isCustom
val isAvailable = !status.currency.isCustom
val supplyStatus = status.value.yieldSupplyStatus
val isUnavailableByYieldSupply = supplyStatus?.isAllowedToSpend == false && supplyStatus.isActive