From 039fbb5868a9f0c34a418f06ce44f7b4ff66e402 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 28 Mar 2025 18:17:56 +0500 Subject: [PATCH] Updated on 2026-08-14 --- .../ui/notifications/NotificationsFactory.kt | 62 +++ .../notifications/NotificationsComponent.kt | 55 +++ .../NotificationsUpdateTrigger.kt | 40 ++ .../analytics/NotificationsAnalyticEvents.kt | 32 ++ .../notifications/di/NotificationsModule.kt | 20 + .../notifications/model/NotificationData.kt | 14 + .../notifications/model/NotificationsModel.kt | 366 ++++++++++++++++++ .../notifications/ui/NotificationsContent.kt | 48 +++ .../state/confirm/SendNotificationFactory.kt | 7 +- .../tangem/blockchainsdk/utils/Blockchain.kt | 4 - .../com/tangem/lib/crypto/BlockchainUtils.kt | 3 +- 11 files changed, 641 insertions(+), 10 deletions(-) create mode 100644 features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/notifications/NotificationsComponent.kt create mode 100644 features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/notifications/NotificationsUpdateTrigger.kt create mode 100644 features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/notifications/analytics/NotificationsAnalyticEvents.kt create mode 100644 features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/notifications/di/NotificationsModule.kt create mode 100644 features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/notifications/model/NotificationData.kt create mode 100644 features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/notifications/model/NotificationsModel.kt create mode 100644 features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/notifications/ui/NotificationsContent.kt diff --git a/common/ui/src/main/java/com/tangem/common/ui/notifications/NotificationsFactory.kt b/common/ui/src/main/java/com/tangem/common/ui/notifications/NotificationsFactory.kt index b5e98fa800..55ee3a6906 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/notifications/NotificationsFactory.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/notifications/NotificationsFactory.kt @@ -16,6 +16,8 @@ import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.model.warnings.CryptoCurrencyCheck import com.tangem.domain.tokens.model.warnings.CryptoCurrencyWarning import com.tangem.domain.transaction.error.GetFeeError +import com.tangem.lib.crypto.BlockchainUtils.getTezosThreshold +import com.tangem.lib.crypto.BlockchainUtils.isTezos import com.tangem.utils.extensions.isZero import com.tangem.utils.extensions.orZero import java.math.BigDecimal @@ -240,6 +242,31 @@ object NotificationsFactory { } } + fun MutableList.addFeeCoverageNotification( + isFeeCoverage: Boolean, + enteredAmountValue: BigDecimal, + sendingValue: BigDecimal, + appCurrency: AppCurrency, + cryptoCurrencyStatus: CryptoCurrencyStatus, + ) { + val cryptoCurrency = cryptoCurrencyStatus.currency + val fiatRate = cryptoCurrencyStatus.value.fiatRate + + val cryptoDiff = enteredAmountValue.minus(sendingValue) + if (isFeeCoverage) { + add( + NotificationUM.Warning.FeeCoverageNotification( + cryptoAmount = cryptoDiff.format { crypto(cryptoCurrency).uncapped() }, + fiatAmount = getFiatString( + value = cryptoDiff, + rate = fiatRate, + appCurrency = appCurrency, + ), + ), + ) + } + } + fun MutableList.addDustWarningNotification( dustValue: BigDecimal?, feeValue: BigDecimal, @@ -425,6 +452,41 @@ object NotificationsFactory { add(NotificationUM.Solana.RentInfo(rentWarning)) } + fun MutableList.addHighFeeWarningNotification( + enteredAmountValue: BigDecimal, + cryptoCurrencyStatus: CryptoCurrencyStatus, + ignoreAmountReduce: Boolean, + onReduceClick: ( + reduceAmountBy: BigDecimal, + reduceAmountByDiff: BigDecimal, + notification: Class, + ) -> Unit, + onCloseClick: (Class) -> Unit, + ) { + val balance = cryptoCurrencyStatus.value.amount ?: BigDecimal.ZERO + val isTezos = isTezos(cryptoCurrencyStatus.currency.network.id.value) + val threshold = getTezosThreshold() + val isTotalBalance = enteredAmountValue >= balance && balance > threshold + if (!ignoreAmountReduce && isTotalBalance && isTezos) { + add( + NotificationUM.Warning.HighFeeError( + currencyName = cryptoCurrencyStatus.currency.name, + amount = threshold.toPlainString(), + onConfirmClick = { + onReduceClick( + threshold, + threshold, + NotificationUM.Warning.HighFeeError::class.java, + ) + }, + onCloseClick = { + onCloseClick(NotificationUM.Warning.HighFeeError::class.java) + }, + ), + ) + } + } + private fun checkDustLimits( feeAmount: BigDecimal, sendingAmount: BigDecimal, diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/notifications/NotificationsComponent.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/notifications/NotificationsComponent.kt new file mode 100644 index 0000000000..a7a941c816 --- /dev/null +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/notifications/NotificationsComponent.kt @@ -0,0 +1,55 @@ +package com.tangem.features.send.v2.subcomponents.notifications + +import androidx.compose.foundation.lazy.LazyListScope +import androidx.compose.ui.Modifier +import com.tangem.blockchain.common.transaction.Fee +import com.tangem.common.ui.notifications.NotificationUM +import com.tangem.core.decompose.context.AppComponentContext +import com.tangem.core.decompose.model.getOrCreateModel +import com.tangem.domain.appcurrency.model.AppCurrency +import com.tangem.domain.tokens.model.CryptoCurrencyStatus +import com.tangem.domain.transaction.error.GetFeeError +import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.features.send.v2.subcomponents.notifications +import com.tangem.features.send.v2.subcomponents.notifications.model.NotificationsModel +import kotlinx.collections.immutable.ImmutableList +import kotlinx.coroutines.flow.StateFlow +import java.math.BigDecimal + +internal class NotificationsComponent( + appComponentContext: AppComponentContext, + params: Params, +) : AppComponentContext by appComponentContext { + + private val model: NotificationsModel = getOrCreateModel(params) + + val state: StateFlow> = model.uiState + + fun LazyListScope.content( + state: ImmutableList, + modifier: Modifier = Modifier, + hasPaddingAbove: Boolean = false, + isClickDisabled: Boolean = false, + ) { + notifications( + notifications = state, + modifier = modifier, + hasPaddingAbove = hasPaddingAbove, + isClickDisabled = isClickDisabled, + ) + } + + data class Params( + val analyticsCategoryName: String, + val userWalletId: UserWalletId, + val cryptoCurrencyStatus: CryptoCurrencyStatus, + val feeCryptoCurrencyStatus: CryptoCurrencyStatus, + val appCurrency: AppCurrency, + val destinationAddress: String, + val amountValue: BigDecimal, + val reduceAmountBy: BigDecimal, + val isIgnoreReduce: Boolean, + val fee: Fee?, + val feeError: GetFeeError?, + ) +} \ No newline at end of file diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/notifications/NotificationsUpdateTrigger.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/notifications/NotificationsUpdateTrigger.kt new file mode 100644 index 0000000000..bdcf4b4076 --- /dev/null +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/notifications/NotificationsUpdateTrigger.kt @@ -0,0 +1,40 @@ +package com.tangem.features.send.v2.subcomponents.notifications + +import com.tangem.features.send.v2.subcomponents.notifications.model.NotificationData +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.asSharedFlow +import javax.inject.Inject +import javax.inject.Singleton + +interface NotificationsUpdateTrigger { + /** Flow triggers notifications update */ + val updateTriggerFlow: Flow + + /** Flow returns whether there is error notifications */ + val hasErrorFlow: Flow + + /** Trigger return callback with check result */ + suspend fun callbackHasError(hasError: Boolean) + + /** Trigger fee check reload */ + suspend fun triggerUpdate(data: NotificationData) +} + +@Singleton +internal class DefaultNotificationsUpdateTrigger @Inject constructor() : NotificationsUpdateTrigger { + + private val _updateTriggerFlow = MutableSharedFlow() + override val updateTriggerFlow = _updateTriggerFlow.asSharedFlow() + + private val _hasErrorFlow = MutableSharedFlow() + override val hasErrorFlow = _hasErrorFlow.asSharedFlow() + + override suspend fun callbackHasError(hasError: Boolean) { + _hasErrorFlow.emit(hasError) + } + + override suspend fun triggerUpdate(data: NotificationData) { + _updateTriggerFlow.emit(data) + } +} \ No newline at end of file diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/notifications/analytics/NotificationsAnalyticEvents.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/notifications/analytics/NotificationsAnalyticEvents.kt new file mode 100644 index 0000000000..eeaeb2e51d --- /dev/null +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/notifications/analytics/NotificationsAnalyticEvents.kt @@ -0,0 +1,32 @@ +package com.tangem.features.send.v2.subcomponents.notifications.analytics + +import com.tangem.core.analytics.models.AnalyticsEvent +import com.tangem.core.analytics.models.AnalyticsParam.Key.BLOCKCHAIN +import com.tangem.core.analytics.models.AnalyticsParam.Key.TOKEN_PARAM + +internal sealed class NotificationsAnalyticEvents( + category: String, + event: String, + params: Map = mapOf(), +) : AnalyticsEvent(category = category, event = event, params = params) { + + abstract val categoryName: String + + /** If not enough fee notification is present */ + data class NoticeNotEnoughFee( + override val categoryName: String, + val token: String, + val blockchain: String, + ) : NotificationsAnalyticEvents( + category = categoryName, + event = "Notice - Not Enough Fee", + params = mapOf(TOKEN_PARAM to token, BLOCKCHAIN to blockchain), + ) + + data class NoticeFeeCoverage( + override val categoryName: String, + ) : NotificationsAnalyticEvents( + category = categoryName, + event = "Notice - Network Fee Coverage", + ) +} \ No newline at end of file diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/notifications/di/NotificationsModule.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/notifications/di/NotificationsModule.kt new file mode 100644 index 0000000000..2c52cdeab5 --- /dev/null +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/notifications/di/NotificationsModule.kt @@ -0,0 +1,20 @@ +package com.tangem.features.send.v2.subcomponents.notifications.di + +import com.tangem.features.send.v2.subcomponents.notifications.DefaultNotificationsUpdateTrigger +import com.tangem.features.send.v2.subcomponents.notifications.NotificationsUpdateTrigger +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@InstallIn(SingletonComponent::class) +@Module +internal object NotificationsModule { + + @Provides + @Singleton + fun providesNotificationsUpdateTrigger(): NotificationsUpdateTrigger { + return DefaultNotificationsUpdateTrigger() + } +} \ No newline at end of file diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/notifications/model/NotificationData.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/notifications/model/NotificationData.kt new file mode 100644 index 0000000000..adf6dc6e5f --- /dev/null +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/notifications/model/NotificationData.kt @@ -0,0 +1,14 @@ +package com.tangem.features.send.v2.subcomponents.notifications.model + +import com.tangem.blockchain.common.transaction.Fee +import com.tangem.domain.transaction.error.GetFeeError +import java.math.BigDecimal + +data class NotificationData( + val destinationAddress: String, + val amountValue: BigDecimal, + val reduceAmountBy: BigDecimal, + val isIgnoreReduce: Boolean, + val fee: Fee?, + val feeError: GetFeeError?, +) \ No newline at end of file diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/notifications/model/NotificationsModel.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/notifications/model/NotificationsModel.kt new file mode 100644 index 0000000000..84e193f53d --- /dev/null +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/notifications/model/NotificationsModel.kt @@ -0,0 +1,366 @@ +package com.tangem.features.send.v2.subcomponents.notifications.model + +import androidx.compose.runtime.Stable +import arrow.core.getOrElse +import com.tangem.blockchain.common.transaction.Fee +import com.tangem.common.routing.AppRoute +import com.tangem.common.routing.AppRouter +import com.tangem.common.ui.amountScreen.converters.AmountReduceByTransformer.ReduceByData +import com.tangem.common.ui.notifications.NotificationUM +import com.tangem.common.ui.notifications.NotificationsFactory.addDustWarningNotification +import com.tangem.common.ui.notifications.NotificationsFactory.addExceedBalanceNotification +import com.tangem.common.ui.notifications.NotificationsFactory.addExceedsBalanceNotification +import com.tangem.common.ui.notifications.NotificationsFactory.addExistentialWarningNotification +import com.tangem.common.ui.notifications.NotificationsFactory.addFeeCoverageNotification +import com.tangem.common.ui.notifications.NotificationsFactory.addFeeUnreachableNotification +import com.tangem.common.ui.notifications.NotificationsFactory.addHighFeeWarningNotification +import com.tangem.common.ui.notifications.NotificationsFactory.addMinimumAmountErrorNotification +import com.tangem.common.ui.notifications.NotificationsFactory.addRentExemptionNotification +import com.tangem.common.ui.notifications.NotificationsFactory.addReserveAmountErrorNotification +import com.tangem.common.ui.notifications.NotificationsFactory.addTransactionLimitErrorNotification +import com.tangem.common.ui.notifications.NotificationsFactory.addValidateTransactionNotifications +import com.tangem.core.analytics.api.AnalyticsEventHandler +import com.tangem.core.decompose.di.ModelScoped +import com.tangem.core.decompose.model.Model +import com.tangem.core.decompose.model.ParamsContainer +import com.tangem.domain.tokens.GetBalanceNotEnoughForFeeWarningUseCase +import com.tangem.domain.tokens.GetCurrencyCheckUseCase +import com.tangem.domain.tokens.IsAmountSubtractAvailableUseCase +import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.domain.tokens.model.CryptoCurrencyStatus +import com.tangem.domain.tokens.model.warnings.CryptoCurrencyCheck +import com.tangem.domain.transaction.usecase.ValidateTransactionUseCase +import com.tangem.domain.utils.convertToSdkAmount +import com.tangem.features.send.v2.subcomponents.amount.SendAmountReduceTrigger +import com.tangem.features.send.v2.subcomponents.fee.SendFeeReloadTrigger +import com.tangem.features.send.v2.subcomponents.fee.model.checkAndCalculateSubtractedAmount +import com.tangem.features.send.v2.subcomponents.fee.model.checkFeeCoverage +import com.tangem.features.send.v2.subcomponents.notifications.NotificationsComponent +import com.tangem.features.send.v2.subcomponents.notifications.NotificationsUpdateTrigger +import com.tangem.features.send.v2.subcomponents.notifications.analytics.NotificationsAnalyticEvents +import com.tangem.lib.crypto.BlockchainUtils +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import com.tangem.utils.extensions.orZero +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.toImmutableList +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch +import java.math.BigDecimal +import javax.inject.Inject + +@Suppress("LongParameterList") +@Stable +@ModelScoped +class NotificationsModel @Inject constructor( + paramsContainer: ParamsContainer, + override val dispatchers: CoroutineDispatcherProvider, + private val appRouter: AppRouter, + private val isAmountSubtractAvailableUseCase: IsAmountSubtractAvailableUseCase, + private val getCurrencyCheckUseCase: GetCurrencyCheckUseCase, + private val getBalanceNotEnoughForFeeWarningUseCase: GetBalanceNotEnoughForFeeWarningUseCase, + private val validateTransactionUseCase: ValidateTransactionUseCase, + private val sendFeeReloadTrigger: SendFeeReloadTrigger, + private val sendAmountReduceTrigger: SendAmountReduceTrigger, + private val notificationsUpdateTrigger: NotificationsUpdateTrigger, + private val analyticsEventHandler: AnalyticsEventHandler, +) : Model() { + + private val params: NotificationsComponent.Params = paramsContainer.require() + + private val analyticsCategoryName = params.analyticsCategoryName + private val userWalletId = params.userWalletId + private val cryptoCurrencyStatus = params.cryptoCurrencyStatus + private val feeCryptoCurrencyStatus = params.feeCryptoCurrencyStatus + private val currency = cryptoCurrencyStatus.currency + private val appCurrency = params.appCurrency + + private var destinationAddress = params.destinationAddress + private var amountValue = params.amountValue + private var reduceAmountBy = params.reduceAmountBy + private var isIgnoreReduce = params.isIgnoreReduce + private var fee = params.fee + private var feeError = params.feeError + + private val _uiState = MutableStateFlow>(persistentListOf()) + val uiState = _uiState.asStateFlow() + + private var isAmountSubtractAvailable = false + + init { + subscribeToNotificationUpdateTrigger() + checkIfSubtractAvailable() + } + + private fun subscribeToNotificationUpdateTrigger() { + notificationsUpdateTrigger.updateTriggerFlow + .onEach { updateState(it) } + .launchIn(modelScope) + } + + private fun checkIfSubtractAvailable() { + modelScope.launch { + isAmountSubtractAvailable = isAmountSubtractAvailableUseCase(userWalletId, currency).getOrElse { false } + buildNotifications() + } + } + + private suspend fun updateState(data: NotificationData) { + destinationAddress = data.destinationAddress + amountValue = data.amountValue + reduceAmountBy = data.reduceAmountBy + isIgnoreReduce = data.isIgnoreReduce + fee = data.fee + feeError = data.feeError + + buildNotifications() + } + + private suspend fun buildNotifications() { + val notifications = buildList { + addFeeUnreachableNotification( + tokenStatus = cryptoCurrencyStatus, + coinStatus = feeCryptoCurrencyStatus, + feeError = feeError, + onReload = { + modelScope.launch { + sendFeeReloadTrigger.triggerUpdate() + } + }, + onClick = ::showTokenDetails, + ) + addDomainNotifications( + destinationAddress = destinationAddress, + amountValue = amountValue, + reduceAmountBy = reduceAmountBy, + fee = fee, + ) + } + + notificationsUpdateTrigger.callbackHasError(notifications.any { it is NotificationUM.Error }) + + _uiState.value = notifications.toImmutableList() + } + + private fun showTokenDetails(currency: CryptoCurrency) { + appRouter.pop { isSuccess -> + if (isSuccess) { + appRouter.push( + AppRoute.CurrencyDetails( + userWalletId = userWalletId, + currency = currency, + ), + ) + } + } + } + + private suspend fun MutableList.addDomainNotifications( + destinationAddress: String, + amountValue: BigDecimal, + reduceAmountBy: BigDecimal, + fee: Fee?, + ) { + val balance = cryptoCurrencyStatus.value.amount ?: return + val feeValue = fee?.amount?.value ?: return + val isFeeCoverage = checkFeeCoverage( + isSubtractAvailable = isAmountSubtractAvailable, + balance = balance, + amountValue = amountValue, + feeValue = feeValue, + reduceAmountBy = reduceAmountBy, + ) + val sendingAmount = checkAndCalculateSubtractedAmount( + isAmountSubtractAvailable = isAmountSubtractAvailable, + cryptoCurrencyStatus = cryptoCurrencyStatus, + amountValue = amountValue, + feeValue = feeValue, + reduceAmountBy = reduceAmountBy, + ) + val feeCurrencyBalanceAfterTransaction = getFeeCurrencyBalanceAfterTx( + sendingAmount = sendingAmount, + feeValue = feeValue, + ) + val currencyCheck = getCurrencyCheckUseCase( + userWalletId = userWalletId, + currencyStatus = cryptoCurrencyStatus, + amount = sendingAmount, + fee = feeValue, + recipientAddress = destinationAddress, + feeCurrencyBalanceAfterTransaction = feeCurrencyBalanceAfterTransaction, + ) + + addErrorNotifications( + sendingAmount = sendingAmount, + feeValue = feeValue, + currencyCheck = currencyCheck, + ) + addWarningNotifications( + enteredAmount = amountValue, + fee = fee, + feeValue = feeValue, + sendingAmount = sendingAmount, + isFeeCoverage = isFeeCoverage, + currencyCheck = currencyCheck, + ) + } + + private fun getFeeCurrencyBalanceAfterTx(sendingAmount: BigDecimal, feeValue: BigDecimal): BigDecimal? { + val sendingCurrencyBalance = cryptoCurrencyStatus.value as? CryptoCurrencyStatus.Loaded + val feeCurrencyBalance = feeCryptoCurrencyStatus.value as? CryptoCurrencyStatus.Loaded + if (feeCryptoCurrencyStatus.value !is CryptoCurrencyStatus.Loaded) return null + return when { + feeCryptoCurrencyStatus == cryptoCurrencyStatus -> sendingCurrencyBalance?.let { + it.amount - sendingAmount - feeValue + } + else -> feeCurrencyBalance?.let { it.amount - feeValue } + } + } + + private suspend fun MutableList.addErrorNotifications( + sendingAmount: BigDecimal, + feeValue: BigDecimal, + currencyCheck: CryptoCurrencyCheck, + ) { + val currencyWarning = getBalanceNotEnoughForFeeWarningUseCase( + fee = feeValue, + userWalletId = userWalletId, + tokenStatus = cryptoCurrencyStatus, + coinStatus = feeCryptoCurrencyStatus, + ).getOrNull() + + addExceedBalanceNotification( + feeAmount = feeValue, + sendingAmount = sendingAmount, + isSubtractionAvailable = isAmountSubtractAvailable, + cryptoCurrencyStatus = cryptoCurrencyStatus, + ) + addExceedsBalanceNotification( + cryptoCurrencyWarning = currencyWarning, + cryptoCurrencyStatus = cryptoCurrencyStatus, + shouldMergeFeeNetworkName = BlockchainUtils.isArbitrum(currency.network.backendId), + onClick = ::showTokenDetails, + onAnalyticsEvent = { + analyticsEventHandler.send( + NotificationsAnalyticEvents.NoticeNotEnoughFee( + categoryName = analyticsCategoryName, + token = cryptoCurrencyStatus.currency.symbol, + blockchain = cryptoCurrencyStatus.currency.network.name, + ), + ) + }, + ) + if (!BlockchainUtils.isCardano(currency.network.id.value)) { + addDustWarningNotification( + dustValue = currencyCheck.dustValue, + feeValue = feeValue, + sendingAmount = sendingAmount, + cryptoCurrencyStatus = cryptoCurrencyStatus, + feeCurrencyStatus = feeCryptoCurrencyStatus, + ) + } + addTransactionLimitErrorNotification( + currencyCheck = currencyCheck, + sendingAmount = sendingAmount, + cryptoCurrencyStatus = cryptoCurrencyStatus, + feeCurrencyStatus = feeCryptoCurrencyStatus, + feeValue = feeValue, + onReduceClick = { reduceTo, _ -> + modelScope.launch { + sendAmountReduceTrigger.triggerReduceTo(reduceTo) + } + }, + ) + addReserveAmountErrorNotification( + reserveAmount = currencyCheck.reserveAmount, + sendingAmount = sendingAmount, + cryptoCurrency = currency, + isAccountFunded = currencyCheck.isAccountFunded, + ) + addMinimumAmountErrorNotification( + minimumSendAmount = currencyCheck.minimumSendAmount, + sendingAmount = sendingAmount, + cryptoCurrency = currency, + ) + } + + private suspend fun MutableList.addWarningNotifications( + enteredAmount: BigDecimal, + sendingAmount: BigDecimal, + fee: Fee?, + feeValue: BigDecimal, + isFeeCoverage: Boolean, + currencyCheck: CryptoCurrencyCheck, + ) { + val validationError = validateTransactionUseCase( + userWalletId = userWalletId, + amount = enteredAmount.convertToSdkAmount(currency), + fee = fee, + memo = null, + destination = "", + network = currency.network, + ).leftOrNull() + + addRentExemptionNotification( + rentWarning = currencyCheck.rentWarning, + ) + + addExistentialWarningNotification( + existentialDeposit = currencyCheck.existentialDeposit, + feeAmount = feeValue, + sendingAmount = sendingAmount, + cryptoCurrencyStatus = cryptoCurrencyStatus, + onReduceClick = { reduceBy, reduceByDiff, _ -> + modelScope.launch { + sendAmountReduceTrigger.triggerReduceBy( + ReduceByData( + reduceAmountBy = reduceBy, + reduceAmountByDiff = reduceByDiff, + ), + ) + } + }, + ) + addFeeCoverageNotification( + isFeeCoverage = isFeeCoverage, + enteredAmountValue = enteredAmount, + sendingValue = sendingAmount, + appCurrency = appCurrency, + cryptoCurrencyStatus = cryptoCurrencyStatus, + ) + addValidateTransactionNotifications( + dustValue = currencyCheck.dustValue.orZero(), + minAdaValue = (fee as? Fee.CardanoToken)?.minAdaValue, + validationError = validationError, + cryptoCurrency = currency, + onReduceClick = { reduceTo, _ -> + modelScope.launch { + sendAmountReduceTrigger.triggerReduceTo(reduceTo) + } + }, + ) + addHighFeeWarningNotification( + enteredAmountValue = enteredAmount, + cryptoCurrencyStatus = cryptoCurrencyStatus, + ignoreAmountReduce = isIgnoreReduce, + onReduceClick = { reduceBy, reduceByDiff, _ -> + modelScope.launch { + sendAmountReduceTrigger.triggerReduceBy( + ReduceByData( + reduceAmountBy = reduceBy, + reduceAmountByDiff = reduceByDiff, + ), + ) + } + }, + onCloseClick = { + modelScope.launch { + buildNotifications() + } + }, + ) + } +} \ No newline at end of file diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/notifications/ui/NotificationsContent.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/notifications/ui/NotificationsContent.kt new file mode 100644 index 0000000000..aa9dd0d609 --- /dev/null +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/notifications/ui/NotificationsContent.kt @@ -0,0 +1,48 @@ +package com.tangem.features.send.v2.subcomponents.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 kotlinx.collections.immutable.ImmutableList + +internal fun LazyListScope.notifications( + 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 NotificationUM.Error.TokenExceedsBalance, + is NotificationUM.Warning.NetworkFeeUnreachable, + is NotificationUM.Warning.HighFeeError, + -> TangemTheme.colors.background.action + else -> TangemTheme.colors.button.disabled + }, + iconTint = when (item) { + 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/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt index 6254300fa0..6b36be9240 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt @@ -1,9 +1,7 @@ package com.tangem.features.send.impl.presentation.state.confirm -import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.transaction.Fee import com.tangem.blockchain.common.transaction.TransactionFee -import com.tangem.blockchainsdk.utils.minimalAmount import com.tangem.common.ui.amountScreen.models.AmountState import com.tangem.common.ui.notifications.NotificationUM import com.tangem.common.ui.notifications.NotificationsFactory.addDustWarningNotification @@ -29,13 +27,14 @@ import com.tangem.domain.transaction.usecase.ValidateTransactionUseCase import com.tangem.domain.utils.convertToSdkAmount import com.tangem.domain.wallets.models.UserWalletId import com.tangem.features.send.impl.presentation.analytics.SendAnalyticEvents +import com.tangem.features.send.impl.presentation.model.SendClickIntents import com.tangem.features.send.impl.presentation.state.SendStates import com.tangem.features.send.impl.presentation.state.SendUiState import com.tangem.features.send.impl.presentation.state.SendUiStateType import com.tangem.features.send.impl.presentation.state.StateRouter import com.tangem.features.send.impl.presentation.state.fee.* -import com.tangem.features.send.impl.presentation.model.SendClickIntents import com.tangem.lib.crypto.BlockchainUtils +import com.tangem.lib.crypto.BlockchainUtils.getTezosThreshold import com.tangem.lib.crypto.BlockchainUtils.isTezos import com.tangem.utils.Provider import com.tangem.utils.extensions.orZero @@ -297,7 +296,7 @@ internal class SendNotificationFactory( val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() val balance = cryptoCurrencyStatus.value.amount ?: BigDecimal.ZERO val isTezos = isTezos(cryptoCurrencyStatus.currency.network.id.value) - val threshold = Blockchain.Tezos.minimalAmount() + val threshold = getTezosThreshold() val isTotalBalance = sendAmount >= balance && balance > threshold if (!ignoreAmountReduce && isTotalBalance && isTezos) { add( diff --git a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/utils/Blockchain.kt b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/utils/Blockchain.kt index dc2f9b8a81..094ed851ec 100644 --- a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/utils/Blockchain.kt +++ b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/utils/Blockchain.kt @@ -463,10 +463,6 @@ fun Blockchain.amountToCreateAccount(walletManager: WalletManager, token: Token? } } -fun Blockchain.minimalAmount(): BigDecimal { - return BigDecimal.ONE.movePointLeft(decimals()) -} - const val OLD_POLYGON_NAME = "matic-network" const val NEW_POLYGON_NAME = "polygon-ecosystem-token" diff --git a/libs/crypto/src/main/java/com/tangem/lib/crypto/BlockchainUtils.kt b/libs/crypto/src/main/java/com/tangem/lib/crypto/BlockchainUtils.kt index 3b8db3bccf..3decd72093 100644 --- a/libs/crypto/src/main/java/com/tangem/lib/crypto/BlockchainUtils.kt +++ b/libs/crypto/src/main/java/com/tangem/lib/crypto/BlockchainUtils.kt @@ -5,7 +5,6 @@ import com.tangem.blockchain.common.Blockchain import com.tangem.blockchainsdk.compatibility.l2BlockchainsList import com.tangem.blockchainsdk.utils.ExcludedBlockchains import com.tangem.blockchainsdk.utils.fromNetworkId -import com.tangem.blockchainsdk.utils.minimalAmount import com.tangem.lib.crypto.converter.XrpTaggedAddressConverter import com.tangem.lib.crypto.models.XrpTaggedAddress import java.math.BigDecimal @@ -125,7 +124,7 @@ object BlockchainUtils { return l2BlockchainsList.contains(blockchain) } - fun getTezosThreshold(): BigDecimal = Blockchain.Tezos.minimalAmount() + fun getTezosThreshold(): BigDecimal = BigDecimal.ONE.movePointLeft(Blockchain.Tezos.decimals()) /** * Blockchains not affecting total balance counting on errors