Updated on 2026-08-14

This commit is contained in:
Tangem 2025-11-28 18:39:59 +07:00
commit 9163a956aa
12 changed files with 106 additions and 21 deletions

View file

@ -625,6 +625,8 @@
<string name="no_account_polkadot">Le compte de destination est inactif. Envoyez %s ou plus pour activer le compte.</string> <string name="no_account_polkadot">Le compte de destination est inactif. Envoyez %s ou plus pour activer le compte.</string>
<string name="no_account_send_to_create">Pour créer un compte, envoyez des fonds à cette adresse</string> <string name="no_account_send_to_create">Pour créer un compte, envoyez des fonds à cette adresse</string>
<string name="no_trustline_xlm_asset">Le compte destinataire n\'a pas de Trustline pour l\'actif envoyé qu\'il tente d\'envoyer.</string> <string name="no_trustline_xlm_asset">Le compte destinataire n\'a pas de Trustline pour l\'actif envoyé qu\'il tente d\'envoyer.</string>
<string name="notification_black_friday_text">Obtenez 10 $ en BTC avec chaque wallet\nDépêchez-vous !</string>
<string name="notification_black_friday_title">Black Friday : jusqu\'à 30% de réduction</string>
<string name="notification_referral_promo_button">Rejoignez maintenant</string> <string name="notification_referral_promo_button">Rejoignez maintenant</string>
<string name="notification_referral_promo_text">Partagez votre code et gagnez 5 USDT par vente. Votre ami bénéficie de 10 % de réduction.</string> <string name="notification_referral_promo_text">Partagez votre code et gagnez 5 USDT par vente. Votre ami bénéficie de 10 % de réduction.</string>
<string name="notification_referral_promo_title">Recevez des RÉCOMPENSES pour chaque ami !</string> <string name="notification_referral_promo_title">Recevez des RÉCOMPENSES pour chaque ami !</string>

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View file

@ -46,12 +46,17 @@ internal class DefaultPromoRepository(
!referralRepository.isReferralParticipant(userWalletId) && shouldShow !referralRepository.isReferralParticipant(userWalletId) && shouldShow
}.getOrDefault(false) }.getOrDefault(false)
PromoId.Sepa -> { PromoId.Sepa -> {
val isActive = getSepaPromoBanner()?.isActive ?: false val isActive = getSepaPromoBanner()?.isActive == true
isActive && shouldShow isActive && shouldShow
} }
PromoId.VisaPresale -> { PromoId.VisaPresale -> {
val isActive = getVisaPromoBanner()?.isActive ?: false val isActive = getVisaPromoBanner()?.isActive == true
isActive && shouldShow
}
PromoId.BlackFriday -> {
val isActive = getBlackFridayPromoBanner()?.isActive == true
isActive && shouldShow isActive && shouldShow
} }
@ -64,6 +69,7 @@ internal class DefaultPromoRepository(
PromoId.Referral -> flowOf(false) PromoId.Referral -> flowOf(false)
PromoId.Sepa -> flowOf(false) PromoId.Sepa -> flowOf(false)
PromoId.VisaPresale -> flowOf(false) PromoId.VisaPresale -> flowOf(false)
PromoId.BlackFriday -> flowOf(false)
} }
} }
@ -147,9 +153,18 @@ internal class DefaultPromoRepository(
}.getOrNull() }.getOrNull()
} }
private suspend fun getBlackFridayPromoBanner(): PromoBanner? {
return runCatching(dispatchers.io) {
promoBannerConverter.convert(
tangemApi.getPromoBanner(BLACK_FRIDAY_NAME).getOrThrow(),
)
}.getOrNull()
}
private companion object { private companion object {
const val SEPA_NAME = "sepa" const val SEPA_NAME = "sepa"
const val VISA_NAME = "visa-waitlist" const val VISA_NAME = "visa-waitlist"
const val BLACK_FRIDAY_NAME = "black-friday"
const val STORIES_LOAD_DELAY = 1000L const val STORIES_LOAD_DELAY = 1000L
} }
} }

View file

@ -29,4 +29,5 @@ enum class PromoId {
Referral, Referral,
Sepa, Sepa,
VisaPresale, VisaPresale,
BlackFriday,
} }

View file

@ -31,6 +31,7 @@ class ShouldShowPromoWalletUseCase(
return when (promoId) { return when (promoId) {
PromoId.Referral, PromoId.Referral,
PromoId.VisaPresale, PromoId.VisaPresale,
PromoId.BlackFriday,
-> true -> true
PromoId.Sepa -> { PromoId.Sepa -> {
val walletFirstUsageDate = settingsRepository.getWalletFirstUsageDate() val walletFirstUsageDate = settingsRepository.getWalletFirstUsageDate()

View file

@ -7,9 +7,9 @@ sealed class PromoAnalyticsEvent(
event: String, event: String,
params: Map<String, String> = mapOf(), params: Map<String, String> = mapOf(),
) : AnalyticsEvent(category = "Promotion", event = event, params = params) { ) : AnalyticsEvent(category = "Promotion", event = event, params = params) {
class NoticePromotionBanner( data class NoticePromotionBanner(
source: AnalyticsParam.ScreensSources, private val source: AnalyticsParam.ScreensSources,
program: Program, private val program: Program,
) : PromoAnalyticsEvent( ) : PromoAnalyticsEvent(
event = "Notice - Promotion Banner", event = "Notice - Promotion Banner",
params = mapOf( params = mapOf(
@ -18,10 +18,10 @@ sealed class PromoAnalyticsEvent(
), ),
) )
class PromotionBannerClicked( data class PromotionBannerClicked(
source: AnalyticsParam.ScreensSources, private val source: AnalyticsParam.ScreensSources,
program: Program, private val program: Program,
action: BannerAction, private val action: BannerAction,
) : PromoAnalyticsEvent( ) : PromoAnalyticsEvent(
event = "Promo Banner Clicked", event = "Promo Banner Clicked",
params = mapOf( params = mapOf(
@ -58,5 +58,6 @@ sealed class PromoAnalyticsEvent(
enum class Program(val programName: String) { enum class Program(val programName: String) {
Empty("Empty"), Empty("Empty"),
Sepa("Sepa"), Sepa("Sepa"),
BlackFriday("Black Friday"),
} }
} }

View file

@ -315,6 +315,11 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor(
action = PromotionBannerClicked.BannerAction.Closed, action = PromotionBannerClicked.BannerAction.Closed,
) )
PromoId.VisaPresale -> PromoAnalyticsEvent.VisaWaitlistPromoDismiss PromoId.VisaPresale -> PromoAnalyticsEvent.VisaWaitlistPromoDismiss
PromoId.BlackFriday -> PromotionBannerClicked(
source = AnalyticsParam.ScreensSources.Main,
program = Program.BlackFriday,
action = PromotionBannerClicked.BannerAction.Closed,
)
}, },
) )
@ -352,6 +357,16 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor(
analyticsEventHandler.send(PromoAnalyticsEvent.VisaWaitlistPromoJoin) analyticsEventHandler.send(PromoAnalyticsEvent.VisaWaitlistPromoJoin)
urlOpener.openUrl(VISA_PROMO_LINK) urlOpener.openUrl(VISA_PROMO_LINK)
} }
PromoId.BlackFriday -> {
analyticsEventHandler.send(
PromotionBannerClicked(
source = AnalyticsParam.ScreensSources.Main,
program = Program.BlackFriday,
action = PromotionBannerClicked.BannerAction.Clicked,
),
)
urlOpener.openUrl(BLACK_FRIDAY_PROMO_LINK)
}
} }
} }
@ -591,5 +606,10 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor(
const val VISA_PROMO_LINK = "https://tangem.com/en/cardwaitlist/?utm_source=tangem-app-banner" + const val VISA_PROMO_LINK = "https://tangem.com/en/cardwaitlist/?utm_source=tangem-app-banner" +
"&utm_medium=banner" + "&utm_medium=banner" +
"&utm_campaign=tangempaywaitlist" "&utm_campaign=tangempaywaitlist"
const val BLACK_FRIDAY_PROMO_LINK = "https://tangem.com/en/pricing/" +
"?promocode=BF2025" +
"&utm_source=tangem-app-banner" +
"&utm_medium=banner" +
"&utm_campaign=BlackFriday2025"
} }
} }

View file

@ -57,6 +57,10 @@ internal class WalletWarningsAnalyticsSender @Inject constructor(
source = AnalyticsParam.ScreensSources.Main, source = AnalyticsParam.ScreensSources.Main,
program = Program.Sepa, program = Program.Sepa,
) )
is WalletNotification.BlackFridayPromo -> NoticePromotionBanner(
source = AnalyticsParam.ScreensSources.Main,
program = Program.BlackFriday,
)
is WalletNotification.ReferralPromo -> MainScreen.ReferralPromo is WalletNotification.ReferralPromo -> MainScreen.ReferralPromo
is WalletNotification.VisaPresalePromo -> VisaWaitlistPromo is WalletNotification.VisaPresalePromo -> VisaWaitlistPromo
is WalletNotification.UnlockWallets -> null // See [SelectedWalletAnalyticsSender] is WalletNotification.UnlockWallets -> null // See [SelectedWalletAnalyticsSender]

View file

@ -103,6 +103,8 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
.distinctUntilChanged(), .distinctUntilChanged(),
shouldShowPromoWalletUseCase(userWalletId = userWallet.walletId, promoId = PromoId.Sepa) shouldShowPromoWalletUseCase(userWalletId = userWallet.walletId, promoId = PromoId.Sepa)
.distinctUntilChanged(), .distinctUntilChanged(),
shouldShowPromoWalletUseCase(userWalletId = userWallet.walletId, promoId = PromoId.BlackFriday)
.distinctUntilChanged(),
notificationsRepository.getShouldShowNotification(NotificationId.EnablePushesReminderNotification.key) notificationsRepository.getShouldShowNotification(NotificationId.EnablePushesReminderNotification.key)
.distinctUntilChanged(), .distinctUntilChanged(),
getAccessCodeSkippedUseCase(userWallet.walletId).distinctUntilChanged(), getAccessCodeSkippedUseCase(userWallet.walletId).distinctUntilChanged(),
@ -117,15 +119,23 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
val seedPhraseIssueStatus = array[3] as SeedPhraseNotificationsStatus val seedPhraseIssueStatus = array[3] as SeedPhraseNotificationsStatus
val shouldShowVisaPromo = array[4] as Boolean val shouldShowVisaPromo = array[4] as Boolean
val shouldShowSepaBanner = array[5] as Boolean val shouldShowSepaBanner = array[5] as Boolean
val shouldShowEnablePushesReminderNotification = array[6] as Boolean val shouldShowBlackFridayPromo = array[6] as Boolean
val accessCodeSkipped = array[7] as Boolean val shouldShowEnablePushesReminderNotification = array[7] as Boolean
val shouldAccessCodeSkipped = array[8] as Boolean
buildList { buildList {
addUsedOutdatedDataNotification(totalFiatBalance) addUsedOutdatedDataNotification(totalFiatBalance)
addCriticalNotifications(userWallet, seedPhraseIssueStatus, clickIntents) addCriticalNotifications(userWallet, seedPhraseIssueStatus, clickIntents)
addFinishWalletActivationNotification(userWallet, totalFiatBalance, clickIntents, accessCodeSkipped) addFinishWalletActivationNotification(
userWallet = userWallet,
totalFiatBalance = totalFiatBalance,
clickIntents = clickIntents,
shouldAccessCodeSkipped = shouldAccessCodeSkipped,
)
addBlackFridayPromoNotification(clickIntents, shouldShowBlackFridayPromo)
addVisaPresalePromoNotification(clickIntents, shouldShowVisaPromo) addVisaPresalePromoNotification(clickIntents, shouldShowVisaPromo)
@ -300,6 +310,19 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
) )
} }
private fun MutableList<WalletNotification>.addBlackFridayPromoNotification(
clickIntents: WalletClickIntents,
shouldShowPromo: Boolean,
) {
addIf(
element = WalletNotification.BlackFridayPromo(
onCloseClick = { clickIntents.onClosePromoClick(promoId = PromoId.BlackFriday) },
onClick = { clickIntents.onPromoClick(promoId = PromoId.BlackFriday) },
),
condition = shouldShowPromo,
)
}
private suspend fun MutableList<WalletNotification>.addSepaPromoNotification( private suspend fun MutableList<WalletNotification>.addSepaPromoNotification(
userWallet: UserWallet, userWallet: UserWallet,
flattenCurrencies: Lce<TokenListError, List<CryptoCurrencyStatus>>, flattenCurrencies: Lce<TokenListError, List<CryptoCurrencyStatus>>,
@ -427,13 +450,13 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
userWallet: UserWallet, userWallet: UserWallet,
totalFiatBalance: Lce<TokenListError, TotalFiatBalance>, totalFiatBalance: Lce<TokenListError, TotalFiatBalance>,
clickIntents: WalletClickIntents, clickIntents: WalletClickIntents,
accessCodeSkipped: Boolean, shouldAccessCodeSkipped: Boolean,
) { ) {
if (userWallet !is UserWallet.Hot) return if (userWallet !is UserWallet.Hot) return
val isBackupExists = userWallet.backedUp val isBackupExists = userWallet.backedUp
val isAccessCodeRequired = userWallet.hotWalletId.authType == HotWalletId.AuthType.NoPassword && val isAccessCodeRequired = userWallet.hotWalletId.authType == HotWalletId.AuthType.NoPassword &&
!accessCodeSkipped !shouldAccessCodeSkipped
val shouldShowFinishActivation = !isBackupExists || isAccessCodeRequired val shouldShowFinishActivation = !isBackupExists || isAccessCodeRequired
val type = totalFiatBalance.fold( val type = totalFiatBalance.fold(

View file

@ -350,6 +350,23 @@ sealed class WalletNotification(val config: NotificationConfig) {
), ),
) )
data class BlackFridayPromo(
val onCloseClick: () -> Unit,
val onClick: () -> Unit,
) : WalletNotification(
config = NotificationConfig(
title = resourceReference(R.string.notification_black_friday_title),
subtitle = resourceReference(R.string.notification_black_friday_text),
iconResId = R.drawable.img_black_friday_promo,
onCloseClick = onCloseClick,
buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig(
text = resourceReference(R.string.common_claim),
onClick = onClick,
),
iconSize = 54.dp,
),
)
data class PushNotifications( data class PushNotifications(
val onCloseClick: () -> Unit, val onCloseClick: () -> Unit,
val onEnabledClick: () -> Unit, val onEnabledClick: () -> Unit,

View file

@ -22,29 +22,29 @@ internal fun LazyListScope.notifications(configs: ImmutableList<WalletNotificati
items = configs, items = configs,
key = { it::class.java }, key = { it::class.java },
contentType = { it::class.java }, contentType = { it::class.java },
itemContent = { itemContent = { item ->
// TODO develop promo banner general component // TODO develop promo banner general component
when (it) { when (item) {
is WalletNotification.SwapPromo -> { is WalletNotification.SwapPromo -> {
// Use it on new promo action // Use it on new promo action
} }
is WalletNotification.NoteMigration -> { is WalletNotification.NoteMigration -> {
NoteMigrationNotification( NoteMigrationNotification(
config = it.config, config = item.config,
modifier = modifier.animateItem(fadeInSpec = null, fadeOutSpec = null), modifier = modifier.animateItem(fadeInSpec = null, fadeOutSpec = null),
) )
} }
is WalletNotification.FinishWalletActivation -> { is WalletNotification.FinishWalletActivation -> {
Notification( Notification(
config = it.config, config = item.config,
modifier = modifier.animateItem(fadeInSpec = null, fadeOutSpec = null), modifier = modifier.animateItem(fadeInSpec = null, fadeOutSpec = null),
) )
} }
else -> { else -> {
Notification( Notification(
config = it.config, config = item.config,
modifier = modifier.animateItem(fadeInSpec = null, fadeOutSpec = null), modifier = modifier.animateItem(fadeInSpec = null, fadeOutSpec = null),
iconTint = when (it) { iconTint = when (item) {
is WalletNotification.Critical -> TangemTheme.colors.icon.warning is WalletNotification.Critical -> TangemTheme.colors.icon.warning
is WalletNotification.Informational -> TangemTheme.colors.icon.accent is WalletNotification.Informational -> TangemTheme.colors.icon.accent
is WalletNotification.RateApp -> TangemTheme.colors.icon.attention is WalletNotification.RateApp -> TangemTheme.colors.icon.attention
@ -52,6 +52,7 @@ internal fun LazyListScope.notifications(configs: ImmutableList<WalletNotificati
is WalletNotification.UsedOutdatedData -> TangemTheme.colors.text.attention is WalletNotification.UsedOutdatedData -> TangemTheme.colors.text.attention
else -> null else -> null
}, },
subtitleColor = TangemTheme.colors.text.secondary,
) )
} }
} }

@ -1 +1 @@
Subproject commit 92c1bfd3123f0b2fd9e5a80b9d69bbc17a6e1160 Subproject commit 3da4c865da5f2a61d5170357c60de4ddd8425815