Updated on 2026-08-14

This commit is contained in:
Tangem 2025-04-25 16:56:19 +05:00
parent 13c3b6d4e2
commit 2d2d157943
14 changed files with 105 additions and 8 deletions

View file

@ -520,6 +520,9 @@
<string name="nft_wallet_title">NFT collections</string>
<string name="nft_wallet_unable_to_load">Unable to load the data</string>
<string name="nft_receive_choose_network">Choose network</string>
<string name="notification_referral_promo_button">Join Now</string>
<string name="notification_referral_promo_text">Share your code - earn 5 USDT per sale. Your friend gets 10% OFF.</string>
<string name="notification_referral_promo_title">Get REWARDS for every friend!</string>
<string name="nft_details_last_sale_price">Last sale price</string>
<string name="nft_details_rarity_label">Rarity label</string>
<string name="nft_details_rarity_rank">Rarity rank</string>

View file

@ -25,6 +25,8 @@ import androidx.compose.ui.semantics.Role
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.R
import com.tangem.core.ui.components.*
import com.tangem.core.ui.components.buttons.common.TangemButtonSize
@ -55,6 +57,7 @@ fun Notification(
subtitleColor: Color = TangemTheme.colors.text.tertiary,
containerColor: Color? = null,
iconTint: Color? = null,
iconSize: Dp = 20.dp,
isEnabled: Boolean = true,
) {
NotificationBaseContainer(
@ -68,6 +71,7 @@ fun Notification(
MainContent(
iconResId = config.iconResId,
iconTint = iconTint,
iconSize = iconSize,
title = config.title,
subtitle = config.subtitle,
subtitleColor = subtitleColor,
@ -127,6 +131,7 @@ internal fun NotificationBaseContainer(
private fun MainContent(
iconResId: Int,
iconTint: Color?,
iconSize: Dp,
title: TextReference?,
subtitle: TextReference,
subtitleColor: Color,
@ -137,7 +142,7 @@ private fun MainContent(
iconResId = iconResId,
tint = iconTint,
modifier = Modifier
.size(size = TangemTheme.dimens.size20)
.size(size = iconSize)
.align(alignment = Alignment.CenterVertically),
)

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

View file

@ -22,6 +22,7 @@ dependencies {
implementation(projects.domain.promo)
implementation(projects.domain.promo.models)
implementation(projects.domain.wallets.models)
implementation(projects.features.referral.domain)
implementation(projects.core.datasource)
implementation(projects.core.utils)

View file

@ -14,9 +14,11 @@ import com.tangem.domain.promo.PromoRepository
import com.tangem.domain.promo.models.PromoId
import com.tangem.domain.promo.models.StoryContent
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.feature.referral.domain.ReferralRepository
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.withContext
import kotlinx.coroutines.withTimeoutOrNull
@ -26,6 +28,7 @@ internal class DefaultPromoRepository(
private val appPreferencesStore: AppPreferencesStore,
private val promoStoriesStore: PromoStoriesStore,
private val dispatchers: CoroutineDispatcherProvider,
private val referralRepository: ReferralRepository,
) : PromoRepository {
private val storyContentConverter = StoryContentResponseConverter()
@ -34,7 +37,13 @@ internal class DefaultPromoRepository(
return appPreferencesStore.get(
key = PreferencesKeys.getShouldShowPromoKey(promoId = promoId.name),
default = true,
)
).map { shouldShow ->
if (promoId == PromoId.Referral) {
!referralRepository.isReferralParticipant(userWalletId) && shouldShow
} else {
shouldShow
}
}
}
override fun isReadyToShowTokenPromo(promoId: PromoId): Flow<Boolean> {

View file

@ -5,6 +5,7 @@ import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.datasource.local.promo.PromoStoriesStore
import com.tangem.domain.promo.PromoRepository
import com.tangem.feature.referral.domain.ReferralRepository
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.Module
import dagger.Provides
@ -23,12 +24,14 @@ internal object PromoDataModule {
appPreferencesStore: AppPreferencesStore,
promoStoriesStore: PromoStoriesStore,
dispatchers: CoroutineDispatcherProvider,
referralRepository: ReferralRepository,
): PromoRepository {
return DefaultPromoRepository(
tangemApi = tangemTechApi,
appPreferencesStore = appPreferencesStore,
promoStoriesStore = promoStoriesStore,
dispatchers = dispatchers,
referralRepository = referralRepository,
)
}
}

View file

@ -39,6 +39,10 @@ internal class ReferralRepositoryImpl @Inject constructor(
}
}
override suspend fun isReferralParticipant(userWalletId: UserWalletId): Boolean {
return getReferralData(userWalletId.stringValue) is ReferralData.ParticipantData
}
override suspend fun startReferral(
walletId: String,
networkId: String,

View file

@ -10,6 +10,9 @@ interface ReferralRepository {
/** Returns data object of [ReferralData] depends on user program status */
suspend fun getReferralData(walletId: String): ReferralData
/** Returns whether user is participating in referral program */
suspend fun isReferralParticipant(userWalletId: UserWalletId): Boolean
/** Starts user referral program */
suspend fun startReferral(walletId: String, networkId: String, tokenId: String, address: String): ReferralData

View file

@ -2,6 +2,8 @@ package com.tangem.feature.wallet.child.wallet.model.intents
import arrow.core.getOrElse
import com.tangem.common.TangemBlogUrlBuilder
import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.AppRouter
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.decompose.di.ModelScoped
@ -70,6 +72,8 @@ internal interface WalletWarningsClickIntents {
fun onClosePromoClick(promoId: PromoId)
fun onPromoClick(promoId: PromoId)
fun onSupportClick()
fun onNoteMigrationButtonClick(url: String)
@ -106,6 +110,7 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor(
private val urlOpener: UrlOpener,
private val tokensFeatureToggles: TokensFeatureToggles,
private val multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
private val appRouter: AppRouter,
) : BaseWalletClickIntents(), WalletWarningsClickIntents {
override fun onAddBackupCardClick() {
@ -271,17 +276,32 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor(
override fun onClosePromoClick(promoId: PromoId) {
analyticsEventHandler.send(
if (promoId == PromoId.Referral) {
MainScreen.ReferralPromoButtonDismiss
} else {
TokenSwapPromoAnalyticsEvent.PromotionBannerClicked(
source = AnalyticsParam.ScreensSources.Main,
programName = TokenSwapPromoAnalyticsEvent.ProgramName.Empty, // Use it on new promo action
action = TokenSwapPromoAnalyticsEvent.PromotionBannerClicked.BannerAction.Closed,
),
)
},
)
modelScope.launch(dispatchers.main) {
shouldShowPromoWalletUseCase.neverToShow(promoId)
}
}
override fun onPromoClick(promoId: PromoId) {
if (promoId == PromoId.Referral) {
val userWallet = getSelectedUserWallet() ?: return
analyticsEventHandler.send(MainScreen.ReferralPromoButtonParticipate)
modelScope.launch {
shouldShowPromoWalletUseCase.neverToShow(promoId)
appRouter.push(AppRoute.ReferralProgram(userWalletId = userWallet.walletId))
}
}
}
override fun onSupportClick() {
val scanResponse = getSelectedUserWallet()?.scanResponse ?: return
val cardInfo = getCardInfoUseCase(scanResponse).getOrNull() ?: return

View file

@ -137,5 +137,11 @@ sealed class WalletScreenAnalyticsEvent {
data object NoticeSeedPhraseSupportButtonUsed : MainScreen(event = "Button - Support Used")
data object NoticeSeedPhraseSupportButtonDeclined : MainScreen(event = "Button - Support Declined")
// region Referral Promo
data object ReferralPromo : MainScreen(event = "Referral Banner")
data object ReferralPromoButtonParticipate : MainScreen(event = "Button - Referral Participate")
data object ReferralPromoButtonDismiss : MainScreen(event = "Button - Referral Dismiss")
//endregion
}
}

View file

@ -53,6 +53,7 @@ internal class WalletWarningsAnalyticsSender @Inject constructor(
source = AnalyticsParam.ScreensSources.Main,
programName = ProgramName.Empty, // Use it on new promo action
)
is WalletNotification.ReferralPromo -> MainScreen.ReferralPromo
is WalletNotification.UnlockWallets -> null // See [SelectedWalletAnalyticsSender]
is WalletNotification.Informational.NoAccount,
is WalletNotification.Warning.LowSignatures,

View file

@ -6,6 +6,8 @@ import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.core.lce.Lce
import com.tangem.domain.demo.IsDemoCardUseCase
import com.tangem.domain.models.StatusSource
import com.tangem.domain.promo.ShouldShowPromoWalletUseCase
import com.tangem.domain.promo.models.PromoId
import com.tangem.domain.settings.IsReadyToShowRateAppUseCase
import com.tangem.domain.tokens.error.TokenListError
import com.tangem.domain.tokens.model.CryptoCurrency
@ -34,6 +36,7 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
private val isNeedToBackupUseCase: IsNeedToBackupUseCase,
private val backupValidator: BackupValidator,
private val seedPhraseNotificationUseCase: SeedPhraseNotificationUseCase,
private val shouldShowPromoWalletUseCase: ShouldShowPromoWalletUseCase,
) {
@Suppress("MagicNumber", "MaximumLineLength")
@ -45,12 +48,15 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
flow2 = isReadyToShowRateAppUseCase(),
flow3 = isNeedToBackupUseCase(userWallet.walletId),
flow4 = seedPhraseNotificationUseCase(userWalletId = userWallet.walletId),
) { maybeTokenList, isReadyToShowRating, isNeedToBackup, seedPhraseIssueStatus ->
flow5 = shouldShowPromoWalletUseCase(userWalletId = userWallet.walletId, promoId = PromoId.Referral),
) { maybeTokenList, isReadyToShowRating, isNeedToBackup, seedPhraseIssueStatus, shouldShowReferralPromo ->
buildList {
addUsedOutdatedDataNotification(maybeTokenList)
addCriticalNotifications(userWallet, seedPhraseIssueStatus, clickIntents)
addReferralPromoNotification(cardTypesResolver, clickIntents, shouldShowReferralPromo)
addInformationalNotifications(cardTypesResolver, maybeTokenList, clickIntents)
addWarningNotifications(cardTypesResolver, maybeTokenList, isNeedToBackup, clickIntents)
@ -187,6 +193,20 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
.map(CryptoCurrencyStatus::currency)
}
private fun MutableList<WalletNotification>.addReferralPromoNotification(
cardTypesResolver: CardTypesResolver,
clickIntents: WalletClickIntents,
shouldShowPromo: Boolean,
) {
addIf(
element = WalletNotification.ReferralPromo(
onCloseClick = { clickIntents.onClosePromoClick(promoId = PromoId.Referral) },
onClick = { clickIntents.onPromoClick(promoId = PromoId.Referral) },
),
condition = shouldShowPromo && cardTypesResolver.isTangemWallet(),
)
}
private fun MutableList<WalletNotification>.addWarningNotifications(
cardTypesResolver: CardTypesResolver,
tokenList: Lce<TokenListError, TokenList>,

View file

@ -254,4 +254,20 @@ sealed class WalletNotification(val config: NotificationConfig) {
iconResId = R.drawable.ic_error_sync_24,
),
)
data class ReferralPromo(
val onCloseClick: () -> Unit,
val onClick: () -> Unit,
) : WalletNotification(
config = NotificationConfig(
title = resourceReference(R.string.notification_referral_promo_title),
subtitle = resourceReference(R.string.notification_referral_promo_text),
iconResId = R.drawable.img_referral_promo,
onCloseClick = onCloseClick,
buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig(
text = resourceReference(R.string.notification_referral_promo_button),
onClick = onClick,
),
),
)
}

View file

@ -3,6 +3,7 @@ package com.tangem.feature.wallet.presentation.wallet.ui.components.common
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.lazy.items
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.notifications.NoteMigrationNotification
import com.tangem.core.ui.components.notifications.Notification
import com.tangem.core.ui.res.TangemTheme
@ -38,6 +39,11 @@ internal fun LazyListScope.notifications(configs: ImmutableList<WalletNotificati
Notification(
config = it.config,
modifier = modifier.animateItem(fadeInSpec = null, fadeOutSpec = null),
iconSize = if (it is WalletNotification.ReferralPromo) {
54.dp
} else {
20.dp
},
iconTint = when (it) {
is WalletNotification.Critical -> TangemTheme.colors.icon.warning
is WalletNotification.Informational -> TangemTheme.colors.icon.accent