Updated on 2026-08-14

This commit is contained in:
Tangem 2025-05-05 13:58:53 +02:00
parent 8a3d05eac7
commit b9ce4eeefc
14 changed files with 184 additions and 10 deletions

View file

@ -156,6 +156,7 @@ dependencies {
implementation(projects.data.networks)
implementation(projects.data.quotes)
implementation(projects.data.blockaid)
implementation(projects.data.notifications)
/** Features */
implementation(projects.features.referral.impl)

View file

@ -1,6 +1,8 @@
package com.tangem.tap.di.domain
import com.tangem.domain.notifications.GetApplicationIdUseCase
import com.tangem.domain.notifications.GetTronFeeNotificationShowCountUseCase
import com.tangem.domain.notifications.IncrementNotificationsShowCountUseCase
import com.tangem.domain.notifications.SendPushTokenUseCase
import com.tangem.domain.notifications.repository.NotificationsRepository
import com.tangem.utils.notifications.PushNotificationsTokenProvider
@ -16,10 +18,11 @@ internal object NotificationsDomainModule {
@Provides
@Singleton
fun providesGetApplicationIdUseCase(notificationsRepository: NotificationsRepository): GetApplicationIdUseCase =
GetApplicationIdUseCase(
fun providesGetApplicationIdUseCase(notificationsRepository: NotificationsRepository): GetApplicationIdUseCase {
return GetApplicationIdUseCase(
notificationsRepository = notificationsRepository,
)
}
@Provides
@Singleton
@ -27,9 +30,31 @@ internal object NotificationsDomainModule {
notificationsRepository: NotificationsRepository,
getApplicationIdUseCase: GetApplicationIdUseCase,
pushNotificationsTokenProvider: PushNotificationsTokenProvider,
): SendPushTokenUseCase = SendPushTokenUseCase(
notificationsRepository = notificationsRepository,
getApplicationIdUseCase = getApplicationIdUseCase,
pushNotificationsTokenProvider = pushNotificationsTokenProvider,
)
): SendPushTokenUseCase {
return SendPushTokenUseCase(
notificationsRepository = notificationsRepository,
getApplicationIdUseCase = getApplicationIdUseCase,
pushNotificationsTokenProvider = pushNotificationsTokenProvider,
)
}
@Provides
@Singleton
fun providesGetTronFeeNotificationShowCountUseCase(
notificationsRepository: NotificationsRepository,
): GetTronFeeNotificationShowCountUseCase {
return GetTronFeeNotificationShowCountUseCase(
notificationsRepository = notificationsRepository,
)
}
@Provides
@Singleton
fun providesIncrementTronFeeNotificationShowCountUseCase(
notificationsRepository: NotificationsRepository,
): IncrementNotificationsShowCountUseCase {
return IncrementNotificationsShowCountUseCase(
notificationsRepository = notificationsRepository,
)
}
}

View file

@ -4,6 +4,7 @@ import com.tangem.blockchain.common.BlockchainSdkError
import com.tangem.common.ui.R
import com.tangem.common.ui.amountScreen.models.AmountFieldModel
import com.tangem.common.ui.amountScreen.utils.getFiatString
import com.tangem.common.ui.notifications.NotificationUM.Warning
import com.tangem.core.ui.extensions.networkIconResId
import com.tangem.core.ui.format.bigdecimal.crypto
import com.tangem.core.ui.format.bigdecimal.format

View file

@ -127,6 +127,10 @@ object PreferencesKeys {
val NOTIFICATIONS_APPLICATION_ID_KEY by lazy { stringPreferencesKey(name = "notificationsApplicationId") }
val NOTIFICATIONS_ENABLED_STATES_KEY by lazy { stringPreferencesKey(name = "notificationsEnabledStates") }
val TRON_NETWORK_FEE_NOTIFICATION_SHOW_COUNT_KEY by lazy {
intPreferencesKey(name = "tronNetworkFeeNotificationShowCount")
}
// endregion
// region Promo

View file

@ -44,6 +44,23 @@ internal class DefaultNotificationsRepository @Inject constructor(
return appPreferencesStore.getSyncOrNull(PreferencesKeys.NOTIFICATIONS_APPLICATION_ID_KEY)
}
override suspend fun incrementTronTokenFeeNotificationShowCounter() {
appPreferencesStore.editData { preferences ->
val count = preferences.getOrDefault(
key = PreferencesKeys.TRON_NETWORK_FEE_NOTIFICATION_SHOW_COUNT_KEY,
default = 0,
)
preferences[PreferencesKeys.TRON_NETWORK_FEE_NOTIFICATION_SHOW_COUNT_KEY] = count + 1
}
}
override suspend fun getTronTokenFeeNotificationShowCounter(): Int {
return appPreferencesStore.getSyncOrDefault(
key = PreferencesKeys.TRON_NETWORK_FEE_NOTIFICATION_SHOW_COUNT_KEY,
default = 0,
)
}
override suspend fun associateApplicationIdWithWallets(appId: String, wallets: List<String>) =
withContext(dispatchers.io) {
tangemTechApi.associateApplicationIdWithWallets(

View file

@ -14,8 +14,10 @@ dependencies {
implementation(projects.core.utils)
implementation(projects.domain.core)
implementation(projects.domain.wallets.models)
implementation(projects.domain.tokens.models)
implementation(projects.domain.notifications.models)
implementation(projects.domain.wallets)
implementation(projects.libs.crypto)
// region DI
implementation(deps.hilt.android)

View file

@ -0,0 +1,12 @@
package com.tangem.domain.notifications
import com.tangem.domain.notifications.repository.NotificationsRepository
class GetTronFeeNotificationShowCountUseCase(
private val notificationsRepository: NotificationsRepository,
) {
suspend operator fun invoke(): Int {
return notificationsRepository.getTronTokenFeeNotificationShowCounter()
}
}

View file

@ -0,0 +1,19 @@
package com.tangem.domain.notifications
import com.tangem.domain.notifications.repository.NotificationsRepository
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.lib.crypto.BlockchainUtils.isTron
class IncrementNotificationsShowCountUseCase(
private val notificationsRepository: NotificationsRepository,
) {
suspend operator fun invoke(cryptoCurrency: CryptoCurrency) {
val isTronToken = cryptoCurrency is CryptoCurrency.Token &&
isTron(cryptoCurrency.network.id.value)
if (isTronToken) {
notificationsRepository.incrementTronTokenFeeNotificationShowCounter()
}
}
}

View file

@ -11,6 +11,10 @@ interface NotificationsRepository {
suspend fun getApplicationId(): String?
suspend fun getTronTokenFeeNotificationShowCounter(): Int
suspend fun incrementTronTokenFeeNotificationShowCounter()
@Throws
suspend fun associateApplicationIdWithWallets(appId: String, wallets: List<String>)

View file

@ -57,6 +57,8 @@ dependencies {
implementation(projects.domain.balanceHiding.models)
implementation(projects.domain.balanceHiding)
implementation(projects.domain.nft.models)
implementation(projects.domain.notifications)
/** Compose libraries */
implementation(deps.compose.foundation)

View file

@ -5,6 +5,7 @@ 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.R
import com.tangem.common.ui.amountScreen.converters.AmountReduceByTransformer.ReduceByData
import com.tangem.common.ui.notifications.NotificationUM
import com.tangem.common.ui.notifications.NotificationsFactory.addDustWarningNotification
@ -23,6 +24,9 @@ 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.core.ui.extensions.resourceReference
import com.tangem.domain.notifications.GetTronFeeNotificationShowCountUseCase
import com.tangem.domain.notifications.IncrementNotificationsShowCountUseCase
import com.tangem.domain.tokens.GetBalanceNotEnoughForFeeWarningUseCase
import com.tangem.domain.tokens.GetCurrencyCheckUseCase
import com.tangem.domain.tokens.IsAmountSubtractAvailableUseCase
@ -40,6 +44,7 @@ import com.tangem.features.send.v2.subcomponents.notifications.NotificationsComp
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.lib.crypto.BlockchainUtils.isTron
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.extensions.orZero
import kotlinx.collections.immutable.ImmutableList
@ -64,6 +69,8 @@ internal class NotificationsModel @Inject constructor(
private val getCurrencyCheckUseCase: GetCurrencyCheckUseCase,
private val getBalanceNotEnoughForFeeWarningUseCase: GetBalanceNotEnoughForFeeWarningUseCase,
private val validateTransactionUseCase: ValidateTransactionUseCase,
private val getTronFeeNotificationShowCountUseCase: GetTronFeeNotificationShowCountUseCase,
private val incrementNotificationsShowCountUseCase: IncrementNotificationsShowCountUseCase,
private val sendFeeReloadTrigger: SendFeeReloadTrigger,
private val sendAmountReduceTrigger: SendAmountReduceTrigger,
private val notificationsUpdateTrigger: NotificationsUpdateTrigger,
@ -89,6 +96,7 @@ internal class NotificationsModel @Inject constructor(
init {
subscribeToNotificationUpdateTrigger()
checkIfSubtractAvailable()
incrementNotificationsShowCount()
}
private fun subscribeToNotificationUpdateTrigger() {
@ -104,6 +112,12 @@ internal class NotificationsModel @Inject constructor(
}
}
private fun incrementNotificationsShowCount() {
modelScope.launch {
incrementNotificationsShowCountUseCase(cryptoCurrencyStatus.currency)
}
}
private suspend fun updateState(data: NotificationData) {
notificationData = data
buildNotifications()
@ -193,6 +207,7 @@ internal class NotificationsModel @Inject constructor(
isFeeCoverage = isFeeCoverage,
currencyCheck = currencyCheck,
)
addInfoNotifications()
}
private fun getFeeCurrencyBalanceAfterTx(sendingAmount: BigDecimal, feeValue: BigDecimal): BigDecimal? {
@ -352,4 +367,27 @@ internal class NotificationsModel @Inject constructor(
},
)
}
private suspend fun MutableList<NotificationUM>.addInfoNotifications() {
addTronNetworkFeesNotification()
}
private suspend fun MutableList<NotificationUM>.addTronNetworkFeesNotification() {
val cryptoCurrency = cryptoCurrencyStatus.currency
val isTronToken = cryptoCurrency is CryptoCurrency.Token &&
isTron(cryptoCurrency.network.id.value)
if (isTronToken && getTronFeeNotificationShowCountUseCase() <= TRON_FEE_NOTIFICATION_MAX_SHOW_COUNT) {
add(
NotificationUM.Info(
title = resourceReference(R.string.tron_will_be_send_token_fee_title),
subtitle = resourceReference(R.string.tron_will_be_send_token_fee_description),
),
)
}
}
companion object {
const val TRON_FEE_NOTIFICATION_MAX_SHOW_COUNT = 3
}
}

View file

@ -80,6 +80,7 @@ dependencies {
implementation(projects.domain.qrScanning)
implementation(projects.domain.qrScanning.models)
implementation(projects.domain.settings)
implementation(projects.domain.notifications)
/** Feature modules */
implementation(projects.features.send.api)

View file

@ -27,6 +27,8 @@ 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.notifications.GetTronFeeNotificationShowCountUseCase
import com.tangem.domain.notifications.IncrementNotificationsShowCountUseCase
import com.tangem.domain.qrscanning.models.SourceType
import com.tangem.domain.qrscanning.usecases.ListenToQrScanningUseCase
import com.tangem.domain.qrscanning.usecases.ParseQrCodeUseCase
@ -115,6 +117,7 @@ internal class SendModel @Inject constructor(
private val shareManager: ShareManager,
private val txHistoryFeatureToggles: TxHistoryFeatureToggles,
private val txHistoryContentUpdateEmitter: TxHistoryContentUpdateEmitter,
private val incrementNotificationsShowCountUseCase: IncrementNotificationsShowCountUseCase,
@DelayedWork private val coroutineScope: CoroutineScope,
private val innerRouter: InnerSendRouter,
appRouter: AppRouter,
@ -123,6 +126,7 @@ internal class SendModel @Inject constructor(
getCurrencyCheckUseCase: GetCurrencyCheckUseCase,
isFeeApproximateUseCase: IsFeeApproximateUseCase,
getBalanceNotEnoughForFeeWarningUseCase: GetBalanceNotEnoughForFeeWarningUseCase,
getTronFeeNotificationShowCountUseCase: GetTronFeeNotificationShowCountUseCase,
) : Model(), SendClickIntents {
private val params = paramsContainer.require<SendComponent.Params>()
@ -195,6 +199,7 @@ internal class SendModel @Inject constructor(
validateTransactionUseCase = validateTransactionUseCase,
getCurrencyCheckUseCase = getCurrencyCheckUseCase,
getBalanceNotEnoughForFeeWarningUseCase = getBalanceNotEnoughForFeeWarningUseCase,
getTronFeeNotificationShowCountUseCase = getTronFeeNotificationShowCountUseCase,
cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus },
feeCryptoCurrencyStatusProvider = Provider { feeCryptoCurrencyStatus },
currentStateProvider = Provider { uiState.value },
@ -528,16 +533,29 @@ internal class SendModel @Inject constructor(
uiState.value = stateFactory.syncEditStates(isFromEdit = isFromEdit)
sendScreenAnalyticSender.send(currentState.type, uiState.value)
when (currentState.type) {
prepareNextState(currentState.type, isFromEdit)
stateRouter.onNextClick()
}
private fun prepareNextState(currentStateType: SendUiStateType, isFromEdit: Boolean) {
when (currentStateType) {
SendUiStateType.Fee,
SendUiStateType.EditFee,
-> if (onFeeNextIntercept(isFromEdit)) return
SendUiStateType.Amount,
SendUiStateType.Amount -> {
loadFee()
incrementNotificationsShowCounter()
}
SendUiStateType.EditAmount,
-> loadFee()
else -> Unit
}
stateRouter.onNextClick()
}
private fun incrementNotificationsShowCounter() {
modelScope.launch {
incrementNotificationsShowCountUseCase(cryptoCurrency)
}
}
override fun onAmountNext() = onNextClick(stateRouter.isEditState)

View file

@ -2,6 +2,7 @@ package com.tangem.features.send.impl.presentation.state.confirm
import com.tangem.blockchain.common.transaction.Fee
import com.tangem.blockchain.common.transaction.TransactionFee
import com.tangem.common.ui.R
import com.tangem.common.ui.amountScreen.models.AmountState
import com.tangem.common.ui.notifications.NotificationUM
import com.tangem.common.ui.notifications.NotificationsFactory.addDustWarningNotification
@ -16,10 +17,13 @@ import com.tangem.common.ui.notifications.NotificationsFactory.addReserveAmountE
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.ui.extensions.resourceReference
import com.tangem.core.ui.utils.parseToBigDecimal
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.notifications.GetTronFeeNotificationShowCountUseCase
import com.tangem.domain.tokens.GetBalanceNotEnoughForFeeWarningUseCase
import com.tangem.domain.tokens.GetCurrencyCheckUseCase
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.error.GetFeeError
@ -36,6 +40,7 @@ import com.tangem.features.send.impl.presentation.state.fee.*
import com.tangem.lib.crypto.BlockchainUtils
import com.tangem.lib.crypto.BlockchainUtils.getTezosThreshold
import com.tangem.lib.crypto.BlockchainUtils.isTezos
import com.tangem.lib.crypto.BlockchainUtils.isTron
import com.tangem.utils.Provider
import com.tangem.utils.extensions.orZero
import kotlinx.collections.immutable.ImmutableList
@ -52,6 +57,7 @@ internal class SendNotificationFactory(
private val validateTransactionUseCase: ValidateTransactionUseCase,
private val getCurrencyCheckUseCase: GetCurrencyCheckUseCase,
private val getBalanceNotEnoughForFeeWarningUseCase: GetBalanceNotEnoughForFeeWarningUseCase,
private val getTronFeeNotificationShowCountUseCase: GetTronFeeNotificationShowCountUseCase,
private val cryptoCurrencyStatusProvider: Provider<CryptoCurrencyStatus>,
private val feeCryptoCurrencyStatusProvider: Provider<CryptoCurrencyStatus?>,
private val currentStateProvider: Provider<SendUiState>,
@ -125,9 +131,14 @@ internal class SendNotificationFactory(
isFeeCoverage = isFeeCoverage,
currencyCheck = currencyCheck,
)
addInfoNotifications()
}.toImmutableList()
}
private suspend fun MutableList<NotificationUM>.addInfoNotifications() {
addTronNetworkFeesNotification()
}
fun dismissNotificationState(clazz: Class<out NotificationUM>, isIgnored: Boolean = false): SendUiState {
val state = currentStateProvider()
val sendState = state.sendState ?: return state
@ -341,4 +352,23 @@ internal class SendNotificationFactory(
add(NotificationUM.Warning.TooHigh(diff))
}
}
private suspend fun MutableList<NotificationUM>.addTronNetworkFeesNotification() {
val cryptoCurrency = cryptoCurrencyStatusProvider().currency
val isTronToken = cryptoCurrency is CryptoCurrency.Token &&
isTron(cryptoCurrency.network.id.value)
if (isTronToken && getTronFeeNotificationShowCountUseCase() <= TRON_FEE_NOTIFICATION_MAX_SHOW_COUNT) {
add(
NotificationUM.Info(
title = resourceReference(R.string.tron_will_be_send_token_fee_title),
subtitle = resourceReference(R.string.tron_will_be_send_token_fee_description),
),
)
}
}
companion object {
const val TRON_FEE_NOTIFICATION_MAX_SHOW_COUNT = 3
}
}