Updated on 2026-08-14

This commit is contained in:
Tangem 2025-06-30 17:47:59 +05:00
parent 0dc200beac
commit 7ee8825f97
3 changed files with 92 additions and 81 deletions

View file

@ -1,6 +1,5 @@
package com.tangem.features.swap.v2.impl.amount
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.swap.models.SwapDirection
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
@ -15,19 +14,19 @@ internal sealed class SwapAmountComponentParams {
abstract val amountUM: SwapAmountUM
abstract val analyticsCategoryName: String
abstract val userWallet: UserWallet
abstract val appCurrency: AppCurrency
abstract val swapDirection: SwapDirection
abstract val isBalanceHidingFlow: StateFlow<Boolean>
abstract val primaryCryptoCurrencyStatusFlow: StateFlow<CryptoCurrencyStatus>
abstract val secondaryCryptoCurrency: CryptoCurrency?
data class AmountParams(
override val amountUM: SwapAmountUM,
override val analyticsCategoryName: String,
override val userWallet: UserWallet,
override val isBalanceHidingFlow: StateFlow<Boolean>,
override val appCurrency: AppCurrency,
override val swapDirection: SwapDirection,
val primaryCryptoCurrency: CryptoCurrency,
val secondaryCryptoCurrency: CryptoCurrency?,
override val primaryCryptoCurrencyStatusFlow: StateFlow<CryptoCurrencyStatus>,
override val secondaryCryptoCurrency: CryptoCurrency?,
val callback: SwapAmountComponent.ModelCallback,
val currentRoute: Flow<SwapRoute.Amount>,
) : SwapAmountComponentParams()
@ -37,10 +36,9 @@ internal sealed class SwapAmountComponentParams {
override val analyticsCategoryName: String,
override val userWallet: UserWallet,
override val isBalanceHidingFlow: StateFlow<Boolean>,
override val appCurrency: AppCurrency,
override val swapDirection: SwapDirection,
val primaryCryptoCurrencyStatus: CryptoCurrencyStatus,
val secondaryCryptoCurrencyStatus: CryptoCurrencyStatus?,
override val primaryCryptoCurrencyStatusFlow: StateFlow<CryptoCurrencyStatus>,
override val secondaryCryptoCurrency: CryptoCurrency?,
val blockClickEnableFlow: StateFlow<Boolean>,
) : SwapAmountComponentParams()
}

View file

@ -14,10 +14,11 @@ import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.format.bigdecimal.crypto
import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.express.models.ExpressError
import com.tangem.domain.express.models.ExpressProvider
import com.tangem.domain.express.models.ExpressRateType
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.swap.models.SwapCurrencies
import com.tangem.domain.swap.models.SwapDirection
import com.tangem.domain.swap.models.SwapQuoteModel
import com.tangem.domain.swap.usecase.GetSwapPairsUseCase
@ -41,12 +42,10 @@ import com.tangem.features.swap.v2.impl.common.entity.SwapQuoteUM
import com.tangem.features.swap.v2.impl.common.entity.SwapQuoteUM.Content.DifferencePercent
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.extensions.orZero
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
import kotlin.properties.Delegates
import com.tangem.utils.transformer.update as transformerUpdate
@ -63,19 +62,23 @@ internal class SwapAmountModel @Inject constructor(
private val getSwapQuoteUseCase: GetSwapQuoteUseCase,
private val swapChooseTokenNetworkListener: SwapChooseTokenNetworkListener,
private val getAllowanceUseCase: GetAllowanceUseCase,
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
private val appRouter: AppRouter,
) : Model(), SwapAmountClickIntents, SwapChooseProviderComponent.ModelCallback {
private val params: SwapAmountComponentParams = paramsContainer.require()
private val swapDirection = params.swapDirection
private val appCurrency = params.appCurrency
private var appCurrency = AppCurrency.Default
private var userWallet = params.userWallet
private var primaryCryptoCurrency: CryptoCurrency by Delegates.notNull()
private var secondaryCryptoCurrency: CryptoCurrency? = null
private var primaryCryptoCurrency: CryptoCurrency = params.primaryCryptoCurrencyStatusFlow.value.currency
private var secondaryCryptoCurrency: CryptoCurrency? = params.secondaryCryptoCurrency
private var primaryCryptoCurrencyStatus: CryptoCurrencyStatus by Delegates.notNull()
private var secondaryCryptoCurrencyStatus: CryptoCurrencyStatus by Delegates.notNull()
private var primaryCryptoCurrencyStatus: CryptoCurrencyStatus = params.primaryCryptoCurrencyStatusFlow.value
private var secondaryCryptoCurrencyStatus: CryptoCurrencyStatus = CryptoCurrencyStatus(
currency = primaryCryptoCurrencyStatus.currency,
value = CryptoCurrencyStatus.Loading,
)
private var primaryMaximumAmountBoundary: EnterAmountBoundary by Delegates.notNull()
private var secondaryMaximumAmountBoundary: EnterAmountBoundary by Delegates.notNull()
@ -86,24 +89,11 @@ internal class SwapAmountModel @Inject constructor(
field = MutableStateFlow(params.amountUM)
init {
when (params) {
is SwapAmountComponentParams.AmountParams -> {
primaryCryptoCurrency = params.primaryCryptoCurrency
secondaryCryptoCurrency = params.secondaryCryptoCurrency
}
is SwapAmountComponentParams.AmountBlockParams -> {
primaryCryptoCurrency = params.primaryCryptoCurrencyStatus.currency
secondaryCryptoCurrency = params.secondaryCryptoCurrencyStatus?.currency
primaryCryptoCurrencyStatus = params.primaryCryptoCurrencyStatus
params.secondaryCryptoCurrencyStatus?.let {
secondaryCryptoCurrencyStatus = it
}
}
modelScope.launch {
appCurrency = getSelectedAppCurrencyUseCase.invokeSync().getOrElse { AppCurrency.Default }
}
initialState()
configAmountNavigation()
subscribeOnCryptoCurrencyStatusFlow()
observeChooseSelectToken()
// todo observe balance hiding flow
}
@ -182,6 +172,27 @@ internal class SwapAmountModel @Inject constructor(
)
}
private fun subscribeOnCryptoCurrencyStatusFlow() {
params.primaryCryptoCurrencyStatusFlow.onEach { primaryCurrencyStatus ->
primaryCryptoCurrencyStatus = primaryCurrencyStatus
val state = uiState.value
if (state is SwapAmountUM.Content) {
secondaryCryptoCurrency = state.primaryCryptoCurrencyStatus.currency
secondaryCryptoCurrencyStatus = state.primaryCryptoCurrencyStatus
initCurrencies(
primaryStatus = state.primaryCryptoCurrencyStatus,
secondaryStatus = state.secondaryCryptoCurrencyStatus,
)
} else {
initPairs(
primaryCryptoCurrency = primaryCryptoCurrency,
secondaryCryptoCurrency = secondaryCryptoCurrency,
)
}
}.launchIn(modelScope)
}
private fun observeChooseSelectToken() {
swapChooseTokenNetworkListener.swapChooseTokenNetworkResultFlow
.onEach { currency ->
@ -202,46 +213,12 @@ internal class SwapAmountModel @Inject constructor(
.launchIn(modelScope)
}
private fun initialState() {
if (uiState.value is SwapAmountUM.Empty) {
initPairs(
primaryCryptoCurrency = primaryCryptoCurrency,
secondaryCryptoCurrency = secondaryCryptoCurrency,
)
uiState.update {
SwapAmountUM.Content(
isPrimaryButtonEnabled = it.isPrimaryButtonEnabled,
primaryAmount = it.primaryAmount,
secondaryAmount = it.secondaryAmount,
swapDirection = swapDirection,
swapCurrencies = SwapCurrencies.EMPTY,
appCurrency = appCurrency,
swapQuotes = persistentListOf(),
swapRateType = ExpressRateType.Float,
selectedAmountType = SwapAmountType.From,
selectedQuote = SwapQuoteUM.Empty,
primaryCryptoCurrencyStatus = null,
secondaryCryptoCurrencyStatus = null,
)
}
}
}
private fun initPairs(primaryCryptoCurrency: CryptoCurrency, secondaryCryptoCurrency: CryptoCurrency?) {
modelScope.launch {
val cryptoCurrencyStatusList = getMultiCryptoCurrencyStatusUseCase
.invokeMultiWalletSync(userWallet.walletId)
.getOrElse { emptyList() }
val primaryStatus = cryptoCurrencyStatusList.firstOrNull {
it.currency.id == primaryCryptoCurrency.id
}
if (primaryStatus == null) {
Timber.e("Failed to get crypto currency status")
// todo error
return@launch
}
val cryptoCurrencyStatusListExceptPrimary = cryptoCurrencyStatusList.filter {
val statusFilter = it.value is CryptoCurrencyStatus.Loaded || it.value is CryptoCurrencyStatus.NoAccount
val notCustomTokenFilter = !it.currency.isCustom
@ -263,7 +240,9 @@ internal class SwapAmountModel @Inject constructor(
)
if (secondaryStatus != null) {
initCurrencies(primaryStatus, secondaryStatus)
initCurrencies(primaryCryptoCurrencyStatus, secondaryStatus)
this@SwapAmountModel.secondaryCryptoCurrency = secondaryCryptoCurrency
secondaryCryptoCurrencyStatus = secondaryStatus
uiState.update {
SwapAmountReadyStateConverter(
swapCurrencies = swapCurrencies,
@ -288,9 +267,6 @@ internal class SwapAmountModel @Inject constructor(
}
private suspend fun initCurrencies(primaryStatus: CryptoCurrencyStatus, secondaryStatus: CryptoCurrencyStatus) {
primaryCryptoCurrencyStatus = primaryStatus
secondaryCryptoCurrencyStatus = secondaryStatus
primaryMinimumAmountBoundary = EnterAmountBoundary(
amount = getMinimumTransactionAmountSyncUseCase
.invoke(
@ -317,8 +293,12 @@ internal class SwapAmountModel @Inject constructor(
val state = uiState.value as? SwapAmountUM.Content ?: return
val (fromCryptoCurrency, toCryptoCurrency) = when (state.swapDirection) {
SwapDirection.Direct -> primaryCryptoCurrencyStatus.currency to secondaryCryptoCurrencyStatus.currency
SwapDirection.Reverse -> secondaryCryptoCurrencyStatus.currency to primaryCryptoCurrencyStatus.currency
SwapDirection.Direct -> {
state.primaryCryptoCurrencyStatus.currency to state.secondaryCryptoCurrencyStatus.currency
}
SwapDirection.Reverse -> {
state.secondaryCryptoCurrencyStatus.currency to state.primaryCryptoCurrencyStatus.currency
}
}
val fromAmount = when (state.swapDirection) {
@ -338,9 +318,11 @@ internal class SwapAmountModel @Inject constructor(
uiState.transformerUpdate(SwapQuoteLoadingStateTransformer)
modelScope.launch {
val quotes = swapGroups.firstOrNull {
val quotes = swapGroups.filter {
it.currencyStatus.currency.id == toCryptoCurrency.id
}?.providers?.map { provider ->
}.flatMap {
it.providers
}.map { provider ->
async {
getSwapQuoteUseCase(
userWallet = userWallet,
@ -353,7 +335,7 @@ internal class SwapAmountModel @Inject constructor(
SwapQuoteUM.Error(
provider = provider,
expressError = error,
)
).takeIf { error is ExpressError.AmountError }
},
ifRight = { quote: SwapQuoteModel ->
convertToSwapProviderUM(
@ -365,7 +347,7 @@ internal class SwapAmountModel @Inject constructor(
},
)
}
}?.awaitAll().orEmpty()
}.awaitAll().filterNotNull()
uiState.transformerUpdate(
SwapAmountSetQuotesTransformer(

View file

@ -6,8 +6,11 @@ import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.express.models.ExpressRateType
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.network.Network
import com.tangem.domain.swap.models.SwapCurrencies
import com.tangem.domain.swap.models.SwapDirection
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountFieldUM
import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountType
import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountUM
@ -16,6 +19,34 @@ import kotlinx.collections.immutable.persistentListOf
internal data object SwapAmountContentPreview {
private val cryptoCurrencyStatus = CryptoCurrencyStatus(
currency = CryptoCurrency.Coin(
id = CryptoCurrency.ID.fromValue("coin⟨BITCOIN⟩bitcoin"),
network = Network(
id = Network.ID(
value = "bitcoin",
derivationPath = Network.DerivationPath.None,
),
backendId = "bitcoin",
name = "Bitcoin",
currencySymbol = "BTC",
derivationPath = Network.DerivationPath.None,
isTestnet = false,
standardType = Network.StandardType.Unspecified("bitcoin"),
hasFiatFeeRate = false,
canHandleTokens = false,
transactionExtrasType = Network.TransactionExtrasType.NONE,
),
name = "Bitcoin",
symbol = "BTC",
decimals = 8,
iconUrl = "",
isCustom = false,
),
value = CryptoCurrencyStatus.Loading,
)
val emptyState = SwapAmountUM.Content(
isPrimaryButtonEnabled = false,
primaryAmount = SwapAmountFieldUM.Empty(
@ -29,8 +60,8 @@ internal data object SwapAmountContentPreview {
swapCurrencies = SwapCurrencies.EMPTY,
swapQuotes = persistentListOf(),
selectedQuote = SwapQuoteUM.Empty,
primaryCryptoCurrencyStatus = null,
secondaryCryptoCurrencyStatus = null,
primaryCryptoCurrencyStatus = cryptoCurrencyStatus,
secondaryCryptoCurrencyStatus = cryptoCurrencyStatus,
swapRateType = ExpressRateType.Float,
appCurrency = AppCurrency.Default,
)
@ -65,8 +96,8 @@ internal data object SwapAmountContentPreview {
swapCurrencies = SwapCurrencies.EMPTY,
swapQuotes = persistentListOf(),
selectedQuote = SwapQuoteUM.Empty,
primaryCryptoCurrencyStatus = null,
secondaryCryptoCurrencyStatus = null,
primaryCryptoCurrencyStatus = cryptoCurrencyStatus,
secondaryCryptoCurrencyStatus = cryptoCurrencyStatus,
swapRateType = ExpressRateType.Float,
isPrimaryButtonEnabled = true,
)