Updated on 2026-08-14

This commit is contained in:
Tangem 2025-07-21 18:04:33 +05:00
parent c813e330c2
commit 541bea5fe0
11 changed files with 230 additions and 23 deletions

View file

@ -7,9 +7,15 @@ interface FeeSelectorReloadTrigger {
/** Trigger fee update */
suspend fun triggerUpdate(feeData: FeeSelectorData = FeeSelectorData())
/** Triggers fee loading status. Used in cases where fee loading state is extended due to business logic */
suspend fun triggerLoadingState()
}
interface FeeSelectorReloadListener {
/** Flow triggers fee reload */
val reloadTriggerFlow: Flow<FeeSelectorData>
/** Triggers fee loading status. Used in cases where fee loading state is extended due to business logic */
val loadingStateTriggerFlow: Flow<Unit>
}

View file

@ -20,6 +20,9 @@ internal class DefaultFeeSelectorReloadTrigger @Inject constructor() :
override val reloadTriggerFlow: SharedFlow<FeeSelectorData>
field = MutableSharedFlow()
override val loadingStateTriggerFlow: SharedFlow<Unit>
field = MutableSharedFlow()
override val checkReloadTriggerFlow: SharedFlow<Unit>
field = MutableSharedFlow()
@ -30,6 +33,10 @@ internal class DefaultFeeSelectorReloadTrigger @Inject constructor() :
reloadTriggerFlow.emit(feeData)
}
override suspend fun triggerLoadingState() {
loadingStateTriggerFlow.emit(Unit)
}
override suspend fun triggerCheckUpdate() {
checkReloadTriggerFlow.emit(Unit)
}

View file

@ -54,6 +54,7 @@ internal class FeeSelectorModel @Inject constructor(
initAppCurrency()
subscribeOnFeeReloadTriggerUpdates()
subscribeOnFeeCheckReloadTriggerUpdates()
subscribeOnFeeLoadingStateTriggerUpdates()
loadFee()
}
@ -68,7 +69,7 @@ internal class FeeSelectorModel @Inject constructor(
}
private fun loadFee() {
uiState.update(FeeSelectorLoadingTransformers)
uiState.update(FeeSelectorLoadingTransformer)
modelScope.launch {
params.onLoadFee()
.fold(
@ -135,6 +136,12 @@ internal class FeeSelectorModel @Inject constructor(
.launchIn(modelScope)
}
private fun subscribeOnFeeLoadingStateTriggerUpdates() {
feeSelectorReloadListener.loadingStateTriggerFlow
.onEach { uiState.update(FeeSelectorLoadingTransformer) }
.launchIn(modelScope)
}
private fun subscribeOnFeeCheckReloadTriggerUpdates() {
feeSelectorCheckReloadListener.checkReloadTriggerFlow
.onEach { checkLoadFee() }

View file

@ -3,7 +3,7 @@ package com.tangem.features.send.v2.feeselector.model.transformers
import com.tangem.features.send.v2.api.entity.FeeSelectorUM
import com.tangem.utils.transformer.Transformer
internal object FeeSelectorLoadingTransformers : Transformer<FeeSelectorUM> {
internal object FeeSelectorLoadingTransformer : Transformer<FeeSelectorUM> {
override fun transform(prevState: FeeSelectorUM): FeeSelectorUM {
return FeeSelectorUM.Loading

View file

@ -4,6 +4,8 @@ import com.tangem.core.decompose.di.ModelComponent
import com.tangem.core.decompose.model.Model
import com.tangem.features.swap.v2.api.subcomponents.SwapAmountUpdateTrigger
import com.tangem.features.swap.v2.impl.amount.DefaultSwapAmountUpdateTrigger
import com.tangem.features.swap.v2.impl.amount.SwapAmountReduceListener
import com.tangem.features.swap.v2.impl.amount.SwapAmountReduceTrigger
import com.tangem.features.swap.v2.impl.amount.SwapAmountUpdateListener
import com.tangem.features.swap.v2.impl.amount.model.SwapAmountModel
import dagger.Binds
@ -32,4 +34,10 @@ internal interface SwapAmountModuleBinds {
@Binds
fun provideSwapAmountUpdateListener(impl: DefaultSwapAmountUpdateTrigger): SwapAmountUpdateListener
@Binds
fun provideSwapAmountReduceTrigger(impl: DefaultSwapAmountUpdateTrigger): SwapAmountReduceTrigger
@Binds
fun provideSwapAmountReduceListener(impl: DefaultSwapAmountUpdateTrigger): SwapAmountReduceListener
}

View file

@ -18,6 +18,7 @@ import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.express.models.ExpressError
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.swap.models.SwapDirection
import com.tangem.domain.swap.models.SwapDirection.Companion.withSwapDirection
import com.tangem.domain.swap.models.SwapQuoteModel
import com.tangem.domain.swap.usecase.GetSwapPairsUseCase
import com.tangem.domain.swap.usecase.GetSwapQuoteUseCase
@ -26,10 +27,12 @@ import com.tangem.domain.tokens.GetMinimumTransactionAmountSyncUseCase
import com.tangem.domain.tokens.GetMultiCryptoCurrencyStatusUseCase
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.transaction.usecase.GetAllowanceUseCase
import com.tangem.features.send.v2.api.subcomponents.feeSelector.FeeSelectorReloadTrigger
import com.tangem.features.swap.v2.api.choosetoken.SwapChooseTokenNetworkListener
import com.tangem.features.swap.v2.impl.R
import com.tangem.features.swap.v2.impl.amount.SwapAmountBlockComponent.SwapChooseProviderConfig
import com.tangem.features.swap.v2.impl.amount.SwapAmountComponentParams
import com.tangem.features.swap.v2.impl.amount.SwapAmountReduceListener
import com.tangem.features.swap.v2.impl.amount.SwapAmountUpdateListener
import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountFieldUM
import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountType
@ -40,6 +43,7 @@ import com.tangem.features.swap.v2.impl.chooseprovider.SwapChooseProviderCompone
import com.tangem.features.swap.v2.impl.common.entity.SwapQuoteUM
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.extensions.orZero
import com.tangem.utils.transformer.update
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.flow.*
@ -65,6 +69,8 @@ internal class SwapAmountModel @Inject constructor(
private val appRouter: AppRouter,
private val swapAlertFactory: SwapAlertFactory,
private val swapAmountUpdateListener: SwapAmountUpdateListener,
private val swapAmountReduceListener: SwapAmountReduceListener,
private val feeSelectorReloadTrigger: FeeSelectorReloadTrigger,
) : Model(), SwapAmountClickIntents, SwapChooseProviderComponent.ModelCallback {
private val params: SwapAmountComponentParams = paramsContainer.require()
@ -92,6 +98,9 @@ internal class SwapAmountModel @Inject constructor(
subscribeOnCryptoCurrencyStatusFlow()
subscribeOnAmountUpdateTriggerUpdates()
observeChooseSelectToken()
subscribeOnAmountReduceToTriggerUpdates()
subscribeOnAmountReduceByTriggerUpdates()
subscribeOnAmountIgnoreReduceTriggerUpdates()
}
fun updateState(amountUM: SwapAmountUM) {
@ -245,6 +254,44 @@ internal class SwapAmountModel @Inject constructor(
.launchIn(modelScope)
}
private fun subscribeOnAmountReduceToTriggerUpdates() {
swapAmountReduceListener.reduceToTriggerFlow
.onEach { reduceTo ->
uiState.update(
SwapAmountReduceToTransformer(
primaryMinimumAmountBoundary = primaryMinimumAmountBoundary,
secondaryMinimumAmountBoundary = secondaryMinimumAmountBoundary,
reduceToValue = reduceTo,
),
)
feeSelectorReloadTrigger.triggerLoadingState()
loadQuotes(reloadFees = true)
}
.launchIn(modelScope)
}
private fun subscribeOnAmountReduceByTriggerUpdates() {
swapAmountReduceListener.reduceByTriggerFlow
.onEach { reduceBy ->
uiState.update(
SwapAmountReduceByTransformer(
primaryMinimumAmountBoundary = primaryMinimumAmountBoundary,
secondaryMinimumAmountBoundary = secondaryMinimumAmountBoundary,
reduceByData = reduceBy,
),
)
feeSelectorReloadTrigger.triggerLoadingState()
loadQuotes(reloadFees = true)
}
.launchIn(modelScope)
}
private fun subscribeOnAmountIgnoreReduceTriggerUpdates() {
swapAmountReduceListener.ignoreReduceTriggerFlow
.onEach { uiState.update(SwapAmountIgnoreReduceTransformer) }
.launchIn(modelScope)
}
private fun observeChooseSelectToken() {
swapChooseTokenNetworkListener.swapChooseTokenNetworkResultFlow
.onEach { currency ->
@ -345,18 +392,14 @@ internal class SwapAmountModel @Inject constructor(
}
}
private fun loadQuotes() {
private fun loadQuotes(reloadFees: Boolean = false) {
val state = uiState.value as? SwapAmountUM.Content ?: return
if (state.secondaryCryptoCurrencyStatus == null) return
val (fromCryptoCurrency, toCryptoCurrency) = when (state.swapDirection) {
SwapDirection.Direct -> {
state.primaryCryptoCurrencyStatus.currency to state.secondaryCryptoCurrencyStatus.currency
}
SwapDirection.Reverse -> {
state.secondaryCryptoCurrencyStatus.currency to state.primaryCryptoCurrencyStatus.currency
}
}
val (fromCryptoCurrency, toCryptoCurrency) = state.swapDirection.withSwapDirection(
onDirect = { state.primaryCryptoCurrencyStatus.currency to state.secondaryCryptoCurrencyStatus.currency },
onReverse = { state.secondaryCryptoCurrencyStatus.currency to state.primaryCryptoCurrencyStatus.currency },
)
val fromAmount = when (state.swapDirection) {
SwapDirection.Direct -> state.primaryAmount.amountField
@ -420,6 +463,10 @@ internal class SwapAmountModel @Inject constructor(
secondaryMinimumAmountBoundary = secondaryMinimumAmountBoundary,
),
)
if (reloadFees) {
feeSelectorReloadTrigger.triggerUpdate()
}
}
}

View file

@ -0,0 +1,28 @@
package com.tangem.features.swap.v2.impl.amount.model.transformers
import com.tangem.common.ui.amountScreen.converters.AmountIgnoreReduceTransformer
import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountUM
import com.tangem.features.swap.v2.impl.amount.model.SwapAmountQuoteUtils.updateAmount
import com.tangem.features.swap.v2.impl.common.entity.SwapQuoteUM
import com.tangem.utils.transformer.Transformer
internal object SwapAmountIgnoreReduceTransformer : Transformer<SwapAmountUM> {
override fun transform(prevState: SwapAmountUM): SwapAmountUM {
if (prevState !is SwapAmountUM.Content) return prevState
return prevState
.copy(selectedQuote = SwapQuoteUM.Loading)
.updateAmount(
onPrimaryAmount = { _ ->
copy(
amountField = AmountIgnoreReduceTransformer.transform(prevState.primaryAmount.amountField),
)
},
onSecondaryAmount = { _ ->
copy(
amountField = AmountIgnoreReduceTransformer.transform(prevState.secondaryAmount.amountField),
)
},
)
}
}

View file

@ -0,0 +1,41 @@
package com.tangem.features.swap.v2.impl.amount.model.transformers
import com.tangem.common.ui.amountScreen.converters.AmountReduceByTransformer
import com.tangem.common.ui.amountScreen.models.EnterAmountBoundary
import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountUM
import com.tangem.features.swap.v2.impl.amount.model.SwapAmountQuoteUtils.updateAmount
import com.tangem.features.swap.v2.impl.common.entity.SwapQuoteUM
import com.tangem.utils.transformer.Transformer
internal class SwapAmountReduceByTransformer(
private val primaryMinimumAmountBoundary: EnterAmountBoundary?,
private val secondaryMinimumAmountBoundary: EnterAmountBoundary?,
private val reduceByData: AmountReduceByTransformer.ReduceByData,
) : Transformer<SwapAmountUM> {
override fun transform(prevState: SwapAmountUM): SwapAmountUM {
if (prevState !is SwapAmountUM.Content) return prevState
return prevState
.copy(selectedQuote = SwapQuoteUM.Loading)
.updateAmount(
onPrimaryAmount = { primaryStatus ->
copy(
amountField = AmountReduceByTransformer(
cryptoCurrencyStatus = primaryStatus,
minimumTransactionAmount = primaryMinimumAmountBoundary,
value = reduceByData,
).transform(prevState.primaryAmount.amountField),
)
},
onSecondaryAmount = { secondaryStatus ->
copy(
amountField = AmountReduceByTransformer(
cryptoCurrencyStatus = secondaryStatus,
minimumTransactionAmount = secondaryMinimumAmountBoundary,
value = reduceByData,
).transform(prevState.secondaryAmount.amountField),
)
},
)
}
}

View file

@ -0,0 +1,42 @@
package com.tangem.features.swap.v2.impl.amount.model.transformers
import com.tangem.common.ui.amountScreen.converters.AmountReduceToTransformer
import com.tangem.common.ui.amountScreen.models.EnterAmountBoundary
import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountUM
import com.tangem.features.swap.v2.impl.amount.model.SwapAmountQuoteUtils.updateAmount
import com.tangem.features.swap.v2.impl.common.entity.SwapQuoteUM
import com.tangem.utils.transformer.Transformer
import java.math.BigDecimal
internal class SwapAmountReduceToTransformer(
private val primaryMinimumAmountBoundary: EnterAmountBoundary?,
private val secondaryMinimumAmountBoundary: EnterAmountBoundary?,
private val reduceToValue: BigDecimal,
) : Transformer<SwapAmountUM> {
override fun transform(prevState: SwapAmountUM): SwapAmountUM {
if (prevState !is SwapAmountUM.Content) return prevState
return prevState
.copy(selectedQuote = SwapQuoteUM.Loading)
.updateAmount(
onPrimaryAmount = { primaryStatus ->
copy(
amountField = AmountReduceToTransformer(
cryptoCurrencyStatus = primaryStatus,
minimumTransactionAmount = primaryMinimumAmountBoundary,
value = reduceToValue,
).transform(prevState.primaryAmount.amountField),
)
},
onSecondaryAmount = { secondaryStatus ->
copy(
amountField = AmountReduceToTransformer(
cryptoCurrencyStatus = secondaryStatus,
minimumTransactionAmount = secondaryMinimumAmountBoundary,
value = reduceToValue,
).transform(prevState.secondaryAmount.amountField),
)
},
)
}
}

View file

@ -4,6 +4,7 @@ import arrow.core.Either
import arrow.core.getOrElse
import arrow.core.left
import com.tangem.blockchain.common.transaction.TransactionFee
import com.tangem.common.ui.amountScreen.converters.AmountReduceByTransformer
import com.tangem.common.ui.amountScreen.models.AmountState
import com.tangem.common.ui.navigationButtons.NavigationButton
import com.tangem.common.ui.navigationButtons.NavigationUM
@ -30,6 +31,7 @@ import com.tangem.features.send.v2.api.subcomponents.feeSelector.FeeSelectorRelo
import com.tangem.features.send.v2.api.subcomponents.notifications.SendNotificationsUpdateListener
import com.tangem.features.send.v2.api.subcomponents.notifications.SendNotificationsUpdateTrigger
import com.tangem.features.swap.v2.impl.R
import com.tangem.features.swap.v2.impl.amount.SwapAmountReduceTrigger
import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountUM
import com.tangem.features.swap.v2.impl.common.SwapUtils.INCREASE_GAS_LIMIT_FOR_CEX
import com.tangem.features.swap.v2.impl.common.entity.ConfirmUM
@ -48,6 +50,7 @@ import com.tangem.utils.extensions.orZero
import jakarta.inject.Inject
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import java.math.BigDecimal
import com.tangem.utils.transformer.update as transformerUpdate
@Suppress("LongParameterList")
@ -63,6 +66,7 @@ internal class SendWithSwapConfirmModel @Inject constructor(
private val swapNotificationsUpdateTrigger: SwapNotificationsUpdateTrigger,
private val sendNotificationsUpdateListener: SendNotificationsUpdateListener,
private val swapNotificationsUpdateListener: SwapNotificationsUpdateListener,
private val swapAmountReduceTrigger: SwapAmountReduceTrigger,
private val feeSelectorReloadTrigger: FeeSelectorReloadTrigger,
swapTransactionSenderFactory: SwapTransactionSender.Factory,
paramsContainer: ParamsContainer,
@ -152,6 +156,29 @@ internal class SendWithSwapConfirmModel @Inject constructor(
}
}
override fun onAmountIgnore() {
modelScope.launch {
swapAmountReduceTrigger.triggerIgnoreReduce()
}
}
override fun onAmountReduceBy(reduceBy: BigDecimal, reduceByDiff: BigDecimal) {
modelScope.launch {
swapAmountReduceTrigger.triggerReduceBy(
reduceBy = AmountReduceByTransformer.ReduceByData(
reduceAmountBy = reduceBy,
reduceAmountByDiff = reduceByDiff,
),
)
}
}
override fun onAmountReduceTo(reduceTo: BigDecimal) {
modelScope.launch {
swapAmountReduceTrigger.triggerReduceTo(reduceTo = reduceTo)
}
}
fun showEditAmount() {
router.push(SendWithSwapRoute.Amount(isEditMode = true))
}

View file

@ -1,7 +1,6 @@
package com.tangem.features.swap.v2.impl.sendviaswap.confirm.ui
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
@ -41,20 +40,15 @@ internal fun SendWithSwapConfirmContent(
Column(modifier = modifier) {
LazyColumn(
verticalArrangement = Arrangement.spacedBy(8.dp),
modifier = Modifier.padding(horizontal = 12.dp),
) {
item(key = "SendWithSwapAmountBlock") {
amountBlockComponent.Content(Modifier)
}
item(key = "SendWithSwapDestinationBlock") {
sendDestinationBlockComponent.Content(Modifier)
}
item(key = "SendWithSwapFeeBLock") {
Box(
modifier = Modifier.clip(RoundedCornerShape(16.dp)),
item(key = "SendWithSwapBlocks") {
Column(
verticalArrangement = Arrangement.spacedBy(12.dp),
) {
feeSelectorBlockComponent.Content(Modifier)
amountBlockComponent.Content(Modifier)
sendDestinationBlockComponent.Content(Modifier)
feeSelectorBlockComponent.Content(Modifier.clip(RoundedCornerShape(16.dp)))
}
}
if (confirmUM != null) {