Updated on 2026-08-14

This commit is contained in:
Tangem 2026-05-14 11:50:47 +03:00
parent f1a4be49b3
commit ce948a71ae
51 changed files with 20 additions and 1186 deletions

View file

@ -24,8 +24,6 @@ import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.datasource.local.preferences.PreferencesKeys
import com.tangem.datasource.local.walletmanager.WalletManagersStore
import com.tangem.datasource.utils.WireMockRedirectInterceptor
import com.tangem.domain.promo.PromoRepository
import com.tangem.domain.promo.models.PromoId
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
import com.tangem.features.pushnotifications.api.utils.PUSH_PERMISSION
import com.tangem.tap.MainActivity
@ -59,9 +57,6 @@ abstract class BaseTestCase : TestCase(
@Inject
lateinit var appPreferencesStore: AppPreferencesStore
@Inject
lateinit var promoRepository: PromoRepository
@Inject
lateinit var walletManagersStore: WalletManagersStore
@ -136,7 +131,6 @@ abstract class BaseTestCase : TestCase(
value = false
)
}
promoRepository.setNeverToShowWalletPromo(PromoId.Sepa)
}
apiEnvironmentRule.setup(apiConfigsManager)
ActivityScenario.launch(MainActivity::class.java)

View file

@ -4,7 +4,6 @@ import com.tangem.domain.offramp.GetOfframpUrlUseCase
import com.tangem.domain.offramp.repository.OfframpRepository
import com.tangem.domain.onramp.*
import com.tangem.domain.onramp.repositories.*
import com.tangem.domain.promo.PromoRepository
import com.tangem.domain.settings.repositories.SettingsRepository
import com.tangem.tap.data.DefaultOfframpRepository
import com.tangem.tap.network.exchangeServices.SellService
@ -269,14 +268,12 @@ internal object OnrampDomainModule {
onrampErrorResolver: OnrampErrorResolver,
onrampTransactionRepository: OnrampTransactionRepository,
settingsRepository: SettingsRepository,
promoRepository: PromoRepository,
): GetOnrampOffersUseCase {
return GetOnrampOffersUseCase(
onrampRepository = onrampRepository,
errorResolver = onrampErrorResolver,
onrampTransactionRepository = onrampTransactionRepository,
settingsRepository = settingsRepository,
promoRepository = promoRepository,
)
}

View file

@ -2,11 +2,8 @@ package com.tangem.tap.di.domain
import com.tangem.domain.promo.GetStoryContentUseCase
import com.tangem.domain.promo.PromoRepository
import com.tangem.domain.promo.ShouldShowPromoTokenUseCase
import com.tangem.domain.promo.ShouldShowPromoWalletUseCase
import com.tangem.domain.promo.ShouldShowStoriesUseCase
import com.tangem.domain.settings.repositories.SettingsRepository
import com.tangem.features.promobanners.api.NewPromoBannersFeatureToggles
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
@ -17,26 +14,6 @@ import javax.inject.Singleton
@InstallIn(SingletonComponent::class)
internal object PromoDomainModule {
@Provides
@Singleton
fun provideShouldShowSwapPromoWalletUseCase(
promoRepository: PromoRepository,
settingsRepository: SettingsRepository,
newPromoBannersFeatureToggles: NewPromoBannersFeatureToggles,
): ShouldShowPromoWalletUseCase {
return ShouldShowPromoWalletUseCase(
promoRepository,
settingsRepository,
newPromoBannersFeatureToggles.isNewPromoBannersEnabled,
)
}
@Provides
@Singleton
fun provideShouldShowSwapPromoTokenUseCase(promoRepository: PromoRepository): ShouldShowPromoTokenUseCase {
return ShouldShowPromoTokenUseCase(promoRepository)
}
@Provides
@Singleton
fun provideShouldShowSwapStoriesUseCase(promoRepository: PromoRepository): ShouldShowStoriesUseCase {

View file

@ -27,10 +27,6 @@
"name": "DYNAMIC_ADDRESSES_ENABLED",
"version": "undefined"
},
{
"name": "NEW_PROMO_BANNERS_ENABLED",
"version": "5.37"
},
{
"name": "VIRTUAL_ACCOUNTS_ENABLED",
"version": "undefined"

View file

@ -1,24 +0,0 @@
package com.tangem.datasource.api.promotion.models
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class PromoBannerResponse(
@Json(name = "name") val name: String,
@Json(name = "all") val bannerState: BannerState?,
) {
@JsonClass(generateAdapter = true)
data class BannerState(
@Json(name = "timeline") val timeline: Timeline,
@Json(name = "status") val status: String,
@Json(name = "link") val link: String?,
)
@JsonClass(generateAdapter = true)
data class Timeline(
@Json(name = "start") val start: String,
@Json(name = "end") val end: String,
)
}

View file

@ -1,10 +0,0 @@
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

@ -1,8 +1,6 @@
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.promobanners.PromoBannerDisplaysResponse
@ -172,20 +170,6 @@ interface TangemTechApi {
): ApiResponse<GetWalletArchivedAccountsResponse>
// endregion
// region promo banners
@GET("/v1/promotion")
suspend fun getPromoBanner(
@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
// region promo banners
@GET("v1/banner/displays")
suspend fun getPromoBannerDisplays(

View file

@ -1,10 +1,7 @@
package com.tangem.datasource.di
import com.tangem.datasource.local.datastore.RuntimeDataStore
import com.tangem.datasource.local.datastore.RuntimeSharedStore
import com.tangem.datasource.local.promo.DefaultPromoBannerStore
import com.tangem.datasource.local.promo.DefaultPromoStoriesStore
import com.tangem.datasource.local.promo.PromoBannerStore
import com.tangem.datasource.local.promo.PromoStoriesStore
import dagger.Module
import dagger.Provides
@ -21,10 +18,4 @@ object PromoStoreModule {
fun providePromoStoriesStore(): PromoStoriesStore {
return DefaultPromoStoriesStore(dataStore = RuntimeDataStore())
}
@Provides
@Singleton
fun providePromoBannerStore(): PromoBannerStore {
return DefaultPromoBannerStore(dataStore = RuntimeSharedStore())
}
}

View file

@ -172,8 +172,6 @@ object PreferencesKeys {
// region Promo
fun getShouldShowStoriesKey(storyId: String) = booleanPreferencesKey("shouldShowStories_$storyId")
fun getShouldShowPromoKey(promoId: String) = booleanPreferencesKey("shouldShowPromo_$promoId")
// endregion
// region Permission

View file

@ -1,19 +0,0 @@
package com.tangem.datasource.local.promo
import com.tangem.datasource.api.promotion.models.PromoBannerResponse
import com.tangem.datasource.local.datastore.RuntimeSharedStore
internal class DefaultPromoBannerStore(
private val dataStore: RuntimeSharedStore<Map<String, PromoBannerResponse>>,
) : PromoBannerStore {
override suspend fun getSyncOrNull(promoId: String): PromoBannerResponse? {
return dataStore.getSyncOrNull()?.get(promoId)
}
override suspend fun store(promoId: String, promoBanner: PromoBannerResponse) {
dataStore.update(emptyMap()) { current ->
current + (promoId to promoBanner)
}
}
}

View file

@ -1,10 +0,0 @@
package com.tangem.datasource.local.promo
import com.tangem.datasource.api.promotion.models.PromoBannerResponse
interface PromoBannerStore {
suspend fun getSyncOrNull(promoId: String): PromoBannerResponse?
suspend fun store(promoId: String, promoBanner: PromoBannerResponse)
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

View file

@ -1,29 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:autoMirrored="true" android:height="24dp" android:viewportHeight="1232.1" android:viewportWidth="2516" android:width="49.00901dp">
<path android:fillColor="#FF000000" android:pathData="m740.2,0L23.5,0c-4.4,0 -8.7,1.6 -11.8,4.6 -3.1,2.9 -4.9,6.9 -4.9,11v670.6c0,4.1 1.8,8.1 4.9,11 3.1,2.9 7.4,4.6 11.8,4.6h716.7c4.4,0 8.7,-1.6 11.8,-4.6 3.1,-2.9 4.9,-6.9 4.9,-11L756.9,15.6c0,-4.1 -1.8,-8.1 -4.9,-11 -3.1,-2.9 -7.4,-4.6 -11.8,-4.6ZM506.9,452.3c0,4.1 -1.8,8.1 -4.9,11 -3.1,2.9 -7.4,4.6 -11.8,4.6h-216.7c-4.4,0 -8.7,-1.6 -11.8,-4.6 -3.1,-2.9 -4.9,-6.9 -4.9,-11v-202.8c0,-4.1 1.8,-8.1 4.9,-11 3.1,-2.9 7.4,-4.6 11.8,-4.6h216.7c4.4,0 8.7,1.6 11.8,4.6 3.1,2.9 4.9,6.9 4.9,11v202.8Z"/>
<path android:fillColor="#FF000000" android:pathData="m2240.6,234h-216.7c-9.2,0 -16.7,7 -16.7,15.6v202.8c0,8.6 7.5,15.6 16.7,15.6h216.7c9.2,0 16.7,-7 16.7,-15.6v-202.8c0,-8.6 -7.5,-15.6 -16.7,-15.6h0Z"/>
<path android:fillColor="#FF000000" android:pathData="m1990.7,0h-216.7c-9.2,0 -16.7,7 -16.7,15.6v202.8c0,8.6 7.5,15.6 16.7,15.6h216.7c9.2,0 16.7,-7 16.7,-15.6V15.6c0,-8.6 -7.5,-15.6 -16.7,-15.6Z"/>
<path android:fillColor="#FF000000" android:pathData="m2490.8,0h-216.7c-9.2,0 -16.7,7 -16.7,15.6v202.8c0,8.6 7.5,15.6 16.7,15.6h216.7c9.2,0 16.7,-7 16.7,-15.6V15.6c0,-8.6 -7.5,-15.6 -16.7,-15.6Z"/>
<path android:fillColor="#FF000000" android:pathData="m1990.7,467.9h-216.7c-9.2,0 -16.7,7 -16.7,15.6v202.8c0,8.6 7.5,15.6 16.7,15.6h216.7c9.2,0 16.7,-7 16.7,-15.6v-202.8c0,-8.6 -7.5,-15.6 -16.7,-15.6Z"/>
<path android:fillColor="#FF000000" android:pathData="m2490.8,467.9h-216.7c-9.2,0 -16.7,7 -16.7,15.6v202.8c0,8.6 7.5,15.6 16.7,15.6h216.7c9.2,0 16.7,-7 16.7,-15.6v-202.8c0,-8.6 -7.5,-15.6 -16.7,-15.6Z"/>
<path android:fillColor="#FF000000" android:pathData="m1615.3,0h-216.7c-9.2,0 -16.7,7 -16.7,15.6v202.8c0,8.6 7.5,15.6 16.7,15.6h216.7c9.2,0 16.7,-7 16.7,-15.6V15.6c0,-8.6 -7.5,-15.6 -16.7,-15.6Z"/>
<path android:fillColor="#FF000000" android:pathData="m1615.3,467.9h-216.7c-9.2,0 -16.7,7 -16.7,15.6v202.8c0,8.6 7.5,15.6 16.7,15.6h216.7c9.2,0 16.7,-7 16.7,-15.6v-202.8c0,-8.6 -7.5,-15.6 -16.7,-15.6Z"/>
<path android:fillColor="#FF000000" android:pathData="m1381.9,249.4c0,-4.1 -1.8,-8.1 -4.9,-11 -3.1,-2.9 -7.4,-4.6 -11.8,-4.6h-233.4V15.6c0,-4.1 -1.8,-8.1 -4.9,-11 -3.1,-2.9 -7.4,-4.6 -11.8,-4.6h-216.7c-4.4,0 -8.7,1.6 -11.8,4.6 -3.1,2.9 -4.9,6.9 -4.9,11v670.3c0,4.1 1.8,8.1 4.9,11 3.1,2.9 7.4,4.6 11.8,4.6h216.7c4.4,0 8.7,-1.6 11.8,-4.6 3.1,-2.9 4.9,-6.9 4.9,-11v-218.2h233.4c4.4,0 8.7,-1.6 11.8,-4.6 3.1,-2.9 4.9,-6.9 4.9,-11v-202.8Z"/>
<path android:fillColor="#FF000000" android:pathData="m1675.2,885c33.8,0 64.5,6.8 92,20.4 27.5,13.6 49.2,33.4 65.2,59.5 16,26.1 24,57.2 24,93.2s-8,67.2 -24,93.4c-16,26.3 -37.6,46.2 -65,60 -27.4,13.8 -58.1,20.6 -92.2,20.6h-125.2v-347.1h125.2ZM1666.2,1163.8c34.5,0 60.5,-9.2 78.3,-27.5 17.7,-18.3 26.6,-44.4 26.6,-78.3s-8.9,-59.4 -26.8,-77.5c-17.9,-18.2 -43.9,-27.3 -78,-27.3h-35.1v210.6h35.1Z"/>
<path android:fillColor="#FF000000" android:pathData="m2125.7,1092.2h-135.6v69.7h157v70.2h-238.1v-347.1h232.4v69.7h-151.3v68.3h135.6v69.2Z"/>
<path android:fillColor="#FF000000" android:pathData="m2509.8,885l-121.4,168.4 127.6,178.8h-100.1l-77.3,-112.9 -78.7,112.9h-92.9l127.6,-176.9 -121.4,-170.3h99.1l71.6,104.3 72.6,-104.3h93.4Z"/>
<path android:fillColor="#FF000000" android:pathData="M0,1033.9h1381.6v49h-1381.6z"/>
</vector>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

View file

@ -13,8 +13,6 @@ android {
dependencies {
implementation(deps.androidx.datastore)
implementation(deps.jodatime)
implementation(deps.hilt.android)
kapt(deps.hilt.kapt)

View file

@ -1,25 +1,17 @@
package com.tangem.data.promo
import com.tangem.data.promo.converters.PromoBannerConverter
import com.tangem.data.promo.converters.StoryContentResponseConverter
import com.tangem.datasource.api.common.response.getOrThrow
import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.datasource.local.preferences.PreferencesKeys
import com.tangem.datasource.local.preferences.PreferencesKeys.getShouldShowStoriesKey
import com.tangem.datasource.local.preferences.utils.get
import com.tangem.datasource.local.preferences.utils.getSyncOrDefault
import com.tangem.datasource.local.preferences.utils.store
import com.tangem.datasource.local.promo.PromoBannerStore
import com.tangem.datasource.local.promo.PromoStoriesStore
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.promo.PromoRepository
import com.tangem.domain.promo.models.PromoBanner
import com.tangem.domain.promo.models.PromoId
import com.tangem.domain.promo.models.StoryContent
import com.tangem.feature.referral.domain.ReferralRepository
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.coroutines.runCatching
import com.tangem.utils.coroutines.runSuspendCatching
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.withContext
@ -29,84 +21,10 @@ internal class DefaultPromoRepository(
private val tangemApi: TangemTechApi,
private val appPreferencesStore: AppPreferencesStore,
private val promoStoriesStore: PromoStoriesStore,
private val promoBannerStore: PromoBannerStore,
private val dispatchers: CoroutineDispatcherProvider,
private val referralRepository: ReferralRepository,
) : PromoRepository {
private val storyContentConverter = StoryContentResponseConverter()
private val promoBannerConverter = PromoBannerConverter()
override fun isReadyToShowWalletPromo(userWalletId: UserWalletId, promoId: PromoId): Flow<Boolean> {
return appPreferencesStore.get(
key = PreferencesKeys.getShouldShowPromoKey(promoId = promoId.name),
default = true,
)
.distinctUntilChanged()
.map { shouldShow ->
when (promoId) {
PromoId.Referral -> runSuspendCatching {
!referralRepository.isReferralParticipant(userWalletId) && shouldShow
}.getOrDefault(false)
PromoId.Sepa -> {
val isActive = getSepaPromoBanner()?.isActive == true
isActive && shouldShow
}
PromoId.VisaPresale -> {
val isActive = getVisaPromoBanner()?.isActive == true
isActive && shouldShow
}
PromoId.BlackFriday -> {
val isActive = getBlackFridayPromoBanner()?.isActive == true
isActive && shouldShow
}
PromoId.OnePlusOne -> {
val isActive = getOnePlusOnePromoBanner()?.isActive == true
isActive && shouldShow
}
PromoId.YieldPromo -> {
val isActive = getYieldPromoBanner(userWalletId)?.isActive == true
isActive && shouldShow
}
}
}
}
override fun isReadyToShowTokenPromo(promoId: PromoId): Flow<Boolean> {
return when (promoId) {
PromoId.Referral -> flowOf(false)
PromoId.Sepa -> flowOf(false)
PromoId.VisaPresale -> flowOf(false)
PromoId.BlackFriday -> flowOf(false)
PromoId.OnePlusOne -> flowOf(false)
PromoId.YieldPromo -> flowOf(false)
}
}
override suspend fun setNeverToShowWalletPromo(promoId: PromoId) {
appPreferencesStore.store(PreferencesKeys.getShouldShowPromoKey(promoId = promoId.name), false)
}
override suspend fun setNeverToShowTokenPromo(promoId: PromoId) {
appPreferencesStore.store(PreferencesKeys.getShouldShowPromoKey(promoId = promoId.name), false)
}
override suspend fun isMoonpayPromoActive(): Boolean {
val banner = runCatching(dispatchers.io) {
val response = promoBannerStore.getSyncOrNull(MOONPAY_NAME) ?: run {
val apiResponse = tangemApi.getPromoBanner(MOONPAY_NAME).getOrThrow()
promoBannerStore.store(MOONPAY_NAME, apiResponse)
apiResponse
}
promoBannerConverter.convert(response)
}.getOrNull()
return banner?.isActive == true
}
override fun getStoryById(id: String): Flow<StoryContent?> = isReadyToShowStories(id).mapLatest {
getStoryByIdSync(id = id, refresh = false)
@ -136,68 +54,21 @@ internal class DefaultPromoRepository(
}
override fun isReadyToShowStories(storyId: String): Flow<Boolean> {
return appPreferencesStore.get(getShouldShowStoriesKey(storyId), true)
return appPreferencesStore.get(PreferencesKeys.getShouldShowStoriesKey(storyId), true)
}
override suspend fun isReadyToShowStoriesSync(storyId: String): Boolean {
return appPreferencesStore.getSyncOrDefault(getShouldShowStoriesKey(storyId), true)
return appPreferencesStore.getSyncOrDefault(PreferencesKeys.getShouldShowStoriesKey(storyId), true)
}
override suspend fun setNeverToShowStories(storyId: String) {
appPreferencesStore.store(
key = getShouldShowStoriesKey(storyId),
key = PreferencesKeys.getShouldShowStoriesKey(storyId),
value = false,
)
}
private suspend fun getSepaPromoBanner(): PromoBanner? {
return runCatching(dispatchers.io) {
promoBannerConverter.convert(
tangemApi.getPromoBanner(SEPA_NAME).getOrThrow(),
)
}.getOrNull()
}
private suspend fun getVisaPromoBanner(): PromoBanner? {
return runCatching(dispatchers.io) {
promoBannerConverter.convert(
tangemApi.getPromoBanner(VISA_NAME).getOrThrow(),
)
}.getOrNull()
}
private suspend fun getBlackFridayPromoBanner(): PromoBanner? {
return runCatching(dispatchers.io) {
promoBannerConverter.convert(
tangemApi.getPromoBanner(BLACK_FRIDAY_NAME).getOrThrow(),
)
}.getOrNull()
}
private suspend fun getOnePlusOnePromoBanner(): PromoBanner? {
return runCatching(dispatchers.io) {
promoBannerConverter.convert(
tangemApi.getPromoBanner(ONE_PLUS_ONE_NAME).getOrThrow(),
)
}.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

@ -1,24 +0,0 @@
package com.tangem.data.promo.converters
import com.tangem.datasource.api.promotion.models.PromoBannerResponse
import com.tangem.domain.promo.models.PromoBanner
import com.tangem.utils.converter.Converter
import org.joda.time.DateTime
class PromoBannerConverter : Converter<PromoBannerResponse, PromoBanner?> {
override fun convert(value: PromoBannerResponse): PromoBanner? {
val bannerState = value.bannerState ?: return null
return PromoBanner(
name = value.name,
bannerState = PromoBanner.BannerState(
status = bannerState.status,
link = bannerState.link,
timeline = PromoBanner.Timeline(
start = DateTime.parse(bannerState.timeline.start),
end = DateTime.parse(bannerState.timeline.end),
),
),
)
}
}

View file

@ -3,10 +3,8 @@ package com.tangem.data.promo.di
import com.tangem.data.promo.DefaultPromoRepository
import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.datasource.local.promo.PromoBannerStore
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
@ -24,17 +22,13 @@ internal object PromoDataModule {
tangemTechApi: TangemTechApi,
appPreferencesStore: AppPreferencesStore,
promoStoriesStore: PromoStoriesStore,
promoBannerStore: PromoBannerStore,
dispatchers: CoroutineDispatcherProvider,
referralRepository: ReferralRepository,
): PromoRepository {
return DefaultPromoRepository(
tangemApi = tangemTechApi,
appPreferencesStore = appPreferencesStore,
promoStoriesStore = promoStoriesStore,
dispatchers = dispatchers,
referralRepository = referralRepository,
promoBannerStore = promoBannerStore,
)
}
}

View file

@ -6,5 +6,4 @@ enum class OnrampSource(val analyticsName: String) {
TOKEN_LONG_TAP("Long Tap"),
TOKEN_DETAILS("Token"),
MARKETS("Markets"),
SEPA_BANNER("SEPA Banner"),
}

View file

@ -11,11 +11,9 @@ import com.tangem.domain.onramp.repositories.OnrampRepository
import com.tangem.domain.onramp.repositories.OnrampTransactionRepository
import com.tangem.domain.onramp.utils.calculateRateDif
import com.tangem.domain.onramp.utils.compareOffersByRateSpeedAndPriority
import com.tangem.domain.promo.PromoRepository
import com.tangem.domain.settings.repositories.SettingsRepository
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map
class GetOnrampOffersUseCase(
@ -23,16 +21,14 @@ class GetOnrampOffersUseCase(
private val onrampTransactionRepository: OnrampTransactionRepository,
private val errorResolver: OnrampErrorResolver,
private val settingsRepository: SettingsRepository,
private val promoRepository: PromoRepository,
) {
operator fun invoke(): EitherFlow<OnrampError, List<OnrampOffersBlock>> {
return combine(
onrampRepository.getQuotes(),
onrampTransactionRepository.getAllTransactions(),
flow { emit(promoRepository.isMoonpayPromoActive()) },
) { quotes, transactions, isMoonpayPromoActive ->
processOffers(quotes, transactions, isMoonpayPromoActive)
) { quotes, transactions ->
processOffers(quotes, transactions)
}
.map { offers -> offers.right() }
.catch { throwable -> errorResolver.resolve(throwable).left() }
@ -41,7 +37,6 @@ class GetOnrampOffersUseCase(
private suspend fun processOffers(
quotes: List<OnrampQuote>,
transactions: List<OnrampTransaction>,
isMoonpayPromoActive: Boolean,
): List<OnrampOffersBlock> {
val validQuotes = quotes.filterIsInstance<OnrampQuote.Data>()
if (validQuotes.isEmpty()) return emptyList()
@ -62,7 +57,7 @@ class GetOnrampOffersUseCase(
val recentOffer = findRecentOffer(offers, transactions)
val bestRateOffer = findBestRateOffer(offers, isGooglePayAvailable)
val fastestOffer = findFastestOffer(offers, isGooglePayAvailable, isMoonpayPromoActive)
val fastestOffer = findFastestOffer(offers, isGooglePayAvailable)
return buildOffersBlocks(
recentOffer = recentOffer,
@ -90,23 +85,8 @@ class GetOnrampOffersUseCase(
return offers.maxWithOrNull(offerComparator(isGooglePayAvailable))
}
private fun findFastestOffer(
offers: List<OnrampOffer>,
isGooglePayAvailable: Boolean,
isMoonpayPromoActive: Boolean,
): OnrampOffer? {
val moonpayPromoOffers = if (isMoonpayPromoActive) {
offers.filter { offer ->
offer.quote.provider.id == MOONPAY_PROMO_PROVIDER_ID &&
offer.quote.paymentMethod.type == PaymentMethodType.GOOGLE_PAY
}
} else {
emptyList()
}
val instantOffers = moonpayPromoOffers.ifEmpty {
offers.filter { it.quote.paymentMethod.type.isInstant() }
}
private fun findFastestOffer(offers: List<OnrampOffer>, isGooglePayAvailable: Boolean): OnrampOffer? {
val instantOffers = offers.filter { it.quote.paymentMethod.type.isInstant() }
return if (instantOffers.isNotEmpty()) {
instantOffers.maxWithOrNull(fastestOfferComparator(isGooglePayAvailable))
@ -308,8 +288,4 @@ class GetOnrampOffersUseCase(
-> true
}
}
private companion object {
const val MOONPAY_PROMO_PROVIDER_ID = "moonpay"
}
}

View file

@ -7,7 +7,6 @@ import com.tangem.domain.onramp.model.cache.OnrampTransaction
import com.tangem.domain.onramp.repositories.OnrampErrorResolver
import com.tangem.domain.onramp.repositories.OnrampRepository
import com.tangem.domain.onramp.repositories.OnrampTransactionRepository
import com.tangem.domain.promo.PromoRepository
import com.tangem.domain.settings.repositories.SettingsRepository
import io.mockk.*
import kotlinx.coroutines.flow.flowOf
@ -25,7 +24,6 @@ class GetOnrampOffersUseCaseTest {
private val errorResolver: OnrampErrorResolver = mockk(relaxUnitFun = true)
private val settingsRepository: SettingsRepository = mockk(relaxUnitFun = true)
private val cryptoCurrencyId: CryptoCurrency.ID = mockk(relaxUnitFun = true)
private val promoRepository: PromoRepository = mockk(relaxUnitFun = true)
private lateinit var useCase: GetOnrampOffersUseCase
@ -37,7 +35,6 @@ class GetOnrampOffersUseCaseTest {
onrampTransactionRepository = onrampTransactionRepository,
errorResolver = errorResolver,
settingsRepository = settingsRepository,
promoRepository = promoRepository,
)
}
@ -228,7 +225,6 @@ class GetOnrampOffersUseCaseTest {
val transactions = emptyList<OnrampTransaction>()
coEvery { promoRepository.isMoonpayPromoActive() } returns false
coEvery { settingsRepository.isGooglePayAvailability() } returns false
coEvery { onrampRepository.getQuotes() } returns flowOf(quotes)
coEvery { onrampTransactionRepository.getAllTransactions() } returns flowOf(
@ -249,107 +245,6 @@ class GetOnrampOffersUseCaseTest {
}
}
@Test
fun `invoke should fallback to standard instant offers when promo is active but no Moonpay offers exist`() =
runTest {
val instantMethod = createMockPaymentMethod("gpay", "Google Pay", PaymentMethodType.GOOGLE_PAY)
val slowMethod = createMockPaymentMethod("bank", "Bank Transfer", PaymentMethodType.CARD)
val provider = createMockProvider("other", "Other Provider")
val quotes = listOf(
createMockQuote(instantMethod, provider, BigDecimal("95.0")),
createMockQuote(slowMethod, provider, BigDecimal("100.0")),
)
val transactions = emptyList<OnrampTransaction>()
coEvery { promoRepository.isMoonpayPromoActive() } returns true
coEvery { settingsRepository.isGooglePayAvailability() } returns true
coEvery { onrampRepository.getQuotes() } returns flowOf(quotes)
coEvery { onrampTransactionRepository.getAllTransactions() } returns flowOf(transactions)
val result = useCase()
result.collect { either ->
Truth.assertThat(either.isRight()).isTrue()
either.fold(
ifLeft = { error -> Truth.assertThat(error).isNull() },
ifRight = { offers ->
Truth.assertThat(offers).hasSize(1)
val recommendedBlock = offers.find { it.category == OnrampOfferCategory.Recommended }
Truth.assertThat(recommendedBlock).isNotNull()
Truth.assertThat(recommendedBlock?.offers).hasSize(2)
val fastestOffer =
recommendedBlock?.offers?.find { it.advantages == OnrampOfferAdvantages.Fastest }
Truth.assertThat(fastestOffer).isNotNull()
when (val quote = fastestOffer?.quote) {
is OnrampQuote.Data -> {
Truth.assertThat(quote.provider.id).isNotEqualTo("moonpay")
Truth.assertThat(quote.paymentMethod.type).isEqualTo(PaymentMethodType.GOOGLE_PAY)
}
else -> Truth.assertThat(false).isTrue()
}
},
)
}
}
@Test
fun `invoke should show Moonpay fastest offer when promo is active`() = runTest {
val moonpayGooglePayMethod = createMockPaymentMethod("moonpay-gpay", "Google Pay", PaymentMethodType.GOOGLE_PAY)
val otherGooglePayMethod = createMockPaymentMethod(
"other-gpay",
"Other Google Pay",
PaymentMethodType.GOOGLE_PAY,
)
val slowMethod = createMockPaymentMethod("bank", "Bank Transfer", PaymentMethodType.CARD)
val moonpayProvider = createMockProvider("moonpay", "Moonpay")
val otherProvider = createMockProvider("other", "Other Provider")
val quotes = listOf(
createMockQuote(moonpayGooglePayMethod, moonpayProvider, BigDecimal("100.0")),
createMockQuote(otherGooglePayMethod, otherProvider, BigDecimal("95.0")),
createMockQuote(slowMethod, otherProvider, BigDecimal("105.0")),
)
val transactions = emptyList<OnrampTransaction>()
coEvery { promoRepository.isMoonpayPromoActive() } returns true
coEvery { settingsRepository.isGooglePayAvailability() } returns true
coEvery { onrampRepository.getQuotes() } returns flowOf(quotes)
coEvery { onrampTransactionRepository.getAllTransactions() } returns flowOf(transactions)
val result = useCase()
result.collect { either ->
Truth.assertThat(either.isRight()).isTrue()
either.fold(
ifLeft = { error -> Truth.assertThat(error).isNull() },
ifRight = { offers ->
Truth.assertThat(offers).hasSize(1)
val recommendedBlock = offers.find { it.category == OnrampOfferCategory.Recommended }
Truth.assertThat(recommendedBlock).isNotNull()
val fastestOffer = recommendedBlock?.offers?.find { it.advantages == OnrampOfferAdvantages.Fastest }
Truth.assertThat(fastestOffer).isNotNull()
when (val quote = fastestOffer?.quote) {
is OnrampQuote.Data -> {
Truth.assertThat(quote.provider.id).isEqualTo("moonpay")
Truth.assertThat(quote.paymentMethod.type).isEqualTo(PaymentMethodType.GOOGLE_PAY)
Truth.assertThat(quote.toAmount.value).isEqualTo(BigDecimal("100.0"))
}
else -> Truth.assertThat(false).isTrue()
}
},
)
}
}
private fun createMockPaymentMethod(
id: String,
name: String,

View file

@ -5,5 +5,4 @@ plugins {
}
dependencies {
implementation(deps.jodatime)
}

View file

@ -1,35 +0,0 @@
package com.tangem.domain.promo.models
import org.joda.time.DateTime
data class PromoBanner(
val name: String,
val bannerState: BannerState,
) {
val isActive = bannerState.status == ACTIVE_STATUS && bannerState.timeline.end.isAfterNow
data class BannerState(
val timeline: Timeline,
val status: String,
val link: String?,
)
data class Timeline(
val start: DateTime,
val end: DateTime,
)
private companion object {
const val ACTIVE_STATUS = "active"
}
}
enum class PromoId {
Referral,
Sepa,
VisaPresale,
BlackFriday,
OnePlusOne,
YieldPromo,
}

View file

@ -1,24 +1,10 @@
package com.tangem.domain.promo
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.promo.models.PromoId
import com.tangem.domain.promo.models.StoryContent
import kotlinx.coroutines.flow.Flow
interface PromoRepository {
// region Promo
fun isReadyToShowWalletPromo(userWalletId: UserWalletId, promoId: PromoId): Flow<Boolean>
fun isReadyToShowTokenPromo(promoId: PromoId): Flow<Boolean>
suspend fun setNeverToShowWalletPromo(promoId: PromoId)
suspend fun setNeverToShowTokenPromo(promoId: PromoId)
suspend fun isMoonpayPromoActive(): Boolean
// endregion
// region Stories
fun getStoryById(id: String): Flow<StoryContent?>

View file

@ -1,11 +0,0 @@
package com.tangem.domain.promo
import com.tangem.domain.promo.models.PromoId
import kotlinx.coroutines.flow.Flow
class ShouldShowPromoTokenUseCase(private val promoRepository: PromoRepository) {
operator fun invoke(promoId: PromoId): Flow<Boolean> = promoRepository.isReadyToShowTokenPromo(promoId)
suspend fun neverToShow(promoId: PromoId) = promoRepository.setNeverToShowTokenPromo(promoId)
}

View file

@ -1,57 +0,0 @@
package com.tangem.domain.promo
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.promo.models.PromoId
import com.tangem.domain.settings.repositories.SettingsRepository
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emitAll
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import java.util.Calendar
class ShouldShowPromoWalletUseCase(
private val promoRepository: PromoRepository,
private val settingsRepository: SettingsRepository,
private val isNewPromoBannersEnabled: Boolean,
) {
operator fun invoke(userWalletId: UserWalletId, promoId: PromoId): Flow<Boolean> {
if (isNewPromoBannersEnabled) return flowOf(false)
return flow {
emit(false)
val promoFlow = promoRepository.isReadyToShowWalletPromo(userWalletId, promoId)
.map { applyWalletFirstUsageCondition(promoId, it) }
emitAll(promoFlow)
}
}
private suspend fun applyWalletFirstUsageCondition(promoId: PromoId, isReady: Boolean): Boolean {
if (!isReady) return false
return when (promoId) {
PromoId.Referral,
PromoId.VisaPresale,
PromoId.BlackFriday,
PromoId.OnePlusOne,
PromoId.YieldPromo,
-> true
PromoId.Sepa -> {
val walletFirstUsageDate = settingsRepository.getWalletFirstUsageDate()
if (walletFirstUsageDate == 0L) return false
val currentDate = Calendar.getInstance().timeInMillis
currentDate - walletFirstUsageDate > ONE_DAY_IN_MILLIS
}
}
}
suspend fun neverToShow(promoId: PromoId) = promoRepository.setNeverToShowWalletPromo(promoId)
private companion object {
const val ONE_DAY_IN_MILLIS = 1 * 24 * 60 * 60 * 1000L
}
}

View file

@ -1,65 +0,0 @@
package com.tangem.domain.tokens.model.analytics
import com.tangem.core.analytics.models.AnalyticsEvent
import com.tangem.core.analytics.models.AnalyticsParam
sealed class PromoAnalyticsEvent(
event: String,
params: Map<String, String> = emptyMap(),
) : AnalyticsEvent(category = "Promotion", event = event, params = params) {
data class NoticePromotionBanner(
private val source: AnalyticsParam.ScreensSources,
private val program: Program,
) : PromoAnalyticsEvent(
event = "Notice - Promotion Banner",
params = mapOf(
AnalyticsParam.SOURCE to source.value,
"Program Name" to program.programName,
),
)
data class PromotionBannerClicked(
private val source: AnalyticsParam.ScreensSources,
private val program: Program,
private val action: BannerAction,
) : PromoAnalyticsEvent(
event = "Promo Banner Clicked",
params = mapOf(
AnalyticsParam.SOURCE to source.value,
"Program Name" to program.programName,
"Action" to action.action,
),
) {
sealed class BannerAction(val action: String) {
class Clicked : BannerAction(action = "Clicked")
class Closed : BannerAction(action = "Closed")
}
}
// region visa waitlist promo
class VisaWaitlistPromo : PromoAnalyticsEvent(event = "Visa Waitlist")
class VisaWaitlistPromoJoin : PromoAnalyticsEvent(
event = "Button - Join Now",
params = mapOf(
"Program Name" to "Visa Waitlist",
),
)
class VisaWaitlistPromoDismiss : PromoAnalyticsEvent(
event = "Button - Close",
params = mapOf(
"Program Name" to "Visa Waitlist",
),
)
//endregion
// Use it on new promo action
enum class Program(val programName: String) {
Empty("Empty"),
Sepa("Sepa"),
BlackFriday("Black Friday"),
OnePlusOne("One-Plus-One"),
YieldPromo("Yield Promo"),
}
}

View file

@ -1,8 +1,6 @@
package com.tangem.domain.tokens.model.warnings
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.promo.models.PromoId
import org.joda.time.DateTime
import java.math.BigDecimal
sealed class CryptoCurrencyWarning {
@ -47,12 +45,6 @@ sealed class CryptoCurrencyWarning {
val cryptoCurrency: CryptoCurrency,
) : CryptoCurrencyWarning()
data class SwapPromo(
val promoId: PromoId,
val startDateTime: DateTime,
val endDateTime: DateTime,
) : CryptoCurrencyWarning()
data object BeaconChainShutdown : CryptoCurrencyWarning()
data object MigrationMaticToPol : CryptoCurrencyWarning()

View file

@ -18,7 +18,6 @@ import com.tangem.features.feed.components.market.list.DefaultMarketsTokenListCo
import com.tangem.features.feed.components.news.details.DefaultNewsDetailsComponent
import com.tangem.features.feed.components.news.list.DefaultNewsListComponent
import com.tangem.features.feed.components.search.DefaultSearchComponent
import com.tangem.features.promobanners.api.NewPromoBannersFeatureToggles
import com.tangem.features.promobanners.api.PromoBannersBlockComponent
import kotlinx.serialization.Serializable
import javax.inject.Inject
@ -30,7 +29,6 @@ internal class FeedEntryChildFactory @Inject constructor(
private val portfolioBlockComponentFactory: PortfolioBlockComponent.Factory,
private val addToPortfolioComponentFactory: AddToPortfolioComponent.Factory,
private val promoBannersBlockComponentFactory: PromoBannersBlockComponent.Factory,
private val newPromoBannersFeatureToggles: NewPromoBannersFeatureToggles,
private val designFeatureToggles: DesignFeatureToggles,
) {
@ -121,7 +119,6 @@ internal class FeedEntryChildFactory @Inject constructor(
params = FeedParams(feedClickIntents = feedEntryClickIntents),
addToPortfolioComponentFactory = addToPortfolioComponentFactory,
promoBannersBlockComponentFactory = promoBannersBlockComponentFactory,
newPromoBannersFeatureToggles = newPromoBannersFeatureToggles,
)
}
is Child.Earn -> {

View file

@ -24,7 +24,6 @@ import com.tangem.features.feed.model.feed.FeedComponentModel
import com.tangem.features.feed.model.feed.FeedModelClickIntents
import com.tangem.features.feed.ui.feed.FeedList
import com.tangem.features.feed.ui.feed.FeedListHeader
import com.tangem.features.promobanners.api.NewPromoBannersFeatureToggles
import com.tangem.features.promobanners.api.PromoBannersBlockComponent
internal class DefaultFeedComponent(
@ -32,13 +31,11 @@ internal class DefaultFeedComponent(
private val params: FeedParams,
private val addToPortfolioComponentFactory: AddToPortfolioComponent.Factory,
private val promoBannersBlockComponentFactory: PromoBannersBlockComponent.Factory,
private val newPromoBannersFeatureToggles: NewPromoBannersFeatureToggles,
) : ComposableModularBottomSheetContentComponent, AppComponentContext by appComponentContext {
private val feedComponentModel = getOrCreateModel<FeedComponentModel, FeedParams>(params = params)
private val promoBannersBlockComponent: PromoBannersBlockComponent? by lazy {
if (!newPromoBannersFeatureToggles.isNewPromoBannersEnabled) return@lazy null
private val promoBannersBlockComponent: PromoBannersBlockComponent by lazy {
promoBannersBlockComponentFactory.create(
context = child("promoBannersBlockComponent"),
params = PromoBannersBlockComponent.Params(
@ -72,7 +69,7 @@ internal class DefaultFeedComponent(
) {
val isExpanded = bottomSheetState.value == BottomSheetState.EXPANDED
LaunchedEffect(isExpanded) {
promoBannersBlockComponent?.setVisibleOnScreen(isExpanded)
promoBannersBlockComponent.setVisibleOnScreen(isExpanded)
}
LifecycleStartEffect(Unit) {

View file

@ -1,5 +0,0 @@
package com.tangem.features.promobanners.api
interface NewPromoBannersFeatureToggles {
val isNewPromoBannersEnabled: Boolean
}

View file

@ -4,13 +4,11 @@ import com.tangem.core.decompose.di.ModelComponent
import com.tangem.core.decompose.model.Model
import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.local.datastore.RuntimeSharedStore
import com.tangem.features.promobanners.api.NewPromoBannersFeatureToggles
import com.tangem.features.promobanners.api.PromoBannersBlockComponent
import com.tangem.features.promobanners.impl.DefaultPromoBannersBlockComponent
import com.tangem.features.promobanners.impl.model.PromoBannersBlockModel
import com.tangem.features.promobanners.impl.repository.DefaultPromoBannersRepository
import com.tangem.features.promobanners.impl.repository.PromoBannersRepository
import com.tangem.features.promobanners.impl.toggles.DefaultNewPromoBannersFeatureToggles
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.Binds
import dagger.Module
@ -31,10 +29,6 @@ internal interface PromoBannersFeatureModule {
factory: DefaultPromoBannersBlockComponent.Factory,
): PromoBannersBlockComponent.Factory
@Binds
@Singleton
fun bindFeatureToggles(impl: DefaultNewPromoBannersFeatureToggles): NewPromoBannersFeatureToggles
companion object {
@Provides

View file

@ -1,14 +0,0 @@
package com.tangem.features.promobanners.impl.toggles
import com.tangem.core.configtoggle.FeatureToggles
import com.tangem.core.configtoggle.feature.FeatureTogglesManager
import com.tangem.features.promobanners.api.NewPromoBannersFeatureToggles
import javax.inject.Inject
internal class DefaultNewPromoBannersFeatureToggles @Inject constructor(
private val featureTogglesManager: FeatureTogglesManager,
) : NewPromoBannersFeatureToggles {
override val isNewPromoBannersEnabled: Boolean
get() = featureTogglesManager.isFeatureEnabled(toggle = FeatureToggles.NEW_PROMO_BANNERS_ENABLED)
}

View file

@ -2,9 +2,7 @@ package com.tangem.feature.tokendetails.presentation.tokendetails.analytics
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.AnalyticsEvent
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.tokens.model.analytics.PromoAnalyticsEvent
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsState
import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.TokenDetailsNotification
import com.tangem.features.yield.supply.api.analytics.YieldSupplyAnalytics
@ -39,10 +37,6 @@ internal class TokenDetailsNotificationsAnalyticsSender(
currency = cryptoCurrency,
source = TokenDetailsAnalyticsEvent.Notice.NotEnoughFee.Source.DetailedScreen,
)
is TokenDetailsNotification.SwapPromo -> PromoAnalyticsEvent.NoticePromotionBanner(
program = PromoAnalyticsEvent.Program.Empty, // Use it on new promo action
source = AnalyticsParam.ScreensSources.Token,
)
is TokenDetailsNotification.KaspaIncompleteTransactionWarning -> TokenDetailsAnalyticsEvent.Notice.Reveal(
currency = cryptoCurrency,
)

View file

@ -3,7 +3,6 @@ package com.tangem.feature.tokendetails.presentation.tokendetails.model
import com.tangem.common.ui.bottomsheet.receive.AddressModel
import com.tangem.core.ui.extensions.TextReference
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.promo.models.PromoId
import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenBalanceSegmentedButtonConfig
@ -42,10 +41,6 @@ interface TokenDetailsClickIntents {
fun onCloseRentInfoNotification()
fun onSwapPromoDismiss(promoId: PromoId)
fun onSwapPromoClick(promoId: PromoId)
fun onGenerateExtendedKey()
fun onDynamicAddressesClick()
@ -143,10 +138,6 @@ internal class EmptyTokenDetailsClickIntents : TokenDetailsClickIntents {
override fun onCloseRentInfoNotification() { /* no op */ }
override fun onSwapPromoDismiss(promoId: PromoId) { /* no op */ }
override fun onSwapPromoClick(promoId: PromoId) { /* no op */ }
override fun onRetryIncompleteTransactionClick() { /* no op */ }
override fun onOpenTrustlineClick() { /* no op */ }

View file

@ -58,15 +58,12 @@ import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.offramp.GetOfframpUrlUseCase
import com.tangem.domain.onramp.model.OnrampSource
import com.tangem.domain.promo.ShouldShowPromoTokenUseCase
import com.tangem.domain.promo.models.PromoId
import com.tangem.domain.staking.GetStakingAvailabilityUseCase
import com.tangem.domain.staking.GetStakingEntryInfoUseCase
import com.tangem.domain.staking.model.StakingAvailability
import com.tangem.domain.tokens.*
import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason
import com.tangem.domain.tokens.model.TokenActionsState
import com.tangem.domain.tokens.model.analytics.PromoAnalyticsEvent
import com.tangem.domain.tokens.model.analytics.TokenReceiveCopyActionSource
import com.tangem.domain.tokens.model.analytics.TokenReceiveNewAnalyticsEvent
import com.tangem.domain.tokens.model.analytics.TokenScreenAnalyticsEvent
@ -136,7 +133,6 @@ internal class TokenDetailsModel @Inject constructor(
private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase,
private val getCurrencyWarningsUseCase: GetCurrencyWarningsUseCase,
private val getExplorerTransactionUrlUseCase: GetExplorerTransactionUrlUseCase,
private val shouldShowPromoTokenUseCase: ShouldShowPromoTokenUseCase,
private val getExtendedPublicKeyForCurrencyUseCase: GetExtendedPublicKeyForCurrencyUseCase,
private val getStakingEntryInfoUseCase: GetStakingEntryInfoUseCase,
private val getStakingAvailabilityUseCase: GetStakingAvailabilityUseCase,
@ -829,33 +825,6 @@ internal class TokenDetailsModel @Inject constructor(
uiState.value = stateFactory.getStateWithRemovedRentNotification()
}
override fun onSwapPromoDismiss(promoId: PromoId) {
modelScope.launch(dispatchers.main) {
shouldShowPromoTokenUseCase.neverToShow(promoId)
analyticsEventsHandler.send(
PromoAnalyticsEvent.PromotionBannerClicked(
source = AnalyticsParam.ScreensSources.Token,
program = PromoAnalyticsEvent.Program.Empty, // Use it on new promo action
action = PromoAnalyticsEvent.PromotionBannerClicked.BannerAction.Closed(),
),
)
}
}
override fun onSwapPromoClick(promoId: PromoId) {
modelScope.launch(dispatchers.main) {
shouldShowPromoTokenUseCase.neverToShow(promoId)
analyticsEventsHandler.send(
PromoAnalyticsEvent.PromotionBannerClicked(
source = AnalyticsParam.ScreensSources.Token,
program = PromoAnalyticsEvent.Program.Empty, // Use it on new promo action
action = PromoAnalyticsEvent.PromotionBannerClicked.BannerAction.Clicked(),
),
)
}
onSwapClick(ScenarioUnavailabilityReason.None)
}
override fun onCopyAddress(): TextReference? {
val networkAddress = cryptoCurrencyStatus?.value?.networkAddress ?: return null
val addresses = networkAddress.availableAddresses.mapToAddressModels(cryptoCurrency).toImmutableList()

View file

@ -13,7 +13,6 @@ import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.tokens.model.warnings.CryptoCurrencyWarning
import com.tangem.features.tokendetails.impl.R
import org.joda.time.DateTime
@Immutable
internal sealed class TokenDetailsNotification(val config: NotificationConfig) {
@ -47,24 +46,6 @@ internal sealed class TokenDetailsNotification(val config: NotificationConfig) {
),
)
data class SwapPromo(
val startDateTime: DateTime,
val endDateTime: DateTime,
val onSwapClick: () -> Unit,
val onCloseClick: () -> Unit,
) : TokenDetailsNotification(
config = NotificationConfig(
title = resourceReference(id = R.string.swap_promo_title),
subtitle = resourceReference(id = R.string.swap_promo_text),
iconResId = R.drawable.img_okx_dex_logo,
onCloseClick = onCloseClick,
buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig(
text = resourceReference(id = com.tangem.core.ui.R.string.token_swap_promotion_button),
onClick = onSwapClick,
),
),
)
data object NetworksUnreachable : Warning(
title = resourceReference(R.string.warning_network_unreachable_title),
subtitle = resourceReference(R.string.warning_network_unreachable_message),

View file

@ -108,12 +108,6 @@ internal class TokenDetailsNotificationConverter(
symbol = warning.amountCurrency.network.currencySymbol,
)
is CryptoCurrencyWarning.TopUpWithoutReserve -> TopUpWithoutReserve
is CryptoCurrencyWarning.SwapPromo -> SwapPromo(
startDateTime = warning.startDateTime,
endDateTime = warning.endDateTime,
onSwapClick = { clickIntents.onSwapPromoClick(warning.promoId) },
onCloseClick = { clickIntents.onSwapPromoDismiss(warning.promoId) },
)
is CryptoCurrencyWarning.BeaconChainShutdown -> NetworkShutdown(
title = resourceReference(R.string.warning_beacon_chain_retirement_title),
subtitle = resourceReference(R.string.warning_beacon_chain_retirement_content),

View file

@ -184,7 +184,6 @@ internal class UpdateNotificationsTransformer(
is CryptoCurrencyWarning.Rent,
is CryptoCurrencyWarning.SomeNetworksNoAccount,
is CryptoCurrencyWarning.TopUpWithoutReserve,
is CryptoCurrencyWarning.SwapPromo,
is CryptoCurrencyWarning.FeeResourceInfo,
is CryptoCurrencyWarning.UsedOutdatedDataWarning,
-> null

View file

@ -37,7 +37,6 @@ import com.tangem.feature.wallet.presentation.wallet.ui.components.visa.KycRejec
import com.tangem.feature.walletsettings.component.RenameWalletComponent
import com.tangem.features.biometry.AskBiometryComponent
import com.tangem.features.feed.entry.components.FeedEntryComponent
import com.tangem.features.promobanners.api.NewPromoBannersFeatureToggles
import com.tangem.features.promobanners.api.PromoBannersBlockComponent
import com.tangem.features.pushnotifications.api.PushNotificationsBottomSheetComponent
import com.tangem.features.pushnotifications.api.PushNotificationsParams
@ -63,7 +62,6 @@ internal class WalletComponent @AssistedInject constructor(
private val tokenReceiveComponentFactory: TokenReceiveComponent.Factory,
private val yieldSupplyDepositedWarningComponent: YieldSupplyDepositedWarningComponent.Factory,
private val promoBannersBlockComponentFactory: PromoBannersBlockComponent.Factory,
private val newPromoBannersFeatureToggles: NewPromoBannersFeatureToggles,
private val networkSelectionComponentFactory: NetworkSelectionComponent.Factory,
private val tokenActionsComponentFactory: TokenActionsComponent.Factory,
private val portfolioSelectorComponentFactory: PortfolioSelectorComponent.Factory,
@ -85,8 +83,7 @@ internal class WalletComponent @AssistedInject constructor(
)
}
private val promoBannersBlockComponent: PromoBannersBlockComponent? by lazy {
if (!newPromoBannersFeatureToggles.isNewPromoBannersEnabled) return@lazy null
private val promoBannersBlockComponent: PromoBannersBlockComponent by lazy {
promoBannersBlockComponentFactory.create(
context = child("promoBannersBlockComponent"),
params = PromoBannersBlockComponent.Params(

View file

@ -12,7 +12,6 @@ import com.tangem.core.analytics.models.event.AssetsDiscoveryAnalyticsEvent
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.ui.UiMessageSender
import com.tangem.core.navigation.review.ReviewManager
import com.tangem.core.navigation.url.UrlOpener
import com.tangem.domain.assetsdiscovery.usecase.AcknowledgeAssetsDiscoveryCompletionUseCase
import com.tangem.domain.card.SetCardWasScannedUseCase
import com.tangem.domain.common.wallets.UserWalletsListRepository
@ -29,17 +28,11 @@ import com.tangem.domain.models.wallet.requireColdWallet
import com.tangem.domain.networks.multi.MultiNetworkStatusFetcher
import com.tangem.domain.notifications.SetShouldShowNotificationUseCase
import com.tangem.domain.notifications.repository.NotificationsRepository
import com.tangem.domain.onramp.model.OnrampSource
import com.tangem.domain.promo.ShouldShowPromoWalletUseCase
import com.tangem.domain.promo.models.PromoId
import com.tangem.domain.quotes.multi.MultiQuoteStatusFetcher
import com.tangem.domain.settings.NeverToSuggestRateAppUseCase
import com.tangem.domain.settings.RemindToRateAppLaterUseCase
import com.tangem.domain.staking.StakingIdFactory
import com.tangem.domain.staking.multi.MultiStakingBalanceFetcher
import com.tangem.domain.tokens.model.analytics.PromoAnalyticsEvent
import com.tangem.domain.tokens.model.analytics.PromoAnalyticsEvent.Program
import com.tangem.domain.tokens.model.analytics.PromoAnalyticsEvent.PromotionBannerClicked
import com.tangem.domain.tokens.model.details.NavigationAction
import com.tangem.domain.wallets.usecase.*
import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent
@ -75,10 +68,6 @@ internal interface WalletWarningsClickIntents {
fun onCloseRateAppWarningClick()
fun onClosePromoClick(promoId: PromoId)
fun onPromoClick(promoId: PromoId, cryptoCurrency: CryptoCurrency? = null)
fun onSupportClick()
fun onBackupErrorClick()
@ -91,8 +80,6 @@ internal interface WalletWarningsClickIntents {
fun onFinishWalletActivationClick(isBackupExists: Boolean)
fun onYieldPromoTermsAndConditionsClick()
fun onUpgradeHotWalletClick(userWalletId: UserWalletId)
fun onCloseUpgradeBannerClick(userWalletId: UserWalletId)
@ -115,10 +102,8 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor(
private val nonBiometricUnlockWalletUseCase: NonBiometricUnlockWalletUseCase,
private val analyticsEventHandler: AnalyticsEventHandler,
private val dispatchers: CoroutineDispatcherProvider,
private val shouldShowPromoWalletUseCase: ShouldShowPromoWalletUseCase,
private val getWalletMetaInfoUseCase: GetWalletMetaInfoUseCase,
private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase,
private val urlOpener: UrlOpener,
private val multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
private val multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
private val multiStakingBalanceFetcher: MultiStakingBalanceFetcher,
@ -243,91 +228,6 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor(
}
}
override fun onClosePromoClick(promoId: PromoId) {
analyticsEventHandler.send(
when (promoId) {
PromoId.Referral -> MainScreen.ReferralPromoButtonDismiss()
PromoId.Sepa -> PromotionBannerClicked(
source = AnalyticsParam.ScreensSources.Main,
program = Program.Sepa,
action = PromotionBannerClicked.BannerAction.Closed(),
)
PromoId.VisaPresale -> PromoAnalyticsEvent.VisaWaitlistPromoDismiss()
PromoId.BlackFriday -> PromotionBannerClicked(
source = AnalyticsParam.ScreensSources.Main,
program = Program.BlackFriday,
action = PromotionBannerClicked.BannerAction.Closed(),
)
PromoId.OnePlusOne -> PromotionBannerClicked(
source = AnalyticsParam.ScreensSources.Main,
program = Program.OnePlusOne,
action = PromotionBannerClicked.BannerAction.Closed(),
)
PromoId.YieldPromo -> PromotionBannerClicked(
source = AnalyticsParam.ScreensSources.Main,
program = Program.YieldPromo,
action = PromotionBannerClicked.BannerAction.Closed(),
)
},
)
modelScope.launch(dispatchers.main) {
shouldShowPromoWalletUseCase.neverToShow(promoId)
}
}
override fun onPromoClick(promoId: PromoId, cryptoCurrency: CryptoCurrency?) {
val userWallet = getSelectedUserWallet() ?: return
when (promoId) {
PromoId.Referral -> {
analyticsEventHandler.send(MainScreen.ReferralPromoButtonParticipate())
appRouter.push(ReferralProgram(userWalletId = userWallet.walletId))
}
PromoId.Sepa -> {
analyticsEventHandler.send(
PromotionBannerClicked(
source = AnalyticsParam.ScreensSources.Main,
program = Program.Sepa,
action = PromotionBannerClicked.BannerAction.Clicked(),
),
)
cryptoCurrency ?: return
appRouter.push(
Onramp(
userWalletId = userWallet.walletId,
currency = cryptoCurrency,
source = OnrampSource.SEPA_BANNER,
shouldLaunchSepa = true,
),
)
}
PromoId.VisaPresale -> {
analyticsEventHandler.send(PromoAnalyticsEvent.VisaWaitlistPromoJoin())
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)
}
PromoId.OnePlusOne -> {
analyticsEventHandler.send(
PromotionBannerClicked(
source = AnalyticsParam.ScreensSources.Main,
program = Program.OnePlusOne,
action = PromotionBannerClicked.BannerAction.Clicked(),
),
)
urlOpener.openUrl(ONE_PLUS_ONE_PROMO_LINK)
}
PromoId.YieldPromo -> Unit // banner is not clickable, only terms and conditions button
}
}
override fun onSupportClick() {
val userWallet = getSelectedUserWallet() ?: return
@ -478,17 +378,6 @@ 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)
}
override fun onUpgradeHotWalletClick(userWalletId: UserWalletId) {
modelScope.launch(dispatchers.main) {
val userWallet = getUserWalletUseCase(userWalletId).getOrNull()
@ -519,21 +408,4 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor(
AccountId.forMainCryptoPortfolio(userWalletId),
)
}
private companion object {
const val VISA_PROMO_LINK = "https://tangem.com/en/cardwaitlist/?utm_source=tangem-app-banner" +
"&utm_medium=banner" +
"&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"
const val ONE_PLUS_ONE_PROMO_LINK = "https://tangem.com/pricing/" +
"?cat=family" +
"&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

@ -125,12 +125,6 @@ sealed class WalletScreenAnalyticsEvent {
if (blockchain != null) put("Blockchain", blockchain)
},
)
// region Referral Promo
class ReferralPromo : MainScreen(event = "Referral Banner")
class ReferralPromoButtonParticipate : MainScreen(event = "Button - Referral Participate")
class ReferralPromoButtonDismiss : MainScreen(event = "Button - Referral Dismiss")
//endregion
}
sealed class PushBannerPromo(

View file

@ -4,7 +4,6 @@ import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.AnalyticsEvent
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.domain.tokens.model.analytics.PromoAnalyticsEvent.*
import com.tangem.feature.wallet.child.wallet.model.WalletActivationBannerType
import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent.MainScreen
import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent.MainScreen.*
@ -69,28 +68,6 @@ internal class WalletWarningsAnalyticsSender @Inject constructor(
is WalletNotification.RateApp -> HowDoYouLikeTangem()
is WalletNotification.Critical.BackupError -> BackupError()
is WalletNotification.NoteMigration -> NotePromo()
is WalletNotification.SwapPromo -> NoticePromotionBanner(
source = AnalyticsParam.ScreensSources.Main,
program = Program.Empty, // Use it on new promo action
)
is WalletNotification.Sepa -> NoticePromotionBanner(
source = AnalyticsParam.ScreensSources.Main,
program = Program.Sepa,
)
is WalletNotification.BlackFridayPromo -> NoticePromotionBanner(
source = AnalyticsParam.ScreensSources.Main,
program = Program.BlackFriday,
)
is WalletNotification.OnePlusOnePromo -> NoticePromotionBanner(
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]
is WalletNotification.Informational.NoAccount,
is WalletNotification.Warning.LowSignatures,
@ -138,14 +115,6 @@ internal class WalletWarningsAnalyticsSender @Inject constructor(
is WalletNotificationUM.RateApp -> HowDoYouLikeTangem()
is WalletNotificationUM.BackupError -> BackupError()
is WalletNotificationUM.NoteMigration -> NotePromo()
is WalletNotificationUM.OnePlusOnePromo -> NoticePromotionBanner(
source = AnalyticsParam.ScreensSources.Main,
program = Program.OnePlusOne,
)
is WalletNotificationUM.YieldPromo -> NoticePromotionBanner(
source = AnalyticsParam.ScreensSources.Main,
program = Program.YieldPromo,
)
is WalletNotificationUM.FinishWalletActivation -> {
val activationState = if (notificationUM.isBackupExists) {
NoticeFinishActivation.ActivationState.Unfinished

View file

@ -24,8 +24,6 @@ import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.notifications.repository.NotificationsRepository
import com.tangem.domain.promo.ShouldShowPromoWalletUseCase
import com.tangem.domain.promo.models.PromoId
import com.tangem.domain.settings.IsReadyToShowRateAppUseCase
import com.tangem.domain.assetsdiscovery.model.AssetsDiscoveryProgress
import com.tangem.domain.assetsdiscovery.usecase.ObserveAssetsDiscoveryUseCase
@ -54,7 +52,6 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
private val isReadyToShowRateAppUseCase: IsReadyToShowRateAppUseCase,
private val isNeedToBackupUseCase: IsNeedToBackupUseCase,
private val backupValidator: BackupValidator,
private val shouldShowPromoWalletUseCase: ShouldShowPromoWalletUseCase,
private val notificationsRepository: NotificationsRepository,
private val accountDependencies: AccountDependencies,
private val getAccessCodeSkippedUseCase: GetAccessCodeSkippedUseCase,
@ -82,13 +79,9 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
accountStatusListFlow,
isReadyToShowRateAppUseCase().distinctUntilChanged(),
isNeedToBackupUseCase(userWallet.walletId).distinctUntilChanged(),
shouldShowPromoWalletUseCase(userWalletId = userWallet.walletId, promoId = PromoId.OnePlusOne)
.distinctUntilChanged(),
notificationsRepository.getShouldShowNotification(NotificationId.EnablePushesReminderNotification.key)
.distinctUntilChanged(),
getAccessCodeSkippedUseCase(userWallet.walletId).distinctUntilChanged(),
shouldShowPromoWalletUseCase(userWalletId = userWallet.walletId, promoId = PromoId.YieldPromo)
.distinctUntilChanged(),
shouldShowUpgradeHotWalletBannerUseCase.invoke(userWallet.walletId)
.distinctUntilChanged(),
getUpgradeBannerClosureTimestampUseCase(userWallet.walletId)
@ -99,13 +92,11 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
val accountStatusList = array[0] as AccountStatusList
val isReadyToShowRating = array[1] as Boolean
val isNeedToBackup = array[2] as Boolean
val shouldShowOnePlusOnePromo = array[3] as Boolean
val shouldShowEnablePushesReminderNotification = array[4] as Boolean
val shouldAccessCodeSkipped = array[5] as Boolean
val shouldShowYieldPromo = array[6] as Boolean
val shouldShowUpgradeBanner = array[7] as Boolean
val closureTimestamp = array[8] as? Long
val assetsDiscoveryProgress = array[9] as AssetsDiscoveryProgress
val shouldShowEnablePushesReminderNotification = array[3] as Boolean
val shouldAccessCodeSkipped = array[4] as Boolean
val shouldShowUpgradeBanner = array[5] as Boolean
val closureTimestamp = array[6] as? Long
val assetsDiscoveryProgress = array[7] as AssetsDiscoveryProgress
val flattenCurrencies = accountStatusList.flattenCurrencies()
val paymentAccountStatus = accountStatusList.accountStatuses
@ -132,10 +123,6 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
shouldAccessCodeSkipped = shouldAccessCodeSkipped,
)
addOnePlusOnePromoNotification(clickIntents, shouldShowOnePlusOnePromo)
addYieldPromoNotification(clickIntents, shouldShowYieldPromo)
addInformationalNotifications(
userWallet = userWallet,
cardTypesResolver = cardTypesResolver,
@ -296,32 +283,6 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
.map(CryptoCurrencyStatus::currency)
}
private fun MutableList<WalletNotification>.addOnePlusOnePromoNotification(
clickIntents: WalletClickIntents,
shouldShowPromo: Boolean,
) {
addIf(
element = WalletNotification.OnePlusOnePromo(
onCloseClick = { clickIntents.onClosePromoClick(promoId = PromoId.OnePlusOne) },
onClick = { clickIntents.onPromoClick(promoId = PromoId.OnePlusOne) },
),
condition = shouldShowPromo,
)
}
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

@ -5,10 +5,7 @@ import com.tangem.common.ui.notifications.NotificationId
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.domain.card.common.util.cardTypesResolver
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.isMultiCurrency
import com.tangem.domain.notifications.repository.NotificationsRepository
import com.tangem.domain.promo.ShouldShowPromoWalletUseCase
import com.tangem.domain.promo.models.PromoId
import com.tangem.domain.settings.IsReadyToShowRateAppUseCase
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
@ -29,32 +26,22 @@ import javax.inject.Inject
@ModelScoped
internal class GetWalletNotificationsCarouselFactory @Inject constructor(
private val isReadyToShowRateAppUseCase: IsReadyToShowRateAppUseCase,
private val shouldShowPromoWalletUseCase: ShouldShowPromoWalletUseCase,
private val getWalletsUseCase: GetWalletsUseCase,
private val notificationsRepository: NotificationsRepository,
) {
fun create(userWallet: UserWallet, clickIntents: WalletClickIntents): Flow<ImmutableList<WalletNotificationUM>> {
return combine(
flow = shouldShowPromoWalletUseCase(userWalletId = userWallet.walletId, promoId = PromoId.YieldPromo)
.distinctUntilChanged(),
flow2 = notificationsRepository.getShouldShowNotification(
flow = notificationsRepository.getShouldShowNotification(
NotificationId.EnablePushesReminderNotification.key,
).distinctUntilChanged(),
flow3 = shouldShowPromoWalletUseCase(userWalletId = userWallet.walletId, promoId = PromoId.OnePlusOne)
.distinctUntilChanged(),
flow4 = isReadyToShowRateAppUseCase().distinctUntilChanged(),
flow5 = getWalletsUseCase().conflate(),
) { showYieldPromo, showPushesNotification, showOnePlusOnePromo, showRateAppPromo, wallets ->
flow2 = isReadyToShowRateAppUseCase().distinctUntilChanged(),
flow3 = getWalletsUseCase().conflate(),
) { showPushesNotification, showRateAppPromo, wallets ->
buildList {
addNoteMigrationNotification(userWallet, wallets, clickIntents)
addRateAppNotification(showRateAppPromo, clickIntents)
if (userWallet.isMultiCurrency) {
addOnePlusOnePromoNotification(clickIntents, showOnePlusOnePromo)
addYieldPromoNotification(clickIntents, showYieldPromo)
}
addPushNotification(
shouldShow = showPushesNotification,
isPushesAllowed = notificationsRepository.isUserAllowToSubscribeOnPushNotifications(),
@ -77,30 +64,6 @@ internal class GetWalletNotificationsCarouselFactory @Inject constructor(
}
}
private fun MutableList<WalletNotificationUM>.addYieldPromoNotification(
clickIntents: WalletClickIntents,
shouldShowPromo: Boolean,
) {
addIf(shouldShowPromo) {
WalletNotificationUM.YieldPromo(
onCloseClick = { clickIntents.onClosePromoClick(promoId = PromoId.YieldPromo) },
onTermsAndConditionsClick = { clickIntents.onYieldPromoTermsAndConditionsClick() },
)
}
}
private fun MutableList<WalletNotificationUM>.addOnePlusOnePromoNotification(
clickIntents: WalletClickIntents,
shouldShowPromo: Boolean,
) {
addIf(shouldShowPromo) {
WalletNotificationUM.OnePlusOnePromo(
onCloseClick = { clickIntents.onClosePromoClick(promoId = PromoId.OnePlusOne) },
onClick = { clickIntents.onPromoClick(promoId = PromoId.OnePlusOne) },
)
}
}
private fun MutableList<WalletNotificationUM>.addNoteMigrationNotification(
userWallet: UserWallet,
userWallets: List<UserWallet>,

View file

@ -12,7 +12,6 @@ import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.feature.wallet.child.wallet.model.WalletActivationBannerType
import com.tangem.feature.wallet.impl.R
import org.joda.time.DateTime
/**
* Wallet notification component state
@ -229,19 +228,6 @@ sealed class WalletNotification(val config: NotificationConfig) {
),
)
data class SwapPromo(
val startDateTime: DateTime,
val endDateTime: DateTime,
val onCloseClick: () -> Unit,
) : WalletNotification(
config = NotificationConfig(
title = resourceReference(id = R.string.swap_promo_title),
subtitle = resourceReference(id = R.string.swap_promo_text),
iconResId = R.drawable.img_okx_dex_logo,
onCloseClick = onCloseClick,
),
)
data class NoteMigration(val onClick: () -> Unit) : WalletNotification(
config = NotificationConfig(
title = resourceReference(R.string.wallet_promo_banner_title),
@ -282,108 +268,6 @@ sealed class WalletNotification(val config: NotificationConfig) {
),
)
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,
),
iconSize = 54.dp,
),
)
data class VisaPresalePromo(
val onCloseClick: () -> Unit,
val onClick: () -> Unit,
) : WalletNotification(
config = NotificationConfig(
title = resourceReference(R.string.notification_visa_waitlist_promo_title),
subtitle = resourceReference(R.string.notification_visa_waitlist_promo_text),
iconResId = R.drawable.img_visa_waitlist_promo,
onCloseClick = onCloseClick,
buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig(
text = resourceReference(R.string.notification_referral_promo_button),
onClick = onClick,
),
iconSize = 54.dp,
),
)
data class Sepa(
val onCloseClick: () -> Unit,
val onClick: () -> Unit,
) : WalletNotification(
config = NotificationConfig(
title = resourceReference(R.string.notification_sepa_title),
subtitle = resourceReference(R.string.notification_sepa_text),
iconResId = R.drawable.img_notification_sepa,
onCloseClick = onCloseClick,
buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig(
text = resourceReference(R.string.notification_sepa_button),
onClick = onClick,
),
iconSize = 54.dp,
),
)
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 OnePlusOnePromo(
val onCloseClick: () -> Unit,
val onClick: () -> Unit,
) : WalletNotification(
config = NotificationConfig(
title = resourceReference(R.string.notification_one_plus_one_title),
subtitle = resourceReference(R.string.notification_one_plus_one_text),
iconResId = R.drawable.img_one_plus_one_promo,
onCloseClick = onCloseClick,
buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig(
text = resourceReference(R.string.notification_one_plus_one_button),
onClick = onClick,
),
iconSize = 54.dp,
),
)
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

@ -360,53 +360,6 @@ internal sealed class WalletNotificationUM(val messageUM: TangemMessageUM, val t
type = WalletNotificationType.Promo,
)
data class OnePlusOnePromo(
val onCloseClick: () -> Unit,
val onClick: () -> Unit,
) : WalletNotificationUM(
messageUM = TangemMessageUM(
id = "OnePlusOnePromoNotification",
title = resourceReference(R.string.notification_one_plus_one_title),
subtitle = resourceReference(R.string.notification_one_plus_one_text),
messageEffect = TangemMessageEffect.Magic,
iconUM = TangemIconUM.Image(R.drawable.img_one_plus_one_promo),
iconSize = 54.dp,
buttonsUM = persistentListOf(
TangemMessageButtonUM(
text = resourceReference(R.string.common_later),
type = TangemButtonType.PrimaryInverse,
onClick = onCloseClick,
),
TangemMessageButtonUM(
text = resourceReference(R.string.notification_one_plus_one_button),
type = TangemButtonType.Primary,
onClick = onClick,
),
),
),
type = WalletNotificationType.Promo,
)
data class YieldPromo(
val onCloseClick: () -> Unit,
val onTermsAndConditionsClick: () -> Unit,
) : WalletNotificationUM(
messageUM = TangemMessageUM(
id = "YieldPromoNotification",
title = resourceReference(R.string.notification_yield_promo_title),
subtitle = resourceReference(R.string.notification_yield_promo_text),
onCloseClick = onCloseClick,
messageEffect = TangemMessageEffect.Magic,
buttonsUM = persistentListOf(
TangemMessageButtonUM(
text = resourceReference(R.string.notification_yield_promo_button),
type = TangemButtonType.Primary,
onClick = onTermsAndConditionsClick,
),
),
),
type = WalletNotificationType.Promo,
)
// endregion
// region Survey

View file

@ -27,11 +27,7 @@ internal fun LazyListScope.notifications(configs: ImmutableList<WalletNotificati
key = { it::class.java },
contentType = { it::class.java },
itemContent = { item ->
// TODO develop promo banner general component
when (item) {
is WalletNotification.SwapPromo -> {
// Use it on new promo action
}
is WalletNotification.NoteMigration -> {
NoteMigrationNotification(
config = item.config,

View file

@ -1,15 +0,0 @@
<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>