Updated on 2026-08-14
This commit is contained in:
parent
5988ef1834
commit
bdaa181aec
13 changed files with 790 additions and 0 deletions
|
|
@ -34,6 +34,9 @@ dependencies {
|
|||
implementation(projects.common.ui)
|
||||
implementation(projects.common.routing)
|
||||
|
||||
/** Libs */
|
||||
implementation(projects.libs.crypto)
|
||||
|
||||
implementation(tangemDeps.blockchain) {
|
||||
exclude(module = "joda-time")
|
||||
}
|
||||
|
|
@ -54,6 +57,9 @@ dependencies {
|
|||
implementation(projects.domain.transaction.models)
|
||||
implementation(projects.domain.transaction)
|
||||
implementation(projects.domain.legacy)
|
||||
implementation(projects.domain.balanceHiding.models)
|
||||
implementation(projects.domain.balanceHiding)
|
||||
implementation(projects.domain.settings)
|
||||
|
||||
/** Compose */
|
||||
implementation(deps.compose.foundation)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
package com.tangem.features.swap.v2.impl.common
|
||||
|
||||
internal object SwapUtils {
|
||||
const val INCREASE_GAS_LIMIT_FOR_DEX = 112 // 12%
|
||||
const val INCREASE_GAS_LIMIT_FOR_CEX = 105 // 5%
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package com.tangem.features.swap.v2.impl.common.entity
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.tangem.common.ui.notifications.NotificationUM
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
@Immutable
|
||||
internal sealed class ConfirmUM {
|
||||
|
||||
abstract val isPrimaryButtonEnabled: Boolean
|
||||
|
||||
data class Content(
|
||||
override val isPrimaryButtonEnabled: Boolean = false,
|
||||
val isTransactionInProcess: Boolean,
|
||||
val showTapHelp: Boolean,
|
||||
val sendingFooter: TextReference,
|
||||
val notifications: ImmutableList<NotificationUM>,
|
||||
) : ConfirmUM()
|
||||
|
||||
data object Empty : ConfirmUM() {
|
||||
override val isPrimaryButtonEnabled: Boolean = false
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
package com.tangem.features.swap.v2.impl.sendviaswap
|
||||
|
||||
import com.tangem.core.decompose.navigation.Route
|
||||
import com.tangem.features.send.v2.api.subcomponents.destination.DestinationRoute
|
||||
import com.tangem.features.swap.v2.impl.amount.SwapAmountRoute
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
internal sealed class SendWithSwapRoute : Route {
|
||||
|
||||
abstract val isEditMode: Boolean
|
||||
|
||||
@Serializable
|
||||
data class Amount(
|
||||
override val isEditMode: Boolean,
|
||||
) : SendWithSwapRoute(), SwapAmountRoute
|
||||
|
||||
@Serializable
|
||||
data class Destination(
|
||||
override val isEditMode: Boolean,
|
||||
) : SendWithSwapRoute(), DestinationRoute
|
||||
|
||||
@Serializable
|
||||
data object Confirm : SendWithSwapRoute() {
|
||||
override val isEditMode: Boolean = false
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data object Success : SendWithSwapRoute() {
|
||||
override val isEditMode: Boolean = false
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,162 @@
|
|||
package com.tangem.features.swap.v2.impl.sendviaswap.confirm
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
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.ComposableContentComponent
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.swap.models.SwapDirection
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.features.send.v2.api.FeeSelectorBlockComponent
|
||||
import com.tangem.features.send.v2.api.SendNotificationsComponent
|
||||
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
|
||||
import com.tangem.features.send.v2.api.subcomponents.destination.SendDestinationComponentParams
|
||||
import com.tangem.features.swap.v2.impl.amount.SwapAmountBlockComponent
|
||||
import com.tangem.features.swap.v2.impl.amount.SwapAmountComponentParams
|
||||
import com.tangem.features.swap.v2.impl.common.entity.ConfirmUM
|
||||
import com.tangem.features.swap.v2.impl.sendviaswap.SendWithSwapRoute
|
||||
import com.tangem.features.swap.v2.impl.sendviaswap.confirm.model.SendWithSwapConfirmModel
|
||||
import com.tangem.features.swap.v2.impl.sendviaswap.confirm.ui.SendWithSwapConfirmContent
|
||||
import com.tangem.features.swap.v2.impl.sendviaswap.entity.SendWithSwapUM
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import kotlinx.coroutines.flow.*
|
||||
import java.math.BigDecimal
|
||||
|
||||
internal class SendWithSwapConfirmComponent @AssistedInject constructor(
|
||||
@Assisted private val appComponentContext: AppComponentContext,
|
||||
@Assisted private val params: Params,
|
||||
sendDestinationBlockComponent: SendDestinationBlockComponent.Factory,
|
||||
feeSelectorBlockComponentFactory: FeeSelectorBlockComponent.Factory,
|
||||
sendNotificationsComponentFactory: SendNotificationsComponent.Factory,
|
||||
) : ComposableContentComponent, AppComponentContext by appComponentContext {
|
||||
|
||||
private val model: SendWithSwapConfirmModel = getOrCreateModel(params = params)
|
||||
|
||||
private val blockClickEnableFlow = MutableStateFlow(false)
|
||||
|
||||
private val amountBlockComponent = SwapAmountBlockComponent(
|
||||
appComponentContext = child("sendWithSwapConfirmAmountBlock"),
|
||||
params = SwapAmountComponentParams.AmountBlockParams(
|
||||
amountUM = model.uiState.value.amountUM,
|
||||
analyticsCategoryName = params.analyticsCategoryName,
|
||||
userWallet = params.userWallet,
|
||||
blockClickEnableFlow = blockClickEnableFlow.asStateFlow(),
|
||||
primaryCryptoCurrencyStatusFlow = params.primaryCryptoCurrencyStatusFlow,
|
||||
secondaryCryptoCurrency = model.secondaryCurrency,
|
||||
isBalanceHidingFlow = params.isBalanceHidingFlow,
|
||||
swapDirection = params.swapDirection,
|
||||
),
|
||||
onResult = model::onAmountResult,
|
||||
onClick = model::showEditAmount,
|
||||
)
|
||||
|
||||
private val sendDestinationBlockComponent = sendDestinationBlockComponent.create(
|
||||
context = child("sendWithSwapConfirmDestinationBlock"),
|
||||
params = SendDestinationComponentParams.DestinationBlockParams(
|
||||
state = model.uiState.value.destinationUM,
|
||||
analyticsCategoryName = params.analyticsCategoryName,
|
||||
userWalletId = params.userWallet.walletId,
|
||||
blockClickEnableFlow = blockClickEnableFlow.asStateFlow(),
|
||||
cryptoCurrency = model.secondaryCurrency,
|
||||
predefinedValues = PredefinedValues.Empty,
|
||||
),
|
||||
onResult = model::onDestinationResult,
|
||||
onClick = model::showEditDestination,
|
||||
)
|
||||
|
||||
private val feeSelectorBlockComponent = feeSelectorBlockComponentFactory.create(
|
||||
context = child("sendWithSwapConfirmFeeBlock"),
|
||||
params = FeeSelectorParams.FeeSelectorBlockParams(
|
||||
state = model.uiState.value.feeSelectorUM,
|
||||
onLoadFee = model::loadFee,
|
||||
feeCryptoCurrencyStatus = model.primaryFeePaidCurrencyStatus,
|
||||
cryptoCurrencyStatus = model.primaryCurrencyStatus,
|
||||
suggestedFeeState = FeeSelectorParams.SuggestedFeeState.None,
|
||||
feeDisplaySource = FeeSelectorParams.FeeDisplaySource.Screen,
|
||||
),
|
||||
onResult = model::onFeeResult,
|
||||
)
|
||||
|
||||
private val sendNotificationsComponent = sendNotificationsComponentFactory.create(
|
||||
context = appComponentContext.childByContext(child("sendWithSwapConfirmNotifications")),
|
||||
params = SendNotificationsComponent.Params(
|
||||
analyticsCategoryName = params.analyticsCategoryName,
|
||||
userWalletId = params.userWallet.walletId,
|
||||
cryptoCurrencyStatus = model.primaryCurrencyStatus,
|
||||
feeCryptoCurrencyStatus = model.primaryFeePaidCurrencyStatus,
|
||||
appCurrency = params.appCurrency,
|
||||
notificationData = SendNotificationsComponent.Params.NotificationData(
|
||||
// todo fill with data
|
||||
destinationAddress = "",
|
||||
memo = "",
|
||||
amountValue = BigDecimal.ZERO,
|
||||
reduceAmountBy = BigDecimal.ZERO,
|
||||
isIgnoreReduce = false,
|
||||
fee = null,
|
||||
feeError = null,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
init {
|
||||
model.uiState.onEach { state ->
|
||||
val confirmUM = state.confirmUM as? ConfirmUM.Content
|
||||
blockClickEnableFlow.value = confirmUM?.isTransactionInProcess == false
|
||||
}.launchIn(componentScope)
|
||||
}
|
||||
|
||||
fun updateState(sendWithSwapUM: SendWithSwapUM) {
|
||||
amountBlockComponent.updateState(sendWithSwapUM.amountUM)
|
||||
sendDestinationBlockComponent.updateState(sendWithSwapUM.destinationUM)
|
||||
feeSelectorBlockComponent.updateState(sendWithSwapUM.feeSelectorUM)
|
||||
model.updateState(sendWithSwapUM)
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
val sendWithSwapUM by model.uiState.collectAsStateWithLifecycle()
|
||||
val sendNotificationsUM by sendNotificationsComponent.state.collectAsStateWithLifecycle()
|
||||
|
||||
SendWithSwapConfirmContent(
|
||||
sendWithSwapUM = sendWithSwapUM,
|
||||
amountBlockComponent = amountBlockComponent,
|
||||
sendDestinationBlockComponent = sendDestinationBlockComponent,
|
||||
feeSelectorBlockComponent = feeSelectorBlockComponent,
|
||||
sendNotificationsComponent = sendNotificationsComponent,
|
||||
sendNotificationsUM = sendNotificationsUM,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
data class Params(
|
||||
val sendWithSwapUM: SendWithSwapUM,
|
||||
val analyticsCategoryName: String,
|
||||
val userWallet: UserWallet,
|
||||
val appCurrency: AppCurrency,
|
||||
val currentRoute: Flow<SendWithSwapRoute>,
|
||||
val swapDirection: SwapDirection,
|
||||
val isBalanceHidingFlow: StateFlow<Boolean>,
|
||||
val primaryCryptoCurrencyStatusFlow: StateFlow<CryptoCurrencyStatus>,
|
||||
val primaryFeePaidCurrencyStatusFlow: StateFlow<CryptoCurrencyStatus>,
|
||||
val callback: ModelCallback,
|
||||
)
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory {
|
||||
fun create(appComponentContext: AppComponentContext, params: Params): SendWithSwapConfirmComponent
|
||||
}
|
||||
|
||||
interface ModelCallback {
|
||||
fun onResult(sendWithSwapUM: SendWithSwapUM)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package com.tangem.features.swap.v2.impl.sendviaswap.confirm.di
|
||||
|
||||
import com.tangem.core.decompose.di.ModelComponent
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.features.swap.v2.impl.sendviaswap.confirm.model.SendWithSwapConfirmModel
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.multibindings.ClassKey
|
||||
import dagger.multibindings.IntoMap
|
||||
|
||||
@Module
|
||||
@InstallIn(ModelComponent::class)
|
||||
internal interface SendWithSwapConfirmModule {
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ClassKey(SendWithSwapConfirmModel::class)
|
||||
fun bindsSendWithSwapConfirmModel(impl: SendWithSwapConfirmModel): Model
|
||||
}
|
||||
|
|
@ -0,0 +1,239 @@
|
|||
package com.tangem.features.swap.v2.impl.sendviaswap.confirm.model
|
||||
|
||||
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.models.AmountState
|
||||
import com.tangem.common.ui.navigationButtons.NavigationButton
|
||||
import com.tangem.common.ui.navigationButtons.NavigationUM
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
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.ui.extensions.resourceReference
|
||||
import com.tangem.domain.express.models.ExpressProviderType
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.settings.IsSendTapHelpEnabledUseCase
|
||||
import com.tangem.domain.swap.models.SwapDirection.Companion.withSwapDirection
|
||||
import com.tangem.domain.tokens.IsAmountSubtractAvailableUseCase
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.transaction.error.GetFeeError
|
||||
import com.tangem.domain.transaction.usecase.EstimateFeeUseCase
|
||||
import com.tangem.features.send.v2.api.SendNotificationsComponent.Params.NotificationData
|
||||
import com.tangem.features.send.v2.api.callbacks.FeeSelectorModelCallback
|
||||
import com.tangem.features.send.v2.api.entity.FeeSelectorUM
|
||||
import com.tangem.features.send.v2.api.subcomponents.destination.entity.DestinationUM
|
||||
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.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
|
||||
import com.tangem.features.swap.v2.impl.common.entity.SwapQuoteUM
|
||||
import com.tangem.features.swap.v2.impl.sendviaswap.SendWithSwapRoute
|
||||
import com.tangem.features.swap.v2.impl.sendviaswap.confirm.SendWithSwapConfirmComponent
|
||||
import com.tangem.features.swap.v2.impl.sendviaswap.confirm.model.transformers.SendWithSwapConfirmInitialStateTransformer
|
||||
import com.tangem.features.swap.v2.impl.sendviaswap.confirm.model.transformers.SendWithSwapConfirmationNotificationsTransformer
|
||||
import com.tangem.features.swap.v2.impl.sendviaswap.entity.SendWithSwapUM
|
||||
import com.tangem.lib.crypto.BlockchainFeeUtils.patchTransactionFeeForSwap
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.extensions.orZero
|
||||
import jakarta.inject.Inject
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import com.tangem.utils.transformer.update as transformerUpdate
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@ModelScoped
|
||||
internal class SendWithSwapConfirmModel @Inject constructor(
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val router: Router,
|
||||
private val isSendTapHelpEnabledUseCase: IsSendTapHelpEnabledUseCase,
|
||||
private val estimateFeeUseCase: EstimateFeeUseCase,
|
||||
private val isAmountSubtractAvailableUseCase: IsAmountSubtractAvailableUseCase,
|
||||
private val sendNotificationsUpdateTrigger: SendNotificationsUpdateTrigger,
|
||||
paramsContainer: ParamsContainer,
|
||||
) : Model(), FeeSelectorModelCallback {
|
||||
|
||||
private val params: SendWithSwapConfirmComponent.Params = paramsContainer.require()
|
||||
|
||||
val uiState: StateFlow<SendWithSwapUM>
|
||||
field = MutableStateFlow(params.sendWithSwapUM)
|
||||
|
||||
val primaryCurrencyStatus: CryptoCurrencyStatus = params.primaryCryptoCurrencyStatusFlow.value
|
||||
val primaryFeePaidCurrencyStatus: CryptoCurrencyStatus = params.primaryFeePaidCurrencyStatusFlow.value
|
||||
|
||||
private val amountUM = uiState.value.amountUM as? SwapAmountUM.Content
|
||||
|
||||
val secondaryCurrency: CryptoCurrency = requireNotNull(amountUM?.secondaryCryptoCurrencyStatus?.currency) {
|
||||
"Crypto currency must not be null"
|
||||
}
|
||||
|
||||
private var isAmountSubtractAvailable = false
|
||||
|
||||
init {
|
||||
initAmountSubtractAvailability()
|
||||
configConfirmNavigation()
|
||||
initialState()
|
||||
}
|
||||
|
||||
override fun onFeeResult(feeSelectorUM: FeeSelectorUM) {
|
||||
uiState.update { it.copy(feeSelectorUM = feeSelectorUM) }
|
||||
updateConfirmNotifications()
|
||||
}
|
||||
|
||||
fun onAmountResult(amountUM: SwapAmountUM) {
|
||||
uiState.update { it.copy(amountUM = amountUM) }
|
||||
updateConfirmNotifications()
|
||||
}
|
||||
|
||||
fun onDestinationResult(destinationUM: DestinationUM) {
|
||||
uiState.update { it.copy(destinationUM = destinationUM) }
|
||||
updateConfirmNotifications()
|
||||
}
|
||||
|
||||
fun updateState(sendWithSwapUM: SendWithSwapUM) {
|
||||
uiState.value = sendWithSwapUM
|
||||
}
|
||||
|
||||
fun showEditAmount() {
|
||||
router.push(SendWithSwapRoute.Amount(isEditMode = true))
|
||||
}
|
||||
|
||||
fun showEditDestination() {
|
||||
router.push(SendWithSwapRoute.Destination(isEditMode = true))
|
||||
}
|
||||
|
||||
suspend fun loadFee(): Either<GetFeeError, TransactionFee> {
|
||||
val defaultError = GetFeeError.UnknownError.left()
|
||||
val quote = amountUM?.selectedQuote as? SwapQuoteUM.Content ?: return defaultError
|
||||
val amountUM = uiState.value.amountUM as? SwapAmountUM.Content ?: return defaultError
|
||||
|
||||
val amountField = amountUM.swapDirection.withSwapDirection(
|
||||
onDirect = { amountUM.primaryAmount.amountField },
|
||||
onReverse = { amountUM.secondaryAmount.amountField },
|
||||
) as? AmountState.Data ?: return defaultError
|
||||
val amountValue = amountField.amountTextField.cryptoAmount.value ?: return defaultError
|
||||
|
||||
return when (val providerType = quote.provider.type) {
|
||||
ExpressProviderType.CEX -> {
|
||||
estimateFeeUseCase(
|
||||
amount = amountValue,
|
||||
userWallet = params.userWallet,
|
||||
cryptoCurrency = primaryCurrencyStatus.currency,
|
||||
).map {
|
||||
it.patchTransactionFeeForSwap(INCREASE_GAS_LIMIT_FOR_CEX)
|
||||
}
|
||||
}
|
||||
ExpressProviderType.DEX,
|
||||
ExpressProviderType.DEX_BRIDGE,
|
||||
-> {
|
||||
// todo send with swap
|
||||
GetFeeError.UnknownError.left()
|
||||
}
|
||||
ExpressProviderType.ONRAMP,
|
||||
-> GetFeeError.DataError(
|
||||
cause = IllegalStateException("Provider $providerType is not supported in Send With Swap"),
|
||||
).left()
|
||||
}
|
||||
}
|
||||
|
||||
private fun onSendClick() {
|
||||
// todo swap send tx
|
||||
}
|
||||
|
||||
private fun initAmountSubtractAvailability() {
|
||||
modelScope.launch {
|
||||
isAmountSubtractAvailable =
|
||||
isAmountSubtractAvailableUseCase(
|
||||
params.userWallet.walletId,
|
||||
primaryCurrencyStatus.currency,
|
||||
).getOrElse { false }
|
||||
}
|
||||
}
|
||||
|
||||
private fun initialState() {
|
||||
val confirmUM = uiState.value.confirmUM
|
||||
|
||||
modelScope.launch {
|
||||
val isShowTapHelp = isSendTapHelpEnabledUseCase().getOrElse { false }
|
||||
if (confirmUM is ConfirmUM.Empty) {
|
||||
uiState.update {
|
||||
it.copy(
|
||||
confirmUM = SendWithSwapConfirmInitialStateTransformer(
|
||||
isShowTapHelp = isShowTapHelp,
|
||||
).transform(uiState.value.confirmUM),
|
||||
)
|
||||
}
|
||||
updateConfirmNotifications()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateConfirmNotifications() {
|
||||
val amountUM = uiState.value.amountUM as? SwapAmountUM.Content ?: return
|
||||
val destinationUM = uiState.value.destinationUM as? DestinationUM.Content ?: return
|
||||
val feeSelectorUMContent = uiState.value.feeSelectorUM as? FeeSelectorUM.Content
|
||||
val feeSelectorUMError = uiState.value.feeSelectorUM as? FeeSelectorUM.Error
|
||||
|
||||
val amountField = amountUM.swapDirection.withSwapDirection(
|
||||
onDirect = { amountUM.primaryAmount.amountField },
|
||||
onReverse = { amountUM.secondaryAmount.amountField },
|
||||
) as? AmountState.Data ?: return
|
||||
val enteredDestination = destinationUM.addressTextField.actualAddress
|
||||
|
||||
modelScope.launch {
|
||||
sendNotificationsUpdateTrigger.triggerUpdate(
|
||||
data = NotificationData(
|
||||
destinationAddress = enteredDestination,
|
||||
memo = null,
|
||||
amountValue = amountField.amountTextField.cryptoAmount.value.orZero(),
|
||||
reduceAmountBy = amountField.reduceAmountBy.orZero(),
|
||||
isIgnoreReduce = amountField.isIgnoreReduce,
|
||||
fee = feeSelectorUMContent?.selectedFeeItem?.fee,
|
||||
feeError = feeSelectorUMError?.error,
|
||||
),
|
||||
)
|
||||
uiState.transformerUpdate(
|
||||
SendWithSwapConfirmationNotificationsTransformer(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun configConfirmNavigation() {
|
||||
combine(
|
||||
flow = uiState,
|
||||
flow2 = params.currentRoute,
|
||||
transform = { state, route -> state to route },
|
||||
).filter {
|
||||
it.second is SendWithSwapRoute.Confirm
|
||||
}.onEach { (state, _) ->
|
||||
val confirmUM = state.confirmUM
|
||||
params.callback.onResult(
|
||||
state.copy(
|
||||
navigationUM = NavigationUM.Content(
|
||||
title = resourceReference(id = R.string.send_with_swap_confirm_title),
|
||||
subtitle = null,
|
||||
backIconRes = R.drawable.ic_back_24,
|
||||
backIconClick = router::pop,
|
||||
primaryButton = NavigationButton(
|
||||
textReference = resourceReference(R.string.common_send),
|
||||
iconRes = R.drawable.ic_tangem_24,
|
||||
isEnabled = confirmUM.isPrimaryButtonEnabled,
|
||||
onClick = {
|
||||
when (confirmUM) {
|
||||
is ConfirmUM.Content -> if (confirmUM.isTransactionInProcess) {
|
||||
return@NavigationButton
|
||||
} else {
|
||||
onSendClick()
|
||||
}
|
||||
else -> return@NavigationButton
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
}.launchIn(modelScope)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package com.tangem.features.swap.v2.impl.sendviaswap.confirm.model.transformers
|
||||
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.features.swap.v2.impl.common.entity.ConfirmUM
|
||||
import com.tangem.utils.transformer.Transformer
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
internal class SendWithSwapConfirmInitialStateTransformer(
|
||||
private val isShowTapHelp: Boolean,
|
||||
) : Transformer<ConfirmUM> {
|
||||
override fun transform(prevState: ConfirmUM): ConfirmUM {
|
||||
return ConfirmUM.Content(
|
||||
isPrimaryButtonEnabled = false,
|
||||
isTransactionInProcess = false,
|
||||
showTapHelp = isShowTapHelp,
|
||||
sendingFooter = TextReference.EMPTY,
|
||||
notifications = persistentListOf(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,106 @@
|
|||
package com.tangem.features.swap.v2.impl.sendviaswap.confirm.model.transformers
|
||||
|
||||
import com.tangem.blockchain.common.transaction.Fee
|
||||
import com.tangem.common.ui.amountScreen.models.AmountState
|
||||
import com.tangem.common.ui.notifications.NotificationUM
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.core.ui.format.bigdecimal.fiat
|
||||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
import com.tangem.domain.swap.models.SwapDirection.Companion.withSwapDirection
|
||||
import com.tangem.features.send.v2.api.entity.FeeSelectorUM
|
||||
import com.tangem.features.send.v2.api.subcomponents.feeSelector.utils.FeeCalculationUtils.checkIfCustomFeeTooHigh
|
||||
import com.tangem.features.send.v2.api.subcomponents.feeSelector.utils.FeeCalculationUtils.checkIfCustomFeeTooLow
|
||||
import com.tangem.features.swap.v2.impl.R
|
||||
import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountUM
|
||||
import com.tangem.features.swap.v2.impl.common.entity.ConfirmUM
|
||||
import com.tangem.features.swap.v2.impl.sendviaswap.entity.SendWithSwapUM
|
||||
import com.tangem.utils.transformer.Transformer
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
|
||||
internal class SendWithSwapConfirmationNotificationsTransformer : Transformer<SendWithSwapUM> {
|
||||
override fun transform(prevState: SendWithSwapUM): SendWithSwapUM {
|
||||
val confirmUM = prevState.confirmUM as? ConfirmUM.Content ?: return prevState
|
||||
val feeSelectorUM = prevState.feeSelectorUM as? FeeSelectorUM.Content ?: return prevState
|
||||
|
||||
return prevState.copy(
|
||||
confirmUM = confirmUM.copy(
|
||||
sendingFooter = getSendingFooterText(feeSelectorUM, prevState.amountUM),
|
||||
notifications = buildList {
|
||||
addTooHighNotification(feeSelectorUM = feeSelectorUM)
|
||||
addTooLowNotification(feeSelectorUM = feeSelectorUM)
|
||||
}.toPersistentList(),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun MutableList<NotificationUM>.addTooLowNotification(feeSelectorUM: FeeSelectorUM.Content) {
|
||||
if (checkIfCustomFeeTooLow(feeSelectorUM = feeSelectorUM)) {
|
||||
add(NotificationUM.Warning.FeeTooLow)
|
||||
}
|
||||
}
|
||||
|
||||
private fun MutableList<NotificationUM>.addTooHighNotification(feeSelectorUM: FeeSelectorUM.Content) {
|
||||
val (isFeeTooHigh, diff) = checkIfCustomFeeTooHigh(feeSelectorUM = feeSelectorUM)
|
||||
if (isFeeTooHigh) {
|
||||
add(NotificationUM.Warning.TooHigh(diff))
|
||||
}
|
||||
}
|
||||
|
||||
private fun getSendingFooterText(feeSelectorUM: FeeSelectorUM.Content, swapAmountUM: SwapAmountUM): TextReference {
|
||||
val amountUM = swapAmountUM.swapDirection.withSwapDirection(
|
||||
onDirect = { swapAmountUM.primaryAmount.amountField },
|
||||
onReverse = { swapAmountUM.secondaryAmount.amountField },
|
||||
) as? AmountState.Data
|
||||
val feeItem = feeSelectorUM.selectedFeeItem
|
||||
val feeFiatRateUM = feeSelectorUM.feeFiatRateUM
|
||||
|
||||
val appCurrency = feeFiatRateUM?.appCurrency
|
||||
|
||||
if (amountUM == null || appCurrency == null) return TextReference.EMPTY
|
||||
|
||||
val fiatAmountValue = amountUM.amountTextField.fiatAmount.value
|
||||
val fiatFeeValue = feeItem.fee.amount.value?.multiply(feeFiatRateUM.rate)
|
||||
|
||||
val fiatSendingValue = if (feeSelectorUM.feeExtraInfo.isFeeConvertibleToFiat) {
|
||||
fiatFeeValue?.let { fiatAmountValue?.plus(it) }
|
||||
} else {
|
||||
fiatAmountValue
|
||||
}
|
||||
|
||||
val fiatSending = fiatSendingValue.format {
|
||||
fiat(
|
||||
fiatCurrencyCode = appCurrency.code,
|
||||
fiatCurrencySymbol = appCurrency.symbol,
|
||||
)
|
||||
}
|
||||
val fiatFee = TextReference.EMPTY
|
||||
// todo send with swap footer
|
||||
// formatFooterFiatFee(
|
||||
// amount = feeItem.fee.amount.copy(value = fiatFeeValue),
|
||||
// isFeeConvertibleToFiat = feeSelectorUM.feeExtraInfo.isFeeConvertibleToFiat,
|
||||
// isFeeApproximate = feeSelectorUM.feeExtraInfo.isFeeApproximate,
|
||||
// appCurrency = appCurrency,
|
||||
// )
|
||||
|
||||
return if (feeSelectorUM.feeExtraInfo.isTronToken && feeItem.fee is Fee.Tron) {
|
||||
// todo send with swap footer
|
||||
// getTronTokenFeeSendingText(
|
||||
// fee = feeItem.fee,
|
||||
// fiatFee = fiatFee,
|
||||
// fiatSending = stringReference(fiatSending),
|
||||
// )
|
||||
TextReference.EMPTY
|
||||
} else {
|
||||
resourceReference(
|
||||
id = if (feeSelectorUM.feeExtraInfo.isFeeConvertibleToFiat) {
|
||||
R.string.send_summary_transaction_description
|
||||
} else {
|
||||
R.string.send_summary_transaction_description_no_fiat_fee
|
||||
},
|
||||
formatArgs = wrappedList(fiatSending, fiatFee),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
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
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.common.ui.notifications.NotificationUM
|
||||
import com.tangem.core.ui.components.SpacerHMax
|
||||
import com.tangem.features.send.v2.api.FeeSelectorBlockComponent
|
||||
import com.tangem.features.send.v2.api.SendNotificationsComponent
|
||||
import com.tangem.features.send.v2.api.subcomponents.destination.SendDestinationBlockComponent
|
||||
import com.tangem.features.swap.v2.impl.amount.SwapAmountBlockComponent
|
||||
import com.tangem.features.swap.v2.impl.common.entity.ConfirmUM
|
||||
import com.tangem.features.swap.v2.impl.sendviaswap.entity.SendWithSwapUM
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@Composable
|
||||
internal fun SendWithSwapConfirmContent(
|
||||
sendWithSwapUM: SendWithSwapUM,
|
||||
amountBlockComponent: SwapAmountBlockComponent,
|
||||
sendDestinationBlockComponent: SendDestinationBlockComponent,
|
||||
feeSelectorBlockComponent: FeeSelectorBlockComponent,
|
||||
sendNotificationsComponent: SendNotificationsComponent,
|
||||
sendNotificationsUM: ImmutableList<NotificationUM>,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val confirmUM = sendWithSwapUM.confirmUM as? ConfirmUM.Content
|
||||
|
||||
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)),
|
||||
) {
|
||||
feeSelectorBlockComponent.Content(Modifier)
|
||||
}
|
||||
}
|
||||
if (confirmUM != null) {
|
||||
// tapHelp(isDisplay = confirmUM.showTapHelp) // todo
|
||||
with(sendNotificationsComponent) {
|
||||
content(
|
||||
state = sendNotificationsUM,
|
||||
isClickDisabled = confirmUM.isTransactionInProcess,
|
||||
)
|
||||
}
|
||||
// notifications(
|
||||
// notifications = confirmUM.notifications,
|
||||
// isClickDisabled = confirmUM.isTransactionInProcess,
|
||||
// )
|
||||
}
|
||||
}
|
||||
SpacerHMax()
|
||||
// todo
|
||||
// SendingText(footerText = confirmUM?.sendingFooter ?: TextReference.EMPTY)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package com.tangem.features.swap.v2.impl.sendviaswap.entity
|
||||
|
||||
import com.tangem.common.ui.navigationButtons.NavigationUM
|
||||
import com.tangem.features.send.v2.api.entity.FeeSelectorUM
|
||||
import com.tangem.features.send.v2.api.subcomponents.destination.entity.DestinationUM
|
||||
import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountUM
|
||||
import com.tangem.features.swap.v2.impl.common.entity.ConfirmUM
|
||||
|
||||
internal data class SendWithSwapUM(
|
||||
val amountUM: SwapAmountUM,
|
||||
val destinationUM: DestinationUM,
|
||||
val feeSelectorUM: FeeSelectorUM,
|
||||
val confirmUM: ConfirmUM,
|
||||
val navigationUM: NavigationUM,
|
||||
)
|
||||
Loading…
Add table
Add a link
Reference in a new issue