Updated on 2026-08-14

This commit is contained in:
Tangem 2025-09-30 14:23:50 +03:00
commit c32a5fa18d
711 changed files with 18558 additions and 4312 deletions

View file

@ -45,19 +45,19 @@ internal class DefaultSwapAmountUpdateTrigger @Inject constructor() :
SwapAmountReduceListener {
override val updateAmountTriggerFlow: SharedFlow<Pair<String, Boolean>>
field = MutableSharedFlow<Pair<String, Boolean>>()
field = MutableSharedFlow<Pair<String, Boolean>>()
override val reduceToTriggerFlow: SharedFlow<BigDecimal>
field = MutableSharedFlow<BigDecimal>()
field = MutableSharedFlow<BigDecimal>()
override val reduceByTriggerFlow: SharedFlow<ReduceByData>
field = MutableSharedFlow<ReduceByData>()
field = MutableSharedFlow<ReduceByData>()
override val ignoreReduceTriggerFlow: SharedFlow<Unit>
field = MutableSharedFlow<Unit>()
field = MutableSharedFlow<Unit>()
override val reloadQuotesTriggerFlow: Flow<Unit>
field = MutableSharedFlow<Unit>()
field = MutableSharedFlow<Unit>()
override suspend fun triggerUpdateAmount(amountValue: String, isEnterInFiatSelected: Boolean) {
updateAmountTriggerFlow.emit(amountValue to isEnterInFiatSelected)

View file

@ -6,6 +6,7 @@ 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.swap.models.SwapDirection
import com.tangem.features.send.v2.api.analytics.CommonSendAnalyticEvents
import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountUM
import com.tangem.features.swap.v2.impl.sendviaswap.SendWithSwapRoute
import kotlinx.coroutines.flow.Flow
@ -15,6 +16,7 @@ internal sealed class SwapAmountComponentParams {
abstract val amountUM: SwapAmountUM
abstract val analyticsCategoryName: String
abstract val analyticsSendSource: CommonSendAnalyticEvents.CommonSendSource
abstract val userWallet: UserWallet
abstract val swapDirection: SwapDirection
abstract val isBalanceHidingFlow: StateFlow<Boolean>
@ -31,6 +33,7 @@ internal sealed class SwapAmountComponentParams {
override val primaryCryptoCurrencyStatusFlow: StateFlow<CryptoCurrencyStatus>,
override val secondaryCryptoCurrency: CryptoCurrency?,
override val filterProviderTypes: List<ExpressProviderType> = emptyList(),
override val analyticsSendSource: CommonSendAnalyticEvents.CommonSendSource,
val title: TextReference,
val callback: SwapAmountComponent.ModelCallback,
val currentRoute: Flow<SendWithSwapRoute>,
@ -45,6 +48,7 @@ internal sealed class SwapAmountComponentParams {
override val primaryCryptoCurrencyStatusFlow: StateFlow<CryptoCurrencyStatus>,
override val secondaryCryptoCurrency: CryptoCurrency?,
override val filterProviderTypes: List<ExpressProviderType> = emptyList(),
override val analyticsSendSource: CommonSendAnalyticEvents.CommonSendSource,
val blockClickEnableFlow: StateFlow<Boolean>,
) : SwapAmountComponentParams()
}

View file

@ -114,7 +114,7 @@ internal class SwapAmountModel @Inject constructor(
private var showBestRateAnimation: Boolean = false
val uiState: StateFlow<SwapAmountUM>
field = MutableStateFlow(params.amountUM)
field = MutableStateFlow(params.amountUM)
private val amountDebouncer = Debouncer()
private val quoteTaskScheduler = SingleTaskScheduler<Unit>()
@ -224,6 +224,7 @@ internal class SwapAmountModel @Inject constructor(
categoryName = params.analyticsCategoryName,
token = primaryCryptoCurrency.symbol,
blockchain = primaryCryptoCurrency.network.name,
source = params.analyticsSendSource,
),
)
uiState.transformerUpdate(

View file

@ -37,7 +37,7 @@ internal class SwapChooseProviderModel @Inject constructor(
}
val uiState: StateFlow<SwapChooseProviderBottomSheetContent>
field: MutableStateFlow<SwapChooseProviderBottomSheetContent> = MutableStateFlow(getInitialState())
field: MutableStateFlow<SwapChooseProviderBottomSheetContent> = MutableStateFlow(getInitialState())
fun onProviderClick(quoteUM: SwapQuoteUM) {
params.callback.onProviderResult(quoteUM)

View file

@ -16,7 +16,7 @@ internal class DefaultSwapChooseTokenNetworkTrigger @Inject constructor() :
SwapChooseTokenNetworkListener {
override val swapChooseTokenNetworkResultFlow: SharedFlow<SwapChooseTokenTriggerData>
field = MutableSharedFlow<SwapChooseTokenTriggerData>()
field = MutableSharedFlow<SwapChooseTokenTriggerData>()
override suspend fun trigger(
swapCurrencies: SwapCurrencies,

View file

@ -46,20 +46,20 @@ internal class SwapChooseTokenNetworkModel @Inject constructor(
private val params: SwapChooseTokenNetworkComponent.Params = paramsContainer.require()
val uiState: StateFlow<SwapChooseTokenNetworkUM>
field: MutableStateFlow<SwapChooseTokenNetworkUM> = MutableStateFlow(
SwapChooseTokenNetworkUM(
TangemBottomSheetConfig(
isShown = true,
onDismissRequest = params.onDismiss,
content = SwapChooseTokenNetworkContentUM.Loading(
messageContent = getErrorMessage(
tokenName = params.token.name,
onDismiss = params.onDismiss,
field: MutableStateFlow<SwapChooseTokenNetworkUM> = MutableStateFlow(
SwapChooseTokenNetworkUM(
TangemBottomSheetConfig(
isShown = true,
onDismissRequest = params.onDismiss,
content = SwapChooseTokenNetworkContentUM.Loading(
messageContent = getErrorMessage(
tokenName = params.token.name,
onDismiss = params.onDismiss,
),
),
),
),
),
)
)
init {
initContent()

View file

@ -30,10 +30,10 @@ internal class DefaultSwapNotificationsUpdateTrigger @Inject constructor() :
SwapNotificationsUpdateTrigger {
override val updateTriggerFlow: SharedFlow<SwapNotificationData>
field = MutableSharedFlow<SwapNotificationData>()
field = MutableSharedFlow<SwapNotificationData>()
override val hasErrorFlow: SharedFlow<Boolean>
field = MutableSharedFlow<Boolean>()
field = MutableSharedFlow<Boolean>()
override suspend fun callbackHasError(hasError: Boolean) {
hasErrorFlow.emit(hasError)

View file

@ -38,7 +38,7 @@ internal class SwapNotificationsModel @Inject constructor(
private var notificationData = params.swapNotificationData
val uiState: StateFlow<ImmutableList<NotificationUM>>
field = MutableStateFlow<ImmutableList<NotificationUM>>(persistentListOf())
field = MutableStateFlow<ImmutableList<NotificationUM>>(persistentListOf())
init {
subscribeToNotificationUpdateTrigger()

View file

@ -18,7 +18,6 @@ import com.tangem.core.decompose.context.childByContext
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.decompose.navigation.inner.InnerRouter
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.core.ui.decompose.getEmptyComposableContentComponent
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.domain.swap.models.R
import com.tangem.domain.swap.models.SwapDirection
@ -85,13 +84,19 @@ internal class DefaultSendWithSwapComponent @AssistedInject constructor(
when (val activeComponent = stack.active.instance) {
is SwapAmountComponent -> {
analyticsEventHandler.send(
CommonSendAnalyticEvents.AmountScreenOpened(categoryName = model.analyticCategoryName),
CommonSendAnalyticEvents.AmountScreenOpened(
categoryName = model.analyticCategoryName,
source = model.analyticsSendSource,
),
)
activeComponent.updateState(model.uiState.value.amountUM)
}
is SendDestinationComponent -> {
analyticsEventHandler.send(
CommonSendAnalyticEvents.AddressScreenOpened(categoryName = model.analyticCategoryName),
CommonSendAnalyticEvents.AddressScreenOpened(
categoryName = model.analyticCategoryName,
source = model.analyticsSendSource,
),
)
activeComponent.updateState(model.uiState.value.destinationUM)
}
@ -99,6 +104,7 @@ internal class DefaultSendWithSwapComponent @AssistedInject constructor(
analyticsEventHandler.send(
CommonSendAnalyticEvents.ConfirmationScreenOpened(
categoryName = model.analyticCategoryName,
source = model.analyticsSendSource,
),
)
if (model.currentRoute.value.isEditMode) {
@ -146,15 +152,16 @@ internal class DefaultSendWithSwapComponent @AssistedInject constructor(
callback = model,
userWallet = model.userWallet,
filterProviderTypes = SEND_WITH_SWAP_PROVIDER_TYPES,
analyticsSendSource = model.analyticsSendSource,
),
)
}
private fun getDestinationComponent(factoryContext: AppComponentContext): ComposableContentComponent {
val amountContentUM = model.uiState.value.amountUM as? SwapAmountUM.Content
?: return getEmptyComposableContentComponent()
?: return ComposableContentComponent.EMPTY
val secondaryCryptoCurrency = amountContentUM.secondaryCryptoCurrencyStatus?.currency
?: return getEmptyComposableContentComponent()
?: return ComposableContentComponent.EMPTY
return sendDestinationComponentFactory.create(
context = factoryContext,
@ -163,6 +170,7 @@ internal class DefaultSendWithSwapComponent @AssistedInject constructor(
currentRoute = model.currentRoute.filterIsInstance<DestinationRoute>(),
isBalanceHidingFlow = model.isBalanceHiddenFlow,
analyticsCategoryName = model.analyticCategoryName,
analyticsSendSource = model.analyticsSendSource,
title = resourceReference(R.string.common_address),
userWalletId = params.userWalletId,
cryptoCurrency = secondaryCryptoCurrency,
@ -184,6 +192,7 @@ internal class DefaultSendWithSwapComponent @AssistedInject constructor(
analyticsCategoryName = model.analyticCategoryName,
primaryCryptoCurrencyStatusFlow = model.primaryCryptoCurrencyStatusFlow,
primaryFeePaidCurrencyStatusFlow = model.primaryFeePaidCurrencyStatusFlow,
analyticsSendSource = model.analyticsSendSource,
swapDirection = SwapDirection.Direct,
),
)

View file

@ -17,6 +17,7 @@ import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.swap.models.SwapDirection
import com.tangem.features.send.v2.api.FeeSelectorBlockComponent
import com.tangem.features.send.v2.api.SendNotificationsComponent
import com.tangem.features.send.v2.api.analytics.CommonSendAnalyticEvents
import com.tangem.features.send.v2.api.entity.PredefinedValues
import com.tangem.features.send.v2.api.params.FeeSelectorParams.*
import com.tangem.features.send.v2.api.subcomponents.destination.SendDestinationBlockComponent
@ -62,6 +63,7 @@ internal class SendWithSwapConfirmComponent @AssistedInject constructor(
isBalanceHidingFlow = params.isBalanceHidingFlow,
swapDirection = params.swapDirection,
filterProviderTypes = SEND_WITH_SWAP_PROVIDER_TYPES,
analyticsSendSource = params.analyticsSendSource,
),
onResult = model::onAmountResult,
onClick = model::showEditAmount,
@ -72,6 +74,7 @@ internal class SendWithSwapConfirmComponent @AssistedInject constructor(
params = SendDestinationComponentParams.DestinationBlockParams(
state = model.uiState.value.destinationUM,
analyticsCategoryName = params.analyticsCategoryName,
analyticsSendSource = params.analyticsSendSource,
userWalletId = params.userWallet.walletId,
blockClickEnableFlow = blockClickEnableFlow.asStateFlow(),
cryptoCurrency = model.secondaryCurrency,
@ -91,6 +94,7 @@ internal class SendWithSwapConfirmComponent @AssistedInject constructor(
feeStateConfiguration = FeeStateConfiguration.ExcludeLow,
feeDisplaySource = FeeDisplaySource.Screen,
analyticsCategoryName = params.analyticsCategoryName,
analyticsSendSource = params.analyticsSendSource,
),
onResult = model::onFeeResult,
)
@ -166,6 +170,7 @@ internal class SendWithSwapConfirmComponent @AssistedInject constructor(
data class Params(
val sendWithSwapUM: SendWithSwapUM,
val analyticsCategoryName: String,
val analyticsSendSource: CommonSendAnalyticEvents.CommonSendSource,
val userWallet: UserWallet,
val appCurrency: AppCurrency,
val currentRoute: Flow<SendWithSwapRoute>,

View file

@ -91,7 +91,7 @@ internal class SendWithSwapConfirmModel @Inject constructor(
private val params: SendWithSwapConfirmComponent.Params = paramsContainer.require()
val uiState: StateFlow<SendWithSwapUM>
field = MutableStateFlow(params.sendWithSwapUM)
field = MutableStateFlow(params.sendWithSwapUM)
val primaryCurrencyStatus: CryptoCurrencyStatus = params.primaryCryptoCurrencyStatusFlow.value
val secondaryCurrencyStatus: CryptoCurrencyStatus? = amountUM?.secondaryCryptoCurrencyStatus
@ -227,7 +227,7 @@ internal class SendWithSwapConfirmModel @Inject constructor(
estimateFeeUseCase(
amount = amountValue,
userWallet = params.userWallet,
cryptoCurrency = primaryCurrencyStatus.currency,
cryptoCurrencyStatus = primaryCurrencyStatus,
).map {
it.patchTransactionFeeForSwap(INCREASE_GAS_LIMIT_FOR_CEX)
}

View file

@ -131,7 +131,7 @@ internal class SwapTransactionSender @AssistedInject constructor(
}
val txData = createTransferTransactionUseCase(
amount = fromAmount.convertToSdkAmount(fromStatus.currency),
amount = fromAmount.convertToSdkAmount(fromStatus),
fee = fee,
memo = swapTransaction.txExtraId,
destination = swapTransaction.txTo,

View file

@ -60,6 +60,7 @@ internal class SendWithSwapModel @Inject constructor(
private val params: SendWithSwapComponent.Params = paramsContainer.require()
val analyticCategoryName = CommonSendAnalyticEvents.SEND_CATEGORY
val analyticsSendSource = CommonSendAnalyticEvents.CommonSendSource.SendWithSwap
val initialRoute = SendWithSwapRoute.Amount(false)
val currentRoute = MutableStateFlow<SendWithSwapRoute>(initialRoute)
@ -67,26 +68,26 @@ internal class SendWithSwapModel @Inject constructor(
var appCurrency: AppCurrency = AppCurrency.Default
val uiState: StateFlow<SendWithSwapUM>
field = MutableStateFlow(initialState())
field = MutableStateFlow(initialState())
val isBalanceHiddenFlow: StateFlow<Boolean>
field = MutableStateFlow(false)
field = MutableStateFlow(false)
val primaryCryptoCurrencyStatusFlow: StateFlow<CryptoCurrencyStatus>
field = MutableStateFlow(
CryptoCurrencyStatus(
currency = params.currency,
value = CryptoCurrencyStatus.Loading,
),
)
field = MutableStateFlow(
CryptoCurrencyStatus(
currency = params.currency,
value = CryptoCurrencyStatus.Loading,
),
)
val primaryFeePaidCurrencyStatusFlow: StateFlow<CryptoCurrencyStatus>
field = MutableStateFlow(
CryptoCurrencyStatus(
currency = params.currency,
value = CryptoCurrencyStatus.Loading,
),
)
field = MutableStateFlow(
CryptoCurrencyStatus(
currency = params.currency,
value = CryptoCurrencyStatus.Loading,
),
)
init {
initUserWallet()

View file

@ -57,10 +57,10 @@ internal class SendWithSwapSuccessModel @Inject constructor(
iconRes = R.drawable.ic_web_24,
onClick = ::onExploreClick,
) to NavigationButton(
textReference = resourceReference(R.string.common_share),
iconRes = R.drawable.ic_share_24,
onClick = ::onShareClick,
)).takeUnless { confirmUM?.txUrl.isNullOrBlank() },
textReference = resourceReference(R.string.common_share),
iconRes = R.drawable.ic_share_24,
onClick = ::onShareClick,
)).takeUnless { confirmUM?.txUrl.isNullOrBlank() },
),
)
}

View file

@ -307,7 +307,7 @@ private fun SendWithSwapSuccessContent_Preview() {
fees = TransactionFee.Single(
normal = Fee.Common(
BigDecimal.ONE.convertToSdkAmount(
SwapAmountContentPreview.cryptoCurrencyStatus.currency,
SwapAmountContentPreview.cryptoCurrencyStatus,
),
),
),
@ -315,7 +315,7 @@ private fun SendWithSwapSuccessContent_Preview() {
selectedFeeItem = FeeItem.Market(
Fee.Common(
BigDecimal.ONE.convertToSdkAmount(
SwapAmountContentPreview.cryptoCurrencyStatus.currency,
SwapAmountContentPreview.cryptoCurrencyStatus,
),
),
),