Updated on 2026-08-14

This commit is contained in:
Tangem 2026-01-30 18:31:21 +02:00
parent 3d70278af1
commit 9bca60feed
18 changed files with 134 additions and 1 deletions

View file

@ -0,0 +1,10 @@
package com.tangem.datasource.api.promotion.models
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class PromoBannerV2Response(
@Json(name = "promotions")
val promotions: List<PromoBannerResponse>,
)

View file

@ -2,6 +2,7 @@ package com.tangem.datasource.api.tangemTech
import com.tangem.datasource.api.common.response.ApiResponse
import com.tangem.datasource.api.promotion.models.PromoBannerResponse
import com.tangem.datasource.api.promotion.models.PromoBannerV2Response
import com.tangem.datasource.api.promotion.models.StoryContentResponse
import com.tangem.datasource.api.tangemTech.models.*
import com.tangem.datasource.api.tangemTech.models.account.GetWalletAccountsResponse
@ -188,6 +189,12 @@ interface TangemTechApi {
@Query("programName") name: String,
@Header("Cache-Control") cacheControl: String = "max-age=600",
): ApiResponse<PromoBannerResponse>
@GET("/v2/promotion")
suspend fun getPromoBannersV2(
@Query("walletId") walletId: String,
@Header("Cache-Control") cacheControl: String = "max-age=600",
): ApiResponse<PromoBannerV2Response>
// endregion
/**

View file

@ -899,6 +899,9 @@
<string name="notification_sepa_title">Krypto mit SEPA kaufen</string>
<string name="notification_visa_waitlist_promo_text">Trage dich in die Warteliste ein und erhalte eine Zahlungskarte, die es so noch nie gab.</string>
<string name="notification_visa_waitlist_promo_title">Tangem Visa Card</string>
<string name="notification_yield_promo_button">Bedingungen</string>
<string name="notification_yield_promo_text">Zahlen Sie mindestens $100 ein, halten Sie den Betrag 30 Tage und erhalten Sie $10.</string>
<string name="notification_yield_promo_title">Yield-Mode-Kampagne</string>
<string name="onboarding_access_code_feature_1_description">Du musst einen einzigen Zugangscode einrichten, um alle deine Geräte zu schützen</string>
<string name="onboarding_access_code_feature_1_title">Schützen</string>
<string name="onboarding_access_code_feature_2_description">Du kannst später auf jeder Karte oder Ring einen individuellen Zugangscode einrichten</string>

View file

@ -907,6 +907,9 @@
<string name="notification_sepa_title">Comprar criptomonedas con SEPA</string>
<string name="notification_visa_waitlist_promo_text">Únete a la lista de espera y consigue una tarjeta de pago como ninguna otra.</string>
<string name="notification_visa_waitlist_promo_title">Tangem Visa Card</string>
<string name="notification_yield_promo_button">Términos y condiciones</string>
<string name="notification_yield_promo_text">Deposite $100 o más, manténgalo durante 30 días y reciba $10.</string>
<string name="notification_yield_promo_title">¡Únase a la campaña Yield Mode!</string>
<string name="onboarding_access_code_feature_1_description">Configure un código de acceso único para proteger todos sus dispositivos.</string>
<string name="onboarding_access_code_feature_1_title">Proteger</string>
<string name="onboarding_access_code_feature_2_description">Puede configurar un código de acceso individual en cada tarjeta más adelante</string>

View file

@ -907,6 +907,9 @@
<string name="notification_sepa_title">Acheter des cryptomonnaies avec SEPA</string>
<string name="notification_visa_waitlist_promo_text">Rejoignez la liste dattente et obtenez une carte de paiement pas comme les autres.</string>
<string name="notification_visa_waitlist_promo_title">Tangem Visa Card</string>
<string name="notification_yield_promo_button">Conditions générales</string>
<string name="notification_yield_promo_text">Déposez 100 $ ou plus, conservez le dépôt pendant 30 jours et recevez 10 $.</string>
<string name="notification_yield_promo_title">Rejoignez la campagne Yield Mode !</string>
<string name="onboarding_access_code_feature_1_description">Vous devez définir un seul code d\'accès pour protéger tous vos appareils.</string>
<string name="onboarding_access_code_feature_1_title">Protéger</string>
<string name="onboarding_access_code_feature_2_description">Vous pourrez définir un code d\'accès individuel sur chaque carte plus tard</string>
@ -1468,7 +1471,7 @@
<string name="tangem_pay_freeze_card_freeze">Geler</string>
<string name="tangem_pay_freeze_card_success">Votre carte est gelée.</string>
<string name="tangem_pay_get_help">Obtenir de l\'aide</string>
<string name="tangem_pay_history_item_spend_mc_declined_reason">Raison : %s</string>
<string name="tangem_pay_history_item_spend_mc_declined_reason">Raison: %s</string>
<string name="tangem_pay_history_item_spend_mc_title_format" formatted="false">%s · %s</string>
<string name="tangem_pay_history_item_spend_mcc">MCC %s</string>
<string name="tangem_pay_other">Autre</string>

View file

@ -894,6 +894,9 @@
<string name="notification_sepa_title">SEPAで暗号資産を購入</string>
<string name="notification_visa_waitlist_promo_text">事前申し込み受付中。他にない特別なカードを、いち早く体験しよう。</string>
<string name="notification_visa_waitlist_promo_title">Tangem Visaカード</string>
<string name="notification_yield_promo_button">利用規約</string>
<string name="notification_yield_promo_text">100ドル以上を入金し、30日間保持すると、10ドルを受け取れます。</string>
<string name="notification_yield_promo_title">Yield Mode キャンペーンに参加しよう!</string>
<string name="onboarding_access_code_feature_1_description">すべてのデバイスを保護するには、単一のアクセスコードを設定してください。</string>
<string name="onboarding_access_code_feature_1_title">保護する</string>
<string name="onboarding_access_code_feature_2_description">後で各カードおよびリングに個別のアクセスコードを設定できます。</string>

View file

@ -925,6 +925,9 @@
<string name="notification_sepa_title">Покупайте крипту через SEPA</string>
<string name="notification_visa_waitlist_promo_text">Присоединяйтесь к списку ожидания и получите уникальную платёжную карту, не похожую ни на одну другую.</string>
<string name="notification_visa_waitlist_promo_title">Tangem Visa Card</string>
<string name="notification_yield_promo_button">Условия программы</string>
<string name="notification_yield_promo_text">Внесите депозит от $100, удерживайте его 30 дней и получите бонус $10</string>
<string name="notification_yield_promo_title">Подключите Режим доходности!</string>
<string name="onboarding_access_code_feature_1_description">Установите единый код доступа для защиты всех ваших карт или колец</string>
<string name="onboarding_access_code_feature_1_title">Защита</string>
<string name="onboarding_access_code_feature_2_description">Установите индивидуальный код доступа для каждой карты или кольца позже.</string>

View file

@ -909,6 +909,9 @@
<string name="notification_sepa_title">Купуйте криптовалюту через SEPA</string>
<string name="notification_visa_waitlist_promo_text">Приєднуйтесь до списку очікування та отримайте унікальну платіжну картку, якої ще не було.</string>
<string name="notification_visa_waitlist_promo_title">Tangem Visa Card</string>
<string name="notification_yield_promo_button">Умови та положення</string>
<string name="notification_yield_promo_text">Внесіть депозит від $100, утримуйте його 30 днів та отримайте $10.</string>
<string name="notification_yield_promo_title">Кампанія Yield Mode!</string>
<string name="onboarding_access_code_feature_1_description">Налаштуйте єдиний код доступу для захисту всіх ваших карток або кілець</string>
<string name="onboarding_access_code_feature_1_title">Захист</string>
<string name="onboarding_access_code_feature_2_description">Встановіть індивідуальний код доступу для кожної картки або кільця пізніше.</string>

View file

@ -86,6 +86,8 @@
<string name="address_qr_code_message_format">Send only %1$s (%2$s) from %3$s network to this address. Using other tokens and networks may result in loss of funds.</string>
<string name="address_type_default">Default</string>
<string name="address_type_legacy">Legacy</string>
<string name="alert_authentication_error_message">Try to reset biometrics on your device or contact support</string>
<string name="alert_authentication_error_title">Authentication error</string>
<string name="alert_button_how_to_scan">How to scan</string>
<string name="alert_button_request_support">Request support</string>
<string name="alert_button_try_again">Try again</string>
@ -908,6 +910,9 @@
<string name="notification_sepa_title">Buy Crypto with SEPA</string>
<string name="notification_visa_waitlist_promo_text">Join the waitlist and get a payment card unlike any other</string>
<string name="notification_visa_waitlist_promo_title">Tangem Visa Card</string>
<string name="notification_yield_promo_button">Terms and conditions</string>
<string name="notification_yield_promo_text">Deposit $100+, hold for 30 days, get $10</string>
<string name="notification_yield_promo_title">Join the Yield Mode Campaign!</string>
<string name="onboarding_access_code_feature_1_description">Set up a single access code to protect all your devices.</string>
<string name="onboarding_access_code_feature_1_title">Protect</string>
<string name="onboarding_access_code_feature_2_description">Set an individual access code for each card or ring later.</string>

View file

@ -66,6 +66,11 @@ internal class DefaultPromoRepository(
PromoId.OnePlusOne -> {
val isActive = getOnePlusOnePromoBanner()?.isActive == true
isActive && shouldShow
}
PromoId.YieldPromo -> {
val isActive = getYieldPromoBanner(userWalletId)?.isActive == true
isActive && shouldShow
}
}
@ -79,6 +84,7 @@ internal class DefaultPromoRepository(
PromoId.VisaPresale -> flowOf(false)
PromoId.BlackFriday -> flowOf(false)
PromoId.OnePlusOne -> flowOf(false)
PromoId.YieldPromo -> flowOf(false)
}
}
@ -190,12 +196,22 @@ internal class DefaultPromoRepository(
}.getOrNull()
}
private suspend fun getYieldPromoBanner(userWalletId: UserWalletId): PromoBanner? {
return runCatching(dispatchers.io) {
val response = tangemApi.getPromoBannersV2(userWalletId.stringValue).getOrThrow()
val yieldPromotion = response.promotions.find { it.name == YIELD_PROMO_NAME }
?: return@runCatching null
promoBannerConverter.convert(yieldPromotion)
}.getOrNull()
}
private companion object {
const val SEPA_NAME = "sepa"
const val VISA_NAME = "visa-waitlist"
const val BLACK_FRIDAY_NAME = "black-friday"
const val MOONPAY_NAME = "moonpay"
const val ONE_PLUS_ONE_NAME = "one-plus-one"
const val YIELD_PROMO_NAME = "promo-yield"
const val STORIES_LOAD_DELAY = 1000L
}
}

View file

@ -31,4 +31,5 @@ enum class PromoId {
VisaPresale,
BlackFriday,
OnePlusOne,
YieldPromo,
}

View file

@ -33,6 +33,7 @@ class ShouldShowPromoWalletUseCase(
PromoId.VisaPresale,
PromoId.BlackFriday,
PromoId.OnePlusOne,
PromoId.YieldPromo,
-> true
PromoId.Sepa -> {
val walletFirstUsageDate = settingsRepository.getWalletFirstUsageDate()

View file

@ -60,5 +60,6 @@ sealed class PromoAnalyticsEvent(
Sepa("Sepa"),
BlackFriday("Black Friday"),
OnePlusOne("One-Plus-One"),
YieldPromo("Yield Promo"),
}
}

View file

@ -108,6 +108,8 @@ internal interface WalletWarningsClickIntents {
fun onDenyPermissions()
fun onFinishWalletActivationClick(isBackupExists: Boolean)
fun onYieldPromoTermsAndConditionsClick()
}
@Suppress("LargeClass", "LongParameterList", "TooManyFunctions")
@ -337,6 +339,11 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor(
program = Program.OnePlusOne,
action = PromotionBannerClicked.BannerAction.Closed(),
)
PromoId.YieldPromo -> PromotionBannerClicked(
source = AnalyticsParam.ScreensSources.Main,
program = Program.YieldPromo,
action = PromotionBannerClicked.BannerAction.Closed(),
)
},
)
@ -394,6 +401,7 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor(
)
urlOpener.openUrl(ONE_PLUS_ONE_PROMO_LINK)
}
PromoId.YieldPromo -> Unit // banner is not clickable, only terms and conditions button
}
}
@ -611,6 +619,17 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor(
}
}
override fun onYieldPromoTermsAndConditionsClick() {
analyticsEventHandler.send(
PromotionBannerClicked(
source = AnalyticsParam.ScreensSources.Main,
program = Program.YieldPromo,
action = PromotionBannerClicked.BannerAction.Clicked(),
),
)
urlOpener.openUrl(YIELD_PROMO_TERMS_LINK)
}
private companion object {
const val VISA_PROMO_LINK = "https://tangem.com/en/cardwaitlist/?utm_source=tangem-app-banner" +
"&utm_medium=banner" +
@ -625,5 +644,6 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor(
"&utm_source=tangem-app-banner" +
"&utm_medium=banner" +
"&utm_campaign=BOGO50"
const val YIELD_PROMO_TERMS_LINK = "https://tangem.com/docs/yield-mode-toc.html"
}
}

View file

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

View file

@ -96,6 +96,8 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
notificationsRepository.getShouldShowNotification(NotificationId.EnablePushesReminderNotification.key)
.distinctUntilChanged(),
getAccessCodeSkippedUseCase(userWallet.walletId).distinctUntilChanged(),
shouldShowPromoWalletUseCase(userWalletId = userWallet.walletId, promoId = PromoId.YieldPromo)
.distinctUntilChanged(),
) { array -> array }
.combine(tokenListFlow()) { array, any: Any -> arrayOf(any).plus(elements = array) }
.map { array ->
@ -108,6 +110,7 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
val shouldShowOnePlusOnePromo = array[4] as Boolean
val shouldShowEnablePushesReminderNotification = array[5] as Boolean
val shouldAccessCodeSkipped = array[6] as Boolean
val shouldShowYieldPromo = array[7] as Boolean
buildList {
addUsedOutdatedDataNotification(totalFiatBalance)
@ -123,6 +126,8 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
addOnePlusOnePromoNotification(clickIntents, shouldShowOnePlusOnePromo)
addYieldPromoNotification(clickIntents, shouldShowYieldPromo)
addInformationalNotifications(userWallet, cardTypesResolver, flattenCurrencies, clickIntents)
addWarningNotifications(cardTypesResolver, flattenCurrencies, isNeedToBackup, clickIntents)
@ -287,6 +292,19 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
)
}
private fun MutableList<WalletNotification>.addYieldPromoNotification(
clickIntents: WalletClickIntents,
shouldShowPromo: Boolean,
) {
addIf(
element = WalletNotification.YieldPromo(
onCloseClick = { clickIntents.onClosePromoClick(promoId = PromoId.YieldPromo) },
onTermsAndConditionsClick = { clickIntents.onYieldPromoTermsAndConditionsClick() },
),
condition = shouldShowPromo,
)
}
// private fun MutableList<WalletNotification>.addYieldSupplyNotifications(
// flattenCurrencies: Lce<TokenListError, List<CryptoCurrencyStatus>>,
// ) {

View file

@ -396,6 +396,23 @@ sealed class WalletNotification(val config: NotificationConfig) {
),
)
data class YieldPromo(
val onCloseClick: () -> Unit,
val onTermsAndConditionsClick: () -> Unit,
) : WalletNotification(
config = NotificationConfig(
title = resourceReference(R.string.notification_yield_promo_title),
subtitle = resourceReference(R.string.notification_yield_promo_text),
iconResId = R.drawable.ic_yield_promo_36,
onCloseClick = onCloseClick,
buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig(
text = resourceReference(R.string.notification_yield_promo_button),
onClick = onTermsAndConditionsClick,
),
iconSize = 36.dp,
),
)
data class PushNotifications(
val onCloseClick: () -> Unit,
val onEnabledClick: () -> Unit,

View file

@ -0,0 +1,15 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="36dp"
android:height="36dp"
android:viewportWidth="36"
android:viewportHeight="36">
<path
android:pathData="M18,18m-18,0a18,18 0,1 1,36 0a18,18 0,1 1,-36 0"
android:strokeAlpha="0.1"
android:fillColor="#0099FF"
android:fillAlpha="0.1"/>
<path
android:pathData="M18.057,7.745C20.247,7.745 21.968,7.745 23.312,7.926C24.688,8.111 25.781,8.498 26.639,9.356C27.497,10.214 27.885,11.307 28.07,12.683C28.25,14.027 28.25,15.748 28.25,17.938V18.052C28.25,20.243 28.25,21.964 28.07,23.307C27.885,24.684 27.497,25.776 26.639,26.635C25.781,27.493 24.688,27.879 23.312,28.064C21.968,28.245 20.247,28.245 18.057,28.245H17.943C15.752,28.245 14.031,28.245 12.688,28.064C11.311,27.879 10.219,27.493 9.361,26.635C8.502,25.776 8.116,24.684 7.931,23.307C7.75,21.964 7.75,20.243 7.75,18.052V17.938C7.75,15.748 7.75,14.027 7.931,12.683C8.116,11.307 8.503,10.214 9.361,9.356C10.219,8.498 11.311,8.111 12.688,7.926C14.031,7.745 15.752,7.745 17.943,7.745H18.057ZM13,21.248C12.586,21.248 12.25,21.584 12.25,21.998V23.998C12.25,24.412 12.586,24.748 13,24.748C13.414,24.748 13.75,24.412 13.75,23.998V21.998C13.75,21.584 13.414,21.248 13,21.248ZM18,20.247C17.586,20.247 17.25,20.583 17.25,20.997V23.997C17.25,24.411 17.586,24.747 18,24.747C18.414,24.747 18.75,24.411 18.75,23.997V20.997C18.75,20.583 18.414,20.247 18,20.247ZM23,18.245C22.586,18.245 22.25,18.581 22.25,18.995V23.995C22.25,24.409 22.586,24.745 23,24.745C23.414,24.745 23.75,24.409 23.75,23.995V18.995C23.75,18.581 23.414,18.245 23,18.245ZM21.859,11.247C21.654,11.256 21.416,11.295 21.179,11.335C21.168,11.337 21.157,11.339 21.146,11.341L19.878,11.55C19.47,11.617 19.193,12.002 19.26,12.411C19.327,12.82 19.714,13.097 20.122,13.029L20.846,12.91C18.963,15.552 15.953,16.745 12,16.745C11.586,16.745 11.25,17.081 11.25,17.495C11.25,17.909 11.586,18.245 12,18.245C16.322,18.245 19.898,16.9 22.132,13.689L22.279,14.201C22.393,14.599 22.808,14.829 23.206,14.716C23.604,14.601 23.835,14.186 23.721,13.788L23.403,12.677C23.399,12.666 23.396,12.655 23.393,12.643C23.335,12.44 23.271,12.218 23.189,12.036C23.088,11.814 22.93,11.576 22.645,11.414C22.369,11.257 22.09,11.236 21.859,11.247Z"
android:fillColor="#0099FF"
android:fillType="evenOdd"/>
</vector>