diff --git a/features/swap-v2/api/src/main/java/com/tangem/features/swap/v2/api/subcomponents/SwapAmountUpdateTrigger.kt b/features/swap-v2/api/src/main/java/com/tangem/features/swap/v2/api/subcomponents/SwapAmountUpdateTrigger.kt index 651312d68f..5cec5c47df 100644 --- a/features/swap-v2/api/src/main/java/com/tangem/features/swap/v2/api/subcomponents/SwapAmountUpdateTrigger.kt +++ b/features/swap-v2/api/src/main/java/com/tangem/features/swap/v2/api/subcomponents/SwapAmountUpdateTrigger.kt @@ -6,4 +6,6 @@ package com.tangem.features.swap.v2.api.subcomponents */ interface SwapAmountUpdateTrigger { suspend fun triggerUpdateAmount(amountValue: String) + + suspend fun triggerQuoteReload() } \ No newline at end of file diff --git a/features/swap-v2/impl/build.gradle.kts b/features/swap-v2/impl/build.gradle.kts index cb6af0648e..4fbb34b684 100644 --- a/features/swap-v2/impl/build.gradle.kts +++ b/features/swap-v2/impl/build.gradle.kts @@ -62,6 +62,8 @@ dependencies { implementation(projects.domain.settings) implementation(projects.domain.txhistory.models) implementation(projects.domain.txhistory) + implementation(projects.domain.feedback.models) + implementation(projects.domain.feedback) /** Compose */ implementation(deps.compose.foundation) diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/DefaultSwapAmountUpdateTrigger.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/DefaultSwapAmountUpdateTrigger.kt index 98655efbd1..3fe62bce62 100644 --- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/DefaultSwapAmountUpdateTrigger.kt +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/DefaultSwapAmountUpdateTrigger.kt @@ -15,6 +15,8 @@ import javax.inject.Singleton */ interface SwapAmountUpdateListener { val updateAmountTriggerFlow: Flow + + val reloadQuotesTriggerFlow: Flow } /** @@ -54,10 +56,17 @@ internal class DefaultSwapAmountUpdateTrigger @Inject constructor() : override val ignoreReduceTriggerFlow: SharedFlow field = MutableSharedFlow() + override val reloadQuotesTriggerFlow: Flow + field = MutableSharedFlow() + override suspend fun triggerUpdateAmount(amountValue: String) { updateAmountTriggerFlow.emit(amountValue) } + override suspend fun triggerQuoteReload() { + reloadQuotesTriggerFlow.emit(Unit) + } + override suspend fun triggerReduceBy(reduceBy: ReduceByData) { reduceByTriggerFlow.emit(reduceBy) } diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/SwapAlertFactory.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/SwapAmountAlertFactory.kt similarity index 97% rename from features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/SwapAlertFactory.kt rename to features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/SwapAmountAlertFactory.kt index 80af642d28..42d272be56 100644 --- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/SwapAlertFactory.kt +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/SwapAmountAlertFactory.kt @@ -12,7 +12,7 @@ import com.tangem.utils.StringsSigns.PERCENT import javax.inject.Inject @ModelScoped -internal class SwapAlertFactory @Inject constructor( +internal class SwapAmountAlertFactory @Inject constructor( private val uiMessageSender: UiMessageSender, ) { diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/SwapAmountModel.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/SwapAmountModel.kt index 80bf23dc34..accd9b772c 100644 --- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/SwapAmountModel.kt +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/SwapAmountModel.kt @@ -40,6 +40,7 @@ import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountUM import com.tangem.features.swap.v2.impl.amount.model.converter.SwapQuoteUMConverter import com.tangem.features.swap.v2.impl.amount.model.transformers.* import com.tangem.features.swap.v2.impl.chooseprovider.SwapChooseProviderComponent +import com.tangem.features.swap.v2.impl.common.SwapAlertFactory import com.tangem.features.swap.v2.impl.common.entity.SwapQuoteUM import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.extensions.orZero @@ -48,6 +49,7 @@ import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch +import timber.log.Timber import java.math.BigDecimal import javax.inject.Inject import kotlin.properties.Delegates @@ -67,6 +69,7 @@ internal class SwapAmountModel @Inject constructor( private val getAllowanceUseCase: GetAllowanceUseCase, private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, private val appRouter: AppRouter, + private val swapAmountAlertFactory: SwapAmountAlertFactory, private val swapAlertFactory: SwapAlertFactory, private val swapAmountUpdateListener: SwapAmountUpdateListener, private val swapAmountReduceListener: SwapAmountReduceListener, @@ -101,6 +104,7 @@ internal class SwapAmountModel @Inject constructor( subscribeOnAmountReduceToTriggerUpdates() subscribeOnAmountReduceByTriggerUpdates() subscribeOnAmountIgnoreReduceTriggerUpdates() + subscribeOnReloadQuotesTriggerUpdates() } fun updateState(amountUM: SwapAmountUM) { @@ -130,7 +134,7 @@ internal class SwapAmountModel @Inject constructor( val amountUM = uiState.value as? SwapAmountUM.Content ?: return val selectedProvider = amountUM.selectedQuote.provider ?: return - swapAlertFactory.priceImpactAlert( + swapAmountAlertFactory.priceImpactAlert( hasPriceImpact = (amountUM.secondaryAmount as? SwapAmountFieldUM.Content)?.priceImpact != null, currencySymbol = amountUM.primaryCryptoCurrencyStatus.currency.symbol, provider = selectedProvider, @@ -292,6 +296,12 @@ internal class SwapAmountModel @Inject constructor( .launchIn(modelScope) } + private fun subscribeOnReloadQuotesTriggerUpdates() { + swapAmountUpdateListener.reloadQuotesTriggerFlow + .onEach { loadQuotes(reloadFees = true) } + .launchIn(modelScope) + } + private fun observeChooseSelectToken() { swapChooseTokenNetworkListener.swapChooseTokenNetworkResultFlow .onEach { currency -> @@ -356,11 +366,19 @@ internal class SwapAmountModel @Inject constructor( ) loadQuotes() } else { - // todo not available currency to swap + Timber.e( + """ + Invalid cryptocurrencies status: + | Primary -> $primaryStatus + | Secondary -> $secondaryStatus + """.trimIndent(), + ) + showErrorAlert(errorMessage = null) } }, - ifLeft = { - // todo error + ifLeft = { error -> + Timber.e(error) + showErrorAlert(errorMessage = error.message) }, ) } @@ -435,7 +453,7 @@ internal class SwapAmountModel @Inject constructor( SwapQuoteUM.Error( provider = provider, expressError = error, - ).takeIf { error is ExpressError.AmountError } + ) }, ifRight = { quote: SwapQuoteModel -> SwapQuoteUMConverter( @@ -454,7 +472,7 @@ internal class SwapAmountModel @Inject constructor( }, ) } - }.awaitAll().filterNotNull() + }.awaitAll() uiState.transformerUpdate( SwapAmountSetQuotesTransformer( @@ -490,6 +508,22 @@ internal class SwapAmountModel @Inject constructor( params.callback.onAmountResult(uiState.value) } + private fun showErrorAlert(errorMessage: String?) { + swapAlertFactory.getGenericErrorState( + expressError = ExpressError.UnknownError, + onFailedTxEmailClick = { + modelScope.launch { + swapAlertFactory.onFailedTxEmailClick( + userWallet = params.userWallet, + cryptoCurrency = primaryCryptoCurrency, + errorMessage = errorMessage, + ) + } + }, + popBack = appRouter::pop, + ) + } + private fun configAmountNavigation() { val params = params as? SwapAmountComponentParams.AmountParams ?: return combine( diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/transformers/SwapQuoteLoadingStateTransformer.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/transformers/SwapQuoteLoadingStateTransformer.kt index 12bcce175c..a26deaf753 100644 --- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/transformers/SwapQuoteLoadingStateTransformer.kt +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/transformers/SwapQuoteLoadingStateTransformer.kt @@ -8,12 +8,14 @@ internal object SwapQuoteLoadingStateTransformer : Transformer { override fun transform(prevState: SwapAmountUM): SwapAmountUM { if (prevState !is SwapAmountUM.Content) return prevState + when (prevState.selectedQuote) { + is SwapQuoteUM.Allowance, + is SwapQuoteUM.Content, + -> return prevState // No need to set loading state if quotes are already display + else -> Unit + } return prevState.copy( - selectedQuote = if (prevState.selectedQuote is SwapQuoteUM.Empty) { - SwapQuoteUM.Loading - } else { - prevState.selectedQuote - }, + selectedQuote = SwapQuoteUM.Loading, ) } } \ No newline at end of file diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/model/ConfirmData.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/common/ConfirmData.kt similarity index 91% rename from features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/model/ConfirmData.kt rename to features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/common/ConfirmData.kt index 96ab992e36..d902dee096 100644 --- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/model/ConfirmData.kt +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/common/ConfirmData.kt @@ -1,4 +1,4 @@ -package com.tangem.features.swap.v2.impl.sendviaswap.confirm.model +package com.tangem.features.swap.v2.impl.common import com.tangem.blockchain.common.transaction.Fee import com.tangem.domain.express.models.ExpressRateType diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/common/SwapAlertFactory.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/common/SwapAlertFactory.kt new file mode 100644 index 0000000000..7327c593f4 --- /dev/null +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/common/SwapAlertFactory.kt @@ -0,0 +1,110 @@ +package com.tangem.features.swap.v2.impl.common + +import com.tangem.common.ui.alerts.TransactionErrorAlertConverter +import com.tangem.common.ui.alerts.models.AlertDemoModeUM +import com.tangem.core.decompose.di.ModelScoped +import com.tangem.core.decompose.ui.UiMessageSender +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.message.DialogMessage +import com.tangem.core.ui.message.EventMessageAction +import com.tangem.domain.express.models.ExpressError +import com.tangem.domain.feedback.GetCardInfoUseCase +import com.tangem.domain.feedback.SaveBlockchainErrorUseCase +import com.tangem.domain.feedback.SendFeedbackEmailUseCase +import com.tangem.domain.feedback.models.BlockchainErrorInfo +import com.tangem.domain.feedback.models.FeedbackEmailType +import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.domain.models.wallet.UserWallet +import com.tangem.domain.models.wallet.requireColdWallet +import com.tangem.domain.transaction.error.SendTransactionError +import com.tangem.features.swap.v2.impl.R +import javax.inject.Inject + +@ModelScoped +internal class SwapAlertFactory @Inject constructor( + private val uiMessageSender: UiMessageSender, + private val saveBlockchainErrorUseCase: SaveBlockchainErrorUseCase, + private val getCardInfoUseCase: GetCardInfoUseCase, + private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase, +) { + fun getGenericErrorState(expressError: ExpressError, onFailedTxEmailClick: () -> Unit, popBack: () -> Unit = {}) { + uiMessageSender.send( + DialogMessage.Companion( + title = SwapUtils.getExpressErrorTitle(expressError), + message = SwapUtils.getExpressErrorMessage(expressError), + onDismissRequest = popBack, + dismissOnFirstAction = false, + firstActionBuilder = { + EventMessageAction( + title = resourceReference(R.string.common_support), + onClick = onFailedTxEmailClick, + ) + }, + secondActionBuilder = { cancelAction() }, + ), + ) + } + + fun getSendTransactionErrorState( + error: SendTransactionError?, + popBack: () -> Unit, + onFailedTxEmailClick: (String) -> Unit, + ) { + val transactionErrorAlertConverter = TransactionErrorAlertConverter( + popBackStack = popBack, + onFailedTxEmailClick = onFailedTxEmailClick, + ) + + val errorAlert = error?.let { transactionErrorAlertConverter.convert(error) } ?: return + val onConfirmClick = errorAlert.onConfirmClick ?: return + + uiMessageSender.send( + DialogMessage.Companion( + title = errorAlert.title, + message = errorAlert.message, + firstActionBuilder = { + EventMessageAction( + title = errorAlert.confirmButtonText, + onClick = onConfirmClick, + ) + }, + secondActionBuilder = if (errorAlert !is AlertDemoModeUM) { + { cancelAction() } + } else { + null + }, + ), + ) + } + + suspend fun onFailedTxEmailClick( + userWallet: UserWallet, + cryptoCurrency: CryptoCurrency?, + errorMessage: String?, + txId: String? = null, + confirmData: ConfirmData? = null, + ) { + saveBlockchainErrorUseCase( + error = BlockchainErrorInfo( + errorMessage = errorMessage.orEmpty(), + blockchainId = cryptoCurrency?.network?.rawId.orEmpty(), + derivationPath = cryptoCurrency?.network?.derivationPath?.value.orEmpty(), + destinationAddress = confirmData?.enteredDestination.orEmpty(), + tokenSymbol = confirmData?.toCryptoCurrencyStatus?.currency?.symbol.orEmpty(), + amount = confirmData?.enteredAmount?.toString().orEmpty(), + fee = confirmData?.fee?.amount?.value?.toString().orEmpty(), + ), + ) + + val cardInfo = + getCardInfoUseCase(userWallet.requireColdWallet().scanResponse).getOrNull() ?: return // TODO [REDACTED_TASK_KEY] + + sendFeedbackEmailUseCase( + type = FeedbackEmailType.SwapProblem( + cardInfo = cardInfo, + providerName = confirmData?.quote?.provider?.name.orEmpty(), + txId = txId.orEmpty(), + ), + ) + } +} \ No newline at end of file diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/notifications/SwapNotificationsComponent.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/notifications/SwapNotificationsComponent.kt index b52a113dee..8de4dc6649 100644 --- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/notifications/SwapNotificationsComponent.kt +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/notifications/SwapNotificationsComponent.kt @@ -3,12 +3,12 @@ package com.tangem.features.swap.v2.impl.notifications import androidx.compose.foundation.lazy.LazyListScope import androidx.compose.ui.Modifier import com.tangem.common.ui.notifications.NotificationUM -import com.tangem.common.ui.notifications.notifications import com.tangem.core.decompose.context.AppComponentContext import com.tangem.core.decompose.model.getOrCreateModel import com.tangem.domain.express.models.ExpressError import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.features.swap.v2.impl.notifications.model.SwapNotificationsModel +import com.tangem.features.swap.v2.impl.notifications.ui.swapNotifications import kotlinx.collections.immutable.ImmutableList import kotlinx.coroutines.flow.StateFlow @@ -26,7 +26,7 @@ internal class SwapNotificationsComponent( hasPaddingAbove: Boolean = false, isClickDisabled: Boolean = false, ) { - notifications( + swapNotifications( notifications = state, modifier = modifier, hasPaddingAbove = hasPaddingAbove, diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/notifications/model/SwapNotificationsModel.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/notifications/model/SwapNotificationsModel.kt index 02c24f08fe..2f4e43e68a 100644 --- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/notifications/model/SwapNotificationsModel.kt +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/notifications/model/SwapNotificationsModel.kt @@ -7,6 +7,7 @@ import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.ui.format.bigdecimal.crypto import com.tangem.core.ui.format.bigdecimal.format import com.tangem.domain.express.models.ExpressError +import com.tangem.features.swap.v2.api.subcomponents.SwapAmountUpdateTrigger import com.tangem.features.swap.v2.impl.notifications.DefaultSwapNotificationsUpdateTrigger import com.tangem.features.swap.v2.impl.notifications.SwapNotificationsComponent import com.tangem.features.swap.v2.impl.notifications.SwapNotificationsComponent.Params.SwapNotificationData @@ -28,6 +29,7 @@ internal class SwapNotificationsModel @Inject constructor( override val dispatchers: CoroutineDispatcherProvider, private val swapNotificationsUpdateListener: SwapNotificationsUpdateListener, private val swapNotificationsUpdateTrigger: DefaultSwapNotificationsUpdateTrigger, + private val swapAmountUpdateTrigger: SwapAmountUpdateTrigger, paramsContainer: ParamsContainer, ) : Model() { @@ -89,7 +91,9 @@ internal class SwapNotificationsModel @Inject constructor( else -> SwapNotificationUM.Warning.ExpressGeneralError( expressError = expressError, onConfirmClick = { - // todo reload quotes + modelScope.launch { + swapAmountUpdateTrigger.triggerQuoteReload() + } }, ) } diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/notifications/ui/SwapNotificationsContent.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/notifications/ui/SwapNotificationsContent.kt new file mode 100644 index 0000000000..9f1ec2d283 --- /dev/null +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/notifications/ui/SwapNotificationsContent.kt @@ -0,0 +1,52 @@ +package com.tangem.features.swap.v2.impl.notifications.ui + +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyListScope +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.tangem.common.ui.notifications.NotificationUM +import com.tangem.core.ui.components.notifications.Notification +import com.tangem.core.ui.res.TangemTheme +import com.tangem.features.swap.v2.impl.notifications.entity.SwapNotificationUM +import kotlinx.collections.immutable.ImmutableList + +internal fun LazyListScope.swapNotifications( + notifications: ImmutableList, + modifier: Modifier = Modifier, + hasPaddingAbove: Boolean = false, + isClickDisabled: Boolean = false, +) { + itemsIndexed( + items = notifications, + key = { _, item -> item::class.java }, + contentType = { _, item -> item::class.java }, + itemContent = { i, item -> + val topPadding = if (i == 0 && hasPaddingAbove) 0.dp else 12.dp + Notification( + config = item.config, + modifier = modifier + .padding(top = topPadding) + .animateItem(), + containerColor = when (item) { + is SwapNotificationUM.Error, + is SwapNotificationUM.Warning.ExpressGeneralError, + is NotificationUM.Error.TokenExceedsBalance, + is NotificationUM.Warning.NetworkFeeUnreachable, + is NotificationUM.Warning.HighFeeError, + -> TangemTheme.colors.background.action + else -> TangemTheme.colors.button.disabled + }, + iconTint = when (item) { + is SwapNotificationUM.Error, + is NotificationUM.Error.TokenExceedsBalance, + is NotificationUM.Warning, + -> null + is NotificationUM.Error -> TangemTheme.colors.icon.warning + is NotificationUM.Info -> TangemTheme.colors.icon.accent + }, + isEnabled = !isClickDisabled, + ) + }, + ) +} \ No newline at end of file diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/SendWithSwapConfirmComponent.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/SendWithSwapConfirmComponent.kt index 35eb1e5219..810c2b6f16 100644 --- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/SendWithSwapConfirmComponent.kt +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/SendWithSwapConfirmComponent.kt @@ -10,9 +10,9 @@ 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.models.wallet.UserWallet import com.tangem.domain.swap.models.SwapDirection import com.tangem.domain.tokens.model.CryptoCurrencyStatus -import com.tangem.domain.models.wallet.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 diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/model/SendWithSwapConfirmModel.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/model/SendWithSwapConfirmModel.kt index d3e1ae4a7c..912632f57b 100644 --- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/model/SendWithSwapConfirmModel.kt +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/model/SendWithSwapConfirmModel.kt @@ -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.routing.AppRouter import com.tangem.common.ui.amountScreen.converters.AmountReduceByTransformer import com.tangem.common.ui.amountScreen.models.AmountState import com.tangem.common.ui.navigationButtons.NavigationButton @@ -13,6 +14,7 @@ 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.ExpressError import com.tangem.domain.express.models.ExpressProviderType import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.settings.IsSendTapHelpEnabledUseCase @@ -33,6 +35,8 @@ import com.tangem.features.send.v2.api.subcomponents.notifications.SendNotificat 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.ConfirmData +import com.tangem.features.swap.v2.impl.common.SwapAlertFactory 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 @@ -53,7 +57,7 @@ import kotlinx.coroutines.launch import java.math.BigDecimal import com.tangem.utils.transformer.update as transformerUpdate -@Suppress("LongParameterList") +@Suppress("LongParameterList", "LargeClass") @ModelScoped internal class SendWithSwapConfirmModel @Inject constructor( override val dispatchers: CoroutineDispatcherProvider, @@ -68,6 +72,8 @@ internal class SendWithSwapConfirmModel @Inject constructor( private val swapNotificationsUpdateListener: SwapNotificationsUpdateListener, private val swapAmountReduceTrigger: SwapAmountReduceTrigger, private val feeSelectorReloadTrigger: FeeSelectorReloadTrigger, + private val swapAlertFactory: SwapAlertFactory, + private val appRouter: AppRouter, swapTransactionSenderFactory: SwapTransactionSender.Factory, paramsContainer: ParamsContainer, ) : Model(), FeeSelectorModelCallback, SendNotificationsComponent.ModelCallback { @@ -80,7 +86,6 @@ internal class SendWithSwapConfirmModel @Inject constructor( val primaryCurrencyStatus: CryptoCurrencyStatus = params.primaryCryptoCurrencyStatusFlow.value val secondaryCurrencyStatus: CryptoCurrencyStatus? = amountUM?.secondaryCryptoCurrencyStatus val primaryFeePaidCurrencyStatus: CryptoCurrencyStatus = params.primaryFeePaidCurrencyStatusFlow.value - val primaryCurrency: CryptoCurrency = primaryCurrencyStatus.currency val secondaryCurrency: CryptoCurrency = requireNotNull(amountUM?.secondaryCryptoCurrencyStatus?.currency) { "Crypto currency must not be null" } @@ -104,13 +109,14 @@ internal class SendWithSwapConfirmModel @Inject constructor( onReverse = { amountUM.secondaryAmount }, ) val amountState = fromAmount?.amountField as? AmountState.Data + val isQuoteContent = amountUM?.selectedQuote is SwapQuoteUM.Content return ConfirmData( enteredAmount = amountState?.amountTextField?.cryptoAmount?.value, - reduceAmountBy = amountState?.reduceAmountBy.orZero(), + reduceAmountBy = amountState?.reduceAmountBy.takeIf { isQuoteContent }.orZero(), isIgnoreReduce = amountState?.isIgnoreReduce == true, enteredDestination = destinationUM?.addressTextField?.actualAddress, - fee = feeSelectorUM?.selectedFeeItem?.fee, - feeError = (uiState.value.feeSelectorUM as? FeeSelectorUM.Error)?.error, + fee = feeSelectorUM?.selectedFeeItem?.fee.takeIf { isQuoteContent }, + feeError = (uiState.value.feeSelectorUM as? FeeSelectorUM.Error)?.error.takeIf { isQuoteContent }, fromCryptoCurrencyStatus = amountUM?.swapDirection?.withSwapDirection( onDirect = { primaryCurrencyStatus }, onReverse = { secondaryCurrencyStatus }, @@ -189,16 +195,11 @@ internal class SendWithSwapConfirmModel @Inject constructor( suspend fun loadFee(): Either { 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 + val provider = (confirmData.quote as? SwapQuoteUM.Content)?.provider ?: return defaultError + val amountValue = confirmData.enteredAmount ?: return defaultError - return when (val providerType = quote.provider.type) { + return when (val providerType = provider.type) { ExpressProviderType.CEX -> { estimateFeeUseCase( amount = amountValue, @@ -223,11 +224,37 @@ internal class SendWithSwapConfirmModel @Inject constructor( swapTransactionSender.sendTransaction( confirmData = confirmData, isAmountSubtractAvailable = isAmountSubtractAvailable, - onExpressError = { - // todo error + onExpressError = { expressError -> + swapAlertFactory.getGenericErrorState( + expressError = ExpressError.UnknownError, + onFailedTxEmailClick = { + modelScope.launch { + swapAlertFactory.onFailedTxEmailClick( + userWallet = params.userWallet, + cryptoCurrency = confirmData.fromCryptoCurrencyStatus?.currency, + errorMessage = expressError.message, + confirmData = confirmData, + ) + } + }, + popBack = appRouter::pop, + ) }, - onSendError = { - // todo error + onSendError = { error -> + swapAlertFactory.getSendTransactionErrorState( + error = error, + onFailedTxEmailClick = { + modelScope.launch { + swapAlertFactory.onFailedTxEmailClick( + userWallet = params.userWallet, + cryptoCurrency = confirmData.fromCryptoCurrencyStatus?.currency, + confirmData = confirmData, + errorMessage = error.toString(), + ) + } + }, + popBack = appRouter::pop, + ) }, onSendSuccess = { txHash, timestamp, data -> val txUrl = getExplorerTransactionUrlUseCase( @@ -281,36 +308,22 @@ internal class SendWithSwapConfirmModel @Inject constructor( } 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, + destinationAddress = confirmData.enteredDestination.orEmpty(), memo = null, - amountValue = amountField.amountTextField.cryptoAmount.value.orZero(), - reduceAmountBy = amountField.reduceAmountBy.orZero(), - isIgnoreReduce = amountField.isIgnoreReduce, - fee = feeSelectorUMContent?.selectedFeeItem?.fee, - feeError = feeSelectorUMError?.error, + amountValue = confirmData.enteredAmount.orZero(), + reduceAmountBy = confirmData.reduceAmountBy, + isIgnoreReduce = confirmData.isIgnoreReduce, + fee = confirmData.fee, + feeError = confirmData.feeError, ), ) swapNotificationsUpdateTrigger.triggerUpdate( data = SwapNotificationData( - expressError = (amountUM.selectedQuote as? SwapQuoteUM.Error)?.expressError, - fromCryptoCurrency = amountUM.swapDirection.withSwapDirection( - onDirect = { primaryCurrency }, - onReverse = { secondaryCurrency }, - ), + expressError = (confirmData.quote as? SwapQuoteUM.Error)?.expressError, + fromCryptoCurrency = confirmData.fromCryptoCurrencyStatus?.currency, ), ) uiState.transformerUpdate( diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/model/SwapTransactionSender.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/model/SwapTransactionSender.kt index 3fa20709be..e5dcb79e12 100644 --- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/model/SwapTransactionSender.kt +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/model/SwapTransactionSender.kt @@ -16,6 +16,7 @@ import com.tangem.domain.transaction.usecase.SendTransactionUseCase import com.tangem.domain.utils.convertToSdkAmount import com.tangem.domain.models.wallet.UserWallet import com.tangem.features.send.v2.api.subcomponents.feeSelector.utils.FeeCalculationUtils +import com.tangem.features.swap.v2.impl.common.ConfirmData import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/model/SendWithSwapModel.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/model/SendWithSwapModel.kt index 4090cce30e..d5ced02adb 100644 --- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/model/SendWithSwapModel.kt +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/model/SendWithSwapModel.kt @@ -11,6 +11,7 @@ import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase import com.tangem.domain.common.util.cardTypesResolver +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.tokens.GetFeePaidCryptoCurrencyStatusSyncUseCase @@ -27,6 +28,7 @@ import com.tangem.features.swap.v2.api.SendWithSwapComponent import com.tangem.features.swap.v2.impl.amount.SwapAmountComponent 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.common.SwapAlertFactory 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.entity.SendWithSwapUM @@ -47,6 +49,7 @@ internal class SendWithSwapModel @Inject constructor( private val getUserWalletUseCase: GetUserWalletUseCase, private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase, + private val swapAlertFactory: SwapAlertFactory, paramsContainer: ParamsContainer, ) : Model(), SwapAmountComponent.ModelCallback, @@ -133,7 +136,19 @@ internal class SendWithSwapModel @Inject constructor( }, ifLeft = { Timber.w(it.toString()) - // todo send with swap error + swapAlertFactory.getGenericErrorState( + expressError = ExpressError.UnknownError, + onFailedTxEmailClick = { + modelScope.launch { + swapAlertFactory.onFailedTxEmailClick( + userWallet = userWallet, + cryptoCurrency = params.currency, + errorMessage = it.toString(), + ) + } + }, + popBack = ::onBackClick, + ) }, ) } @@ -173,8 +188,20 @@ internal class SendWithSwapModel @Inject constructor( cryptoCurrencyStatus = cryptoCurrencyStatus, ).getOrNull() ?: cryptoCurrencyStatus }, - ifLeft = { - // todo send with swap error + ifLeft = { error -> + swapAlertFactory.getGenericErrorState( + expressError = ExpressError.UnknownError, + onFailedTxEmailClick = { + modelScope.launch { + swapAlertFactory.onFailedTxEmailClick( + userWallet = userWallet, + cryptoCurrency = params.currency, + errorMessage = error.toString(), + ) + } + }, + popBack = ::onBackClick, + ) }, ) }.launchIn(modelScope)