Updated on 2026-08-14

This commit is contained in:
Tangem 2026-01-27 15:55:18 +03:00
parent c77fcafdc2
commit b4b35916d9
7 changed files with 55 additions and 12 deletions

View file

@ -314,12 +314,14 @@ object NotificationsFactory {
} }
} }
@Suppress("LongParameterList")
fun MutableList<NotificationUM>.addExceedsBalanceNotification( fun MutableList<NotificationUM>.addExceedsBalanceNotification(
cryptoCurrencyWarning: CryptoCurrencyWarning?, cryptoCurrencyWarning: CryptoCurrencyWarning?,
cryptoCurrencyStatus: CryptoCurrencyStatus, cryptoCurrencyStatus: CryptoCurrencyStatus,
shouldMergeFeeNetworkName: Boolean, shouldMergeFeeNetworkName: Boolean,
onClick: (CryptoCurrency) -> Unit, onClick: (CryptoCurrency) -> Unit,
onAnalyticsEvent: (CryptoCurrency) -> Unit, onAnalyticsEvent: (CryptoCurrency) -> Unit,
onResetAnalyticsEvent: (CryptoCurrency) -> Unit,
) { ) {
when (cryptoCurrencyWarning) { when (cryptoCurrencyWarning) {
is CryptoCurrencyWarning.BalanceNotEnoughForFee -> { is CryptoCurrencyWarning.BalanceNotEnoughForFee -> {
@ -357,7 +359,7 @@ object NotificationsFactory {
) )
onAnalyticsEvent(cryptoCurrencyWarning.currency) onAnalyticsEvent(cryptoCurrencyWarning.currency)
} }
else -> Unit else -> onResetAnalyticsEvent(cryptoCurrencyStatus.currency)
} }
} }

View file

@ -0,0 +1,19 @@
package com.tangem.core.analytics.api
import com.tangem.core.analytics.models.AnalyticsEvent
import java.util.concurrent.ConcurrentHashMap
class ResettableOneTimeEventSender(val analyticsEventHandler: AnalyticsEventHandler) {
private val sentEvents = ConcurrentHashMap<String, AnalyticsEvent>()
fun sendEventOnce(key: String, event: AnalyticsEvent) {
if (sentEvents[key] != null) return
analyticsEventHandler.send(event)
sentEvents[key] = event
}
fun reset(key: String) {
sentEvents.remove(key)
}
}

View file

@ -40,7 +40,7 @@ class GetBalanceNotEnoughForFeeWarningUseCase(
val feePaidCurrency = currenciesRepository.getFeePaidCurrency(userWalletId, tokenStatus.currency.network) val feePaidCurrency = currenciesRepository.getFeePaidCurrency(userWalletId, tokenStatus.currency.network)
val feeTokenBalance = feeStatus.value.amount ?: BigDecimal.ZERO val feeTokenBalance = feeStatus.value.amount ?: BigDecimal.ZERO
val isFeePaidByCoin = tokenStatus.currency is CryptoCurrency.Token val isSendingCurrencyToken = tokenStatus.currency is CryptoCurrency.Token
val isFeePaidByToken = val isFeePaidByToken =
feePaidCurrency is FeePaidCurrency.Token && tokenStatus.currency.id != feePaidCurrency.tokenId feePaidCurrency is FeePaidCurrency.Token && tokenStatus.currency.id != feePaidCurrency.tokenId
@ -49,10 +49,15 @@ class GetBalanceNotEnoughForFeeWarningUseCase(
feeStatus.currency is CryptoCurrency.Token feeStatus.currency is CryptoCurrency.Token
val warning = when { val warning = when {
isFeePaidByGaslessToken && feeTokenBalance == BigDecimal.ZERO -> {
CryptoCurrencyWarning.BalanceNotEnoughForFee(
tokenCurrency = tokenStatus.currency,
coinCurrency = feeStatus.currency,
)
}
feePaidCurrency is FeePaidCurrency.Coin && feePaidCurrency is FeePaidCurrency.Coin &&
isFeePaidByCoin && isSendingCurrencyToken &&
fee > feeTokenBalance && fee > feeTokenBalance -> {
!isFeePaidByGaslessToken -> {
CryptoCurrencyWarning.BalanceNotEnoughForFee( CryptoCurrencyWarning.BalanceNotEnoughForFee(
tokenCurrency = tokenStatus.currency, tokenCurrency = tokenStatus.currency,
coinCurrency = feeStatus.currency, coinCurrency = feeStatus.currency,

View file

@ -2,6 +2,7 @@ package com.tangem.features.send.v2.api.subcomponents.feeSelector.analytics
import com.tangem.core.analytics.models.AnalyticsEvent import com.tangem.core.analytics.models.AnalyticsEvent
import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.analytics.models.AnalyticsParam.Key.BLOCKCHAIN
import com.tangem.core.analytics.models.AnalyticsParam.Key.FEE_TOKEN import com.tangem.core.analytics.models.AnalyticsParam.Key.FEE_TOKEN
import com.tangem.core.analytics.models.AnalyticsParam.Key.FEE_TYPE import com.tangem.core.analytics.models.AnalyticsParam.Key.FEE_TYPE
import com.tangem.core.analytics.models.AnalyticsParam.Key.SOURCE import com.tangem.core.analytics.models.AnalyticsParam.Key.SOURCE
@ -21,6 +22,7 @@ sealed class CommonSendFeeAnalyticEvents(
val feeType: AnalyticsParam.FeeType, val feeType: AnalyticsParam.FeeType,
val source: CommonSendSource, val source: CommonSendSource,
val feeToken: String, val feeToken: String,
val blockchain: String,
) : CommonSendFeeAnalyticEvents( ) : CommonSendFeeAnalyticEvents(
category = categoryName, category = categoryName,
event = "Fee Selected", event = "Fee Selected",
@ -28,15 +30,20 @@ sealed class CommonSendFeeAnalyticEvents(
FEE_TYPE to feeType.value, FEE_TYPE to feeType.value,
SOURCE to source.analyticsName, SOURCE to source.analyticsName,
FEE_TOKEN to feeToken, FEE_TOKEN to feeToken,
BLOCKCHAIN to blockchain,
), ),
) )
/** Custom fee selected */ /** Custom fee selected */
data class CustomFeeButtonClicked( data class CustomFeeButtonClicked(
override val categoryName: String, override val categoryName: String,
val blockchain: String,
) : CommonSendFeeAnalyticEvents( ) : CommonSendFeeAnalyticEvents(
category = categoryName, category = categoryName,
event = "Custom Fee Clicked", event = "Custom Fee Clicked",
params = mapOf(
BLOCKCHAIN to blockchain,
),
) )
/** Custom fee edited */ /** Custom fee edited */

View file

@ -20,6 +20,7 @@ import com.tangem.common.ui.notifications.NotificationsFactory.addReserveAmountE
import com.tangem.common.ui.notifications.NotificationsFactory.addTransactionLimitErrorNotification import com.tangem.common.ui.notifications.NotificationsFactory.addTransactionLimitErrorNotification
import com.tangem.common.ui.notifications.NotificationsFactory.addValidateTransactionNotifications import com.tangem.common.ui.notifications.NotificationsFactory.addValidateTransactionNotifications
import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.api.ResettableOneTimeEventSender
import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.decompose.model.ParamsContainer
@ -82,6 +83,7 @@ internal class NotificationsModel @Inject constructor(
private val feeCryptoCurrencyStatus = params.notificationData.feeCryptoCurrencyStatus private val feeCryptoCurrencyStatus = params.notificationData.feeCryptoCurrencyStatus
private val currency = cryptoCurrencyStatus.currency private val currency = cryptoCurrencyStatus.currency
private val appCurrency = params.appCurrency private val appCurrency = params.appCurrency
private val resettableOneTimeEventSender = ResettableOneTimeEventSender(analyticsEventHandler)
private var notificationData = params.notificationData private var notificationData = params.notificationData
@ -250,13 +252,18 @@ internal class NotificationsModel @Inject constructor(
shouldMergeFeeNetworkName = BlockchainUtils.isArbitrum(currency.network.backendId), shouldMergeFeeNetworkName = BlockchainUtils.isArbitrum(currency.network.backendId),
onClick = ::showTokenDetails, onClick = ::showTokenDetails,
onAnalyticsEvent = { onAnalyticsEvent = {
analyticsEventHandler.send( val event = NotificationsAnalyticEvents.NoticeNotEnoughFee(
NotificationsAnalyticEvents.NoticeNotEnoughFee( categoryName = analyticsCategoryName,
categoryName = analyticsCategoryName, token = cryptoCurrencyStatus.currency.symbol,
token = cryptoCurrencyStatus.currency.symbol, blockchain = cryptoCurrencyStatus.currency.network.name,
blockchain = cryptoCurrencyStatus.currency.network.name,
),
) )
resettableOneTimeEventSender.sendEventOnce(
key = EXCEEDS_BALANCE_EVENT,
event = event,
)
},
onResetAnalyticsEvent = {
resettableOneTimeEventSender.reset(EXCEEDS_BALANCE_EVENT)
}, },
) )
if (!BlockchainUtils.isCardano(currency.network.rawId)) { if (!BlockchainUtils.isCardano(currency.network.rawId)) {
@ -389,5 +396,6 @@ internal class NotificationsModel @Inject constructor(
companion object { companion object {
const val TRON_FEE_NOTIFICATION_MAX_SHOW_COUNT = 3 const val TRON_FEE_NOTIFICATION_MAX_SHOW_COUNT = 3
private const val EXCEEDS_BALANCE_EVENT = "EXCEED_BALANCE_EVENT"
} }
} }

View file

@ -235,6 +235,7 @@ internal class AddStakingNotificationsTransformer(
shouldMergeFeeNetworkName = BlockchainUtils.isArbitrum(network.backendId), shouldMergeFeeNetworkName = BlockchainUtils.isArbitrum(network.backendId),
onClick = prevState.clickIntents::openTokenDetails, onClick = prevState.clickIntents::openTokenDetails,
onAnalyticsEvent = { prevState.clickIntents.onNotEnoughFeeNotificationShow() }, onAnalyticsEvent = { prevState.clickIntents.onNotEnoughFeeNotificationShow() },
onResetAnalyticsEvent = { /*no-op*/ },
) )
if (!BlockchainUtils.isCardano(network.rawId)) { if (!BlockchainUtils.isCardano(network.rawId)) {
addDustWarningNotification( addDustWarningNotification(

View file

@ -64,7 +64,8 @@ internal class YieldSupplyNotificationsModel @Inject constructor(
blockchainId = cryptoCurrencyStatus.currency.network.backendId, blockchainId = cryptoCurrencyStatus.currency.network.backendId,
), ),
onClick = ::openTokenDetails, onClick = ::openTokenDetails,
onAnalyticsEvent = { }, onAnalyticsEvent = { /*no-op*/ },
onResetAnalyticsEvent = { /*no-op*/ },
) )
addFeeUnreachableNotification( addFeeUnreachableNotification(