diff --git a/core/ui/src/main/res/drawable/ic_yield_32.xml b/core/ui/src/main/res/drawable/ic_yield_32.xml
new file mode 100644
index 0000000000..e3ed6f4694
--- /dev/null
+++ b/core/ui/src/main/res/drawable/ic_yield_32.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/WalletWarningsAnalyticsSender.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/WalletWarningsAnalyticsSender.kt
index 0b097d38b8..8c1eaa8cdf 100644
--- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/WalletWarningsAnalyticsSender.kt
+++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/WalletWarningsAnalyticsSender.kt
@@ -143,6 +143,7 @@ internal class WalletWarningsAnalyticsSender @Inject constructor(
is WalletNotificationUM.CloreMigration,
is WalletNotificationUM.TangemPayRefreshNeeded,
WalletNotificationUM.TangemPayUnreachable,
+ is WalletNotificationUM.YieldBoostPromo,
-> null
}
}
diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetWalletNotificationsCarouselFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetWalletNotificationsCarouselFactory.kt
index 05fde4c1ac..ac71135be8 100644
--- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetWalletNotificationsCarouselFactory.kt
+++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetWalletNotificationsCarouselFactory.kt
@@ -8,8 +8,11 @@ import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.notifications.repository.NotificationsRepository
import com.tangem.domain.settings.IsReadyToShowRateAppUseCase
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
+import com.tangem.domain.yield.supply.promo.usecase.ShouldShowYieldBoostMainBannerUseCase
+import com.tangem.domain.yield.supply.usecase.YieldSupplyGetShouldShowMainPromoUseCase
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNotificationUM
+import com.tangem.features.yield.supply.api.YieldSupplyFeatureToggles
import com.tangem.utils.extensions.addIf
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
@@ -28,6 +31,9 @@ internal class GetWalletNotificationsCarouselFactory @Inject constructor(
private val isReadyToShowRateAppUseCase: IsReadyToShowRateAppUseCase,
private val getWalletsUseCase: GetWalletsUseCase,
private val notificationsRepository: NotificationsRepository,
+ private val shouldShowYieldBoostMainBannerUseCase: ShouldShowYieldBoostMainBannerUseCase,
+ private val yieldSupplyGetShouldShowMainPromoUseCase: YieldSupplyGetShouldShowMainPromoUseCase,
+ private val yieldSupplyFeatureToggles: YieldSupplyFeatureToggles,
) {
fun create(userWallet: UserWallet, clickIntents: WalletClickIntents): Flow> {
return combine(
@@ -36,7 +42,8 @@ internal class GetWalletNotificationsCarouselFactory @Inject constructor(
).distinctUntilChanged(),
flow2 = isReadyToShowRateAppUseCase().distinctUntilChanged(),
flow3 = getWalletsUseCase().conflate(),
- ) { showPushesNotification, showRateAppPromo, wallets ->
+ flow4 = yieldSupplyGetShouldShowMainPromoUseCase().distinctUntilChanged(),
+ ) { showPushesNotification, showRateAppPromo, wallets, shouldShowYieldPromoLocal ->
buildList {
addNoteMigrationNotification(userWallet, wallets, clickIntents)
@@ -47,10 +54,33 @@ internal class GetWalletNotificationsCarouselFactory @Inject constructor(
isPushesAllowed = notificationsRepository.isUserAllowToSubscribeOnPushNotifications(),
clickIntents = clickIntents,
)
+
+ addYieldBoostBannerNotification(
+ userWallet = userWallet,
+ shouldShowLocal = shouldShowYieldPromoLocal,
+ clickIntents = clickIntents,
+ )
}.sortedBy { it.type.ordinal }.toImmutableList()
}
}
+ private suspend fun MutableList.addYieldBoostBannerNotification(
+ userWallet: UserWallet,
+ shouldShowLocal: Boolean,
+ clickIntents: WalletClickIntents,
+ ) {
+ if (!shouldShowLocal) return
+ if (!yieldSupplyFeatureToggles.isYieldPromoEnabled) return
+ val shouldShow = shouldShowYieldBoostMainBannerUseCase(userWallet.walletId).getOrNull() == true
+ if (!shouldShow) return
+ add(
+ WalletNotificationUM.YieldBoostPromo(
+ onExploreClick = { clickIntents.onYieldBoostBannerClick(userWallet.walletId) },
+ onLaterClick = { clickIntents.onDismissYieldBoostBanner(userWallet.walletId) },
+ ),
+ )
+ }
+
private fun MutableList.addRateAppNotification(
isReadyToShowRating: Boolean,
clickIntents: WalletClickIntents,
diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletNotificationUM.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletNotificationUM.kt
index c366fb05ac..3ae343814d 100644
--- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletNotificationUM.kt
+++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletNotificationUM.kt
@@ -8,8 +8,10 @@ import com.tangem.core.ui.ds.message.TangemMessageButtonUM
import com.tangem.core.ui.ds.message.TangemMessageEffect
import com.tangem.core.ui.ds.message.TangemMessageUM
import com.tangem.core.ui.extensions.TextReference
+import com.tangem.core.ui.extensions.combinedReference
import com.tangem.core.ui.extensions.pluralReference
import com.tangem.core.ui.extensions.resourceReference
+import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.core.ui.res.TangemTheme
import com.tangem.feature.wallet.impl.R
@@ -412,6 +414,36 @@ internal sealed class WalletNotificationUM(val messageUM: TangemMessageUM, val t
type = WalletNotificationType.Promo,
)
+ data class YieldBoostPromo(
+ val onExploreClick: () -> Unit,
+ val onLaterClick: () -> Unit,
+ ) : WalletNotificationUM(
+ messageUM = TangemMessageUM(
+ id = "YieldBoostPromoNotification",
+ title = combinedReference(
+ resourceReference(CoreResR.string.yield_apy_boost_banner_title),
+ stringReference(" ยท "),
+ resourceReference(CoreResR.string.yield_apy_boost_banner_title_apy_multiplied),
+ ),
+ subtitle = resourceReference(CoreResR.string.yield_apy_boost_banner_subtitle),
+ iconUM = TangemIconUM.Image(imageRes = CoreUiR.drawable.ic_yield_32),
+ messageEffect = TangemMessageEffect.Magic,
+ buttonsUM = persistentListOf(
+ TangemMessageButtonUM(
+ text = resourceReference(CoreResR.string.common_later),
+ type = TangemButtonType.Secondary,
+ onClick = onLaterClick,
+ ),
+ TangemMessageButtonUM(
+ text = resourceReference(CoreResR.string.yield_apy_boost_banner_button_title),
+ type = TangemButtonType.Primary,
+ onClick = onExploreClick,
+ ),
+ ),
+ ),
+ type = WalletNotificationType.Promo,
+ )
+
// endregion
// region Survey
diff --git a/features/wallet/impl/src/test/kotlin/com/tangem/feature/wallet/presentation/wallet/domain/GetWalletNotificationsCarouselFactoryTest.kt b/features/wallet/impl/src/test/kotlin/com/tangem/feature/wallet/presentation/wallet/domain/GetWalletNotificationsCarouselFactoryTest.kt
new file mode 100644
index 0000000000..fb3b3d3ad7
--- /dev/null
+++ b/features/wallet/impl/src/test/kotlin/com/tangem/feature/wallet/presentation/wallet/domain/GetWalletNotificationsCarouselFactoryTest.kt
@@ -0,0 +1,127 @@
+package com.tangem.feature.wallet.presentation.wallet.domain
+
+import arrow.core.Either
+import com.google.common.truth.Truth.assertThat
+import com.tangem.domain.models.wallet.UserWallet
+import com.tangem.domain.models.wallet.UserWalletId
+import com.tangem.domain.notifications.repository.NotificationsRepository
+import com.tangem.domain.settings.IsReadyToShowRateAppUseCase
+import com.tangem.domain.wallets.usecase.GetWalletsUseCase
+import com.tangem.domain.yield.supply.promo.usecase.ShouldShowYieldBoostMainBannerUseCase
+import com.tangem.domain.yield.supply.usecase.YieldSupplyGetShouldShowMainPromoUseCase
+import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
+import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNotificationUM
+import com.tangem.features.yield.supply.api.YieldSupplyFeatureToggles
+import io.mockk.clearMocks
+import io.mockk.coEvery
+import io.mockk.every
+import io.mockk.mockk
+import io.mockk.verify
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.runTest
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.TestInstance
+import org.junit.jupiter.params.ParameterizedTest
+import org.junit.jupiter.params.provider.MethodSource
+
+@TestInstance(TestInstance.Lifecycle.PER_CLASS)
+internal class GetWalletNotificationsCarouselFactoryTest {
+
+ private val isReadyToShowRateAppUseCase: IsReadyToShowRateAppUseCase = mockk()
+ private val getWalletsUseCase: GetWalletsUseCase = mockk()
+ private val notificationsRepository: NotificationsRepository = mockk()
+ private val shouldShowYieldBoostMainBannerUseCase: ShouldShowYieldBoostMainBannerUseCase = mockk()
+ private val yieldSupplyGetShouldShowMainPromoUseCase: YieldSupplyGetShouldShowMainPromoUseCase = mockk()
+ private val yieldSupplyFeatureToggles: YieldSupplyFeatureToggles = mockk()
+ private val clickIntents: WalletClickIntents = mockk(relaxed = true)
+ private val userWallet: UserWallet.Hot = mockk(relaxed = true)
+
+ private val factory = GetWalletNotificationsCarouselFactory(
+ isReadyToShowRateAppUseCase = isReadyToShowRateAppUseCase,
+ getWalletsUseCase = getWalletsUseCase,
+ notificationsRepository = notificationsRepository,
+ shouldShowYieldBoostMainBannerUseCase = shouldShowYieldBoostMainBannerUseCase,
+ yieldSupplyGetShouldShowMainPromoUseCase = yieldSupplyGetShouldShowMainPromoUseCase,
+ yieldSupplyFeatureToggles = yieldSupplyFeatureToggles,
+ )
+
+ @BeforeEach
+ fun setup() {
+ clearMocks(
+ isReadyToShowRateAppUseCase,
+ getWalletsUseCase,
+ notificationsRepository,
+ shouldShowYieldBoostMainBannerUseCase,
+ yieldSupplyGetShouldShowMainPromoUseCase,
+ yieldSupplyFeatureToggles,
+ clickIntents,
+ userWallet,
+ )
+ // Defaults: only the yield boost banner can appear; every other notification is suppressed.
+ every { userWallet.walletId } returns WALLET_ID
+ every { notificationsRepository.getShouldShowNotification(any()) } returns flowOf(false)
+ coEvery { notificationsRepository.isUserAllowToSubscribeOnPushNotifications() } returns true
+ every { isReadyToShowRateAppUseCase() } returns flowOf(false)
+ every { getWalletsUseCase() } returns flowOf(emptyList())
+ every { yieldSupplyGetShouldShowMainPromoUseCase() } returns flowOf(true)
+ every { yieldSupplyFeatureToggles.isYieldPromoEnabled } returns true
+ coEvery { shouldShowYieldBoostMainBannerUseCase(any()) } returns Either.Right(true)
+ }
+
+ @ParameterizedTest
+ @MethodSource("provideTestModels")
+ fun `GIVEN gating conditions WHEN create THEN yield boost banner visibility matches`(model: Model) = runTest {
+ // Arrange
+ every { yieldSupplyFeatureToggles.isYieldPromoEnabled } returns model.toggleEnabled
+ every { yieldSupplyGetShouldShowMainPromoUseCase() } returns flowOf(model.shouldShowLocal)
+ coEvery { shouldShowYieldBoostMainBannerUseCase(WALLET_ID) } returns model.mainBanner
+
+ // Act
+ val result = factory.create(userWallet, clickIntents).first()
+
+ // Assert
+ assertThat(result.any { it is WalletNotificationUM.YieldBoostPromo }).isEqualTo(model.expectedShown)
+ }
+
+ @Test
+ fun `GIVEN banner shown WHEN buttons clicked THEN routes to click intents`() = runTest {
+ // Arrange
+ val banner = factory.create(userWallet, clickIntents).first()
+ .filterIsInstance()
+ .first()
+
+ // Act
+ banner.onExploreClick()
+ banner.onLaterClick()
+
+ // Assert
+ verify { clickIntents.onYieldBoostBannerClick(WALLET_ID) }
+ verify { clickIntents.onDismissYieldBoostBanner(WALLET_ID) }
+ }
+
+ internal data class Model(
+ val toggleEnabled: Boolean,
+ val shouldShowLocal: Boolean,
+ val mainBanner: Either,
+ val expectedShown: Boolean,
+ )
+
+ private fun provideTestModels() = listOf(
+ Model(toggleEnabled = true, shouldShowLocal = true, mainBanner = Either.Right(true), expectedShown = true),
+ Model(toggleEnabled = false, shouldShowLocal = true, mainBanner = Either.Right(true), expectedShown = false),
+ Model(toggleEnabled = true, shouldShowLocal = false, mainBanner = Either.Right(true), expectedShown = false),
+ Model(toggleEnabled = true, shouldShowLocal = true, mainBanner = Either.Right(false), expectedShown = false),
+ Model(
+ toggleEnabled = true,
+ shouldShowLocal = true,
+ mainBanner = Either.Left(RuntimeException("boom")),
+ expectedShown = false,
+ ),
+ )
+
+ private companion object {
+ val WALLET_ID = UserWalletId("01")
+ }
+}
\ No newline at end of file