diff --git a/features/feed/impl/build.gradle.kts b/features/feed/impl/build.gradle.kts index deccd0db2a..eeaa3157ac 100644 --- a/features/feed/impl/build.gradle.kts +++ b/features/feed/impl/build.gradle.kts @@ -23,6 +23,7 @@ dependencies { api(projects.features.forYou.api) api(projects.features.promoBanners.api) api(projects.features.tokenRecieve.api) + api(projects.features.marketing.api) /* Data */ implementation(projects.data.common) @@ -49,6 +50,7 @@ dependencies { implementation(projects.domain.core) implementation(projects.domain.feedback.models) implementation(projects.domain.tokens.models) + implementation(projects.domain.marketing.models) implementation(projects.domain.yieldSupply.models) runtimeOnly(projects.domain.manageTokens) runtimeOnly(projects.domain.offramp) diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/FeedEntryChildFactory.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/FeedEntryChildFactory.kt index 91e9077c31..fb1c45c47b 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/FeedEntryChildFactory.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/FeedEntryChildFactory.kt @@ -24,6 +24,7 @@ import com.tangem.features.feed.components.search.DefaultSearchComponent import com.tangem.features.feed.model.market.list.state.SortByTypeUM import com.tangem.features.foryou.ForYouComponent import com.tangem.features.foryou.TokenSummaryComponent +import com.tangem.features.marketing.api.MarketingBannerComponent import com.tangem.features.promobanners.api.PromoBannersBlockComponent import kotlinx.serialization.Serializable import javax.inject.Inject @@ -39,6 +40,7 @@ internal class FeedEntryChildFactory @Inject constructor( private val designFeatureToggles: DesignFeatureToggles, private val forYouComponentFactory: ForYouComponent.Factory, private val tokenSummaryComponentFactory: TokenSummaryComponent.Factory, + private val marketingBannerComponentFactory: MarketingBannerComponent.Factory, ) { @Serializable @@ -103,6 +105,7 @@ internal class FeedEntryChildFactory @Inject constructor( designFeatureToggles = designFeatureToggles, addToPortfolioComponentFactory = addToPortfolioComponentFactory, manageFundsComponentFactory = manageFundsComponentFactory, + marketingBannerComponentFactory = marketingBannerComponentFactory, ) } is Child.TokenList -> { diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/DefaultMarketsTokenDetailsComponent.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/DefaultMarketsTokenDetailsComponent.kt index efbc99093b..e49e5ee19c 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/DefaultMarketsTokenDetailsComponent.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/DefaultMarketsTokenDetailsComponent.kt @@ -37,6 +37,7 @@ import com.tangem.features.feed.model.market.details.analytics.MarketDetailsAnal import com.tangem.features.feed.model.market.details.state.TokenNetworksState import com.tangem.features.feed.ui.market.detailed.MarketsTokenDetailsContent import com.tangem.features.feed.ui.market.detailed.MarketsTokenDetailsTitle +import com.tangem.features.marketing.api.MarketingBannerComponent import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch import kotlinx.serialization.Serializable @@ -51,6 +52,7 @@ internal class DefaultMarketsTokenDetailsComponent( val params: Params, private val addToPortfolioComponentFactory: AddToPortfolioComponent.Factory, private val manageFundsComponentFactory: ManageFundsComponent.Factory, + private val marketingBannerComponentFactory: MarketingBannerComponent.Factory, ) : ComposableModularBottomSheetContentComponent, AppComponentContext by appComponentContext { // applying l2 compatibility @@ -62,6 +64,11 @@ internal class DefaultMarketsTokenDetailsComponent( private val analyticsParams = params.analyticsParams private val model: MarketsTokenDetailsModel = getOrCreateModel(updatedParams) + private val marketingBannerComponent = marketingBannerComponentFactory.create( + context = child("marketsTokenDetailsMarketingBanner"), + params = MarketingBannerComponent.Params.Standalone(requestFlow = model.marketingRequest), + ) + private val portfolioComponent: MarketsPortfolioComponent? = if (updatedParams.shouldShowPortfolio && !designFeatureToggles.isRedesignEnabled) { portfolioComponentFactory.create( @@ -214,6 +221,9 @@ internal class DefaultMarketsTokenDetailsComponent( component.Content(blockModifier) } }, + marketingBanner = { blockModifier -> + marketingBannerComponent.Content(blockModifier) + }, ) bottomSheet.child?.instance?.BottomSheet() addFundsBs.child?.instance?.BottomSheet() diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/market/details/MarketsTokenDetailsModel.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/market/details/MarketsTokenDetailsModel.kt index e79b45966a..a9006e4bdd 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/market/details/MarketsTokenDetailsModel.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/market/details/MarketsTokenDetailsModel.kt @@ -40,6 +40,7 @@ import com.tangem.domain.card.common.extensions.hotWalletExcludedBlockchains import com.tangem.domain.feedback.SendFeedbackEmailUseCase import com.tangem.domain.feedback.models.FeedbackEmailType import com.tangem.domain.markets.* +import com.tangem.domain.marketing.models.MarketingScreen import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.news.model.NewsListConfig import com.tangem.domain.news.usecase.GetNewsUseCase @@ -49,6 +50,7 @@ import com.tangem.domain.settings.usercountry.models.needApplyFCARestrictions import com.tangem.domain.wallets.usecase.GetWalletsUseCase import com.tangem.features.commonfeatures.api.addtoportfolio.AddToPortfolioManager import com.tangem.features.commonfeatures.api.tokenactions.BottomAction +import com.tangem.features.marketing.api.MarketingBannerRequest import com.tangem.features.feed.components.market.details.AddFundsSlotRoute import com.tangem.features.feed.components.market.details.AddToPortfolioSlotRoute import com.tangem.features.feed.components.market.details.DefaultMarketsTokenDetailsComponent @@ -234,6 +236,10 @@ internal class MarketsTokenDetailsModel @Inject constructor( val isVisibleOnScreen = MutableStateFlow(false) val networksState = MutableStateFlow(TokenNetworksState.Loading) + val marketingRequest: Flow = flowOf( + MarketingBannerRequest(screen = MarketingScreen.TokenMarkets(coingeckoId = params.token.id.value)), + ) + val addToPortfolioSheetNavigation = SlotNavigation() val addFundsSheetNavigation = SlotNavigation() diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/market/detailed/MarketsTokenDetailsContent.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/market/detailed/MarketsTokenDetailsContent.kt index d6f674062c..a041bf92a2 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/market/detailed/MarketsTokenDetailsContent.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/market/detailed/MarketsTokenDetailsContent.kt @@ -64,6 +64,7 @@ internal fun MarketsTokenDetailsContent( modifier: Modifier = Modifier, portfolioBlock: @Composable ((Modifier) -> Unit)?, portfolioFloatingBlock: @Composable ((Modifier) -> Unit)?, + marketingBanner: @Composable (Modifier) -> Unit, ) { Content( contentPadding = contentPadding, @@ -72,6 +73,7 @@ internal fun MarketsTokenDetailsContent( state = state, portfolioBlock = portfolioBlock, portfolioFloatingBlock = portfolioFloatingBlock, + marketingBanner = marketingBanner, ) when (state.bottomSheetConfig.content) { @@ -90,6 +92,7 @@ private fun Content( modifier: Modifier = Modifier, portfolioBlock: @Composable ((Modifier) -> Unit)?, portfolioFloatingBlock: @Composable ((Modifier) -> Unit)?, + marketingBanner: @Composable (Modifier) -> Unit, ) { val isRedesignEnabled = LocalRedesignEnabled.current val density = LocalDensity.current @@ -148,14 +151,13 @@ private fun Content( ) } item { SpacerH16() } - tokenMarketDetailsBody( state = state.body, portfolioBlock = portfolioBlock, relatedNews = state.relatedNews, isRedesignEnabled = isRedesignEnabled, + marketingBanner = marketingBanner, ) - item { SpacerH(bottomSpacing) } } } @@ -493,6 +495,7 @@ private fun MarketsTokenDetailsContent_Preview( backgroundColor = TangemTheme.colors.background.tertiary, portfolioBlock = {}, portfolioFloatingBlock = null, + marketingBanner = {}, contentPadding = PaddingValues(), ) } diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/market/detailed/components/TokenMarketDetailsBody.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/market/detailed/components/TokenMarketDetailsBody.kt index 20520a4398..121f46303b 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/market/detailed/components/TokenMarketDetailsBody.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/market/detailed/components/TokenMarketDetailsBody.kt @@ -28,11 +28,13 @@ internal fun LazyListScope.tokenMarketDetailsBody( state: MarketsTokenDetailsUM.Body, portfolioBlock: @Composable ((Modifier) -> Unit)?, relatedNews: RelatedNews, + marketingBanner: @Composable (Modifier) -> Unit, ) { if (isRedesignEnabled) { tokenMarketDetailsBodyV2( state = state, relatedNews = relatedNews, + marketingBanner = marketingBanner, ) } else { tokenMarketDetailsBodyV1( @@ -95,13 +97,28 @@ private fun LazyListScope.tokenMarketDetailsBodyV1( } } +private fun LazyListScope.marketingBannerItem(marketingBanner: @Composable (Modifier) -> Unit) { + item(key = "marketing_banner") { + marketingBanner( + Modifier + .padding(horizontal = 16.dp) + .padding(bottom = 8.dp) + .fillMaxWidth(), + ) + } +} + // Empty item with a key so that deeplink scroll-to-section can target it before the real content is composed private fun LazyListScope.sectionStub(key: String) { item(key) { } } @Suppress("CanBeNonNullable") -private fun LazyListScope.tokenMarketDetailsBodyV2(state: MarketsTokenDetailsUM.Body, relatedNews: RelatedNews) { +private fun LazyListScope.tokenMarketDetailsBodyV2( + state: MarketsTokenDetailsUM.Body, + relatedNews: RelatedNews, + marketingBanner: @Composable (Modifier) -> Unit, +) { when (state) { MarketsTokenDetailsUM.Body.Loading -> { item("description-loading") { @@ -115,6 +132,8 @@ private fun LazyListScope.tokenMarketDetailsBodyV2(state: MarketsTokenDetailsUM. description(state.description) } + marketingBannerItem(marketingBanner) + infoBlocksListV2( state = state.infoBlocks, relatedNews = relatedNews, @@ -169,7 +188,7 @@ private fun LazyListScope.description(description: MarketsTokenDetailsUM.Descrip modifier = { this .padding(horizontal = TangemTheme.dimens2.x4) - .padding(bottom = TangemTheme.dimens2.x8) + .padding(bottom = 24.dp) }, otherModifier = { blockPaddings() diff --git a/features/yield-supply/impl/build.gradle.kts b/features/yield-supply/impl/build.gradle.kts index 37aee32489..0737c02016 100644 --- a/features/yield-supply/impl/build.gradle.kts +++ b/features/yield-supply/impl/build.gradle.kts @@ -14,6 +14,7 @@ dependencies { /** Feature */ implementation(projects.features.yieldSupply.api) + implementation(projects.features.marketing.api) /** Core */ implementation(projects.core.configToggles) @@ -59,6 +60,7 @@ dependencies { implementation(projects.domain.feedback) implementation(projects.domain.balanceHiding.models) implementation(projects.domain.balanceHiding) + implementation(projects.domain.marketing.models) implementation(projects.libs.crypto) /** Compose */ diff --git a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/active/DefaultYieldSupplyActiveComponent.kt b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/active/DefaultYieldSupplyActiveComponent.kt index 28d83b8379..3b3694b8f8 100644 --- a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/active/DefaultYieldSupplyActiveComponent.kt +++ b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/active/DefaultYieldSupplyActiveComponent.kt @@ -23,6 +23,7 @@ import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.test.YieldSupplyTestTags import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.features.marketing.api.MarketingBannerComponent import com.tangem.features.yield.supply.api.YieldSupplyActiveComponent import com.tangem.features.yield.supply.impl.R import com.tangem.features.yield.supply.impl.active.model.YieldSupplyActiveModel @@ -40,6 +41,7 @@ internal class DefaultYieldSupplyActiveComponent @AssistedInject constructor( @Assisted private val appComponentContext: AppComponentContext, @Assisted private val params: YieldSupplyActiveComponent.Params, private val appRouter: AppRouter, + private val marketingBannerComponentFactory: MarketingBannerComponent.Factory, ) : YieldSupplyActiveComponent, AppComponentContext by appComponentContext { private val model: YieldSupplyActiveModel = getOrCreateModel(params = params) @@ -51,6 +53,14 @@ internal class DefaultYieldSupplyActiveComponent @AssistedInject constructor( ), ) + private val marketingBannerComponent = marketingBannerComponentFactory.create( + context = child("marketingBanner"), + params = MarketingBannerComponent.Params.Standalone( + requestFlow = model.marketingRequest, + onDeeplinkClick = model::onMarketingBannerDeeplink, + ), + ) + private val bottomSheetSlot = childSlot( key = "yieldSupplyActiveStack", source = model.slotNavigation, @@ -85,6 +95,7 @@ internal class DefaultYieldSupplyActiveComponent @AssistedInject constructor( state = state, isBalanceHidden = isBalanceHidden, chartComponent = chartComponent, + marketingBannerComponent = marketingBannerComponent, onReadMoreClick = model::onReadMoreClick, ) diff --git a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/active/model/YieldSupplyActiveModel.kt b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/active/model/YieldSupplyActiveModel.kt index c51653bd5b..83c1750529 100644 --- a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/active/model/YieldSupplyActiveModel.kt +++ b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/active/model/YieldSupplyActiveModel.kt @@ -28,10 +28,15 @@ import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.wallets.usecase.GetUserWalletUseCase import com.tangem.common.routing.AppRoute +import com.tangem.common.routing.deeplink.resolveMarketingDeeplink +import com.tangem.common.routing.deeplink.toContextualRoute +import com.tangem.core.analytics.models.AnalyticsParam +import com.tangem.domain.marketing.models.MarketingScreen import com.tangem.domain.stories.models.StoryContentIds import com.tangem.domain.yield.supply.models.YieldBoostStatus import com.tangem.domain.yield.supply.promo.usecase.GetYieldBoostStatusUseCase import com.tangem.domain.yield.supply.usecase.* +import com.tangem.features.marketing.api.MarketingBannerRequest import com.tangem.features.yield.supply.api.YieldSupplyActiveComponent import com.tangem.features.yield.supply.api.analytics.YieldSupplyAnalytics import com.tangem.features.yield.supply.impl.R @@ -96,6 +101,15 @@ internal class YieldSupplyActiveModel @Inject constructor( private val cryptoCurrency = cryptoCurrencyStatusFlow.value.currency private var appCurrency = AppCurrency.Default + val marketingRequest: Flow = flowOf( + MarketingBannerRequest( + screen = MarketingScreen.Yield( + networkId = params.cryptoCurrency.network.rawId, + contractAddress = (params.cryptoCurrency as? CryptoCurrency.Token)?.contractAddress.orEmpty(), + ), + ), + ) + val uiState: StateFlow field = MutableStateFlow( YieldSupplyActiveContentUM( @@ -145,6 +159,16 @@ internal class YieldSupplyActiveModel @Inject constructor( } } + fun onMarketingBannerDeeplink(deeplink: String): Boolean { + val route = resolveMarketingDeeplink(deeplink).toContextualRoute( + userWalletId = userWalletId, + currency = cryptoCurrency, + screenSource = AnalyticsParam.ScreensSources.Token, + ) ?: return false + appRouter.push(route) + return true + } + override fun onDismissClick() { if (!transactionInProgressFlow.value) { slotNavigation.dismiss() diff --git a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/active/ui/YieldSupplyActiveContent.kt b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/active/ui/YieldSupplyActiveContent.kt index def68cb652..daa1d00eed 100644 --- a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/active/ui/YieldSupplyActiveContent.kt +++ b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/active/ui/YieldSupplyActiveContent.kt @@ -40,6 +40,7 @@ import com.tangem.core.ui.decompose.ComposableContentComponent import com.tangem.core.ui.extensions.* import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview +import com.tangem.core.ui.res.TangemThemeRedesign import com.tangem.features.yield.supply.impl.R import com.tangem.features.yield.supply.impl.active.entity.YieldSupplyActiveContentUM import kotlinx.collections.immutable.persistentListOf @@ -51,6 +52,7 @@ internal fun YieldSupplyActiveContent( isBalanceHidden: Boolean, onReadMoreClick: () -> Unit, chartComponent: ComposableContentComponent, + marketingBannerComponent: ComposableContentComponent, modifier: Modifier = Modifier, ) { Column( @@ -88,6 +90,10 @@ internal fun YieldSupplyActiveContent( } } + TangemThemeRedesign { + marketingBannerComponent.Content(Modifier.fillMaxWidth()) + } + AnimatedVisibility(state.notifications.isNotEmpty()) { val notifications = remember(state.notifications) { state.notifications } Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { @@ -418,6 +424,7 @@ private fun YieldSupplyActiveBottomSheet_Preview( state = params, isBalanceHidden = true, chartComponent = ComposableContentComponent.EMPTY, + marketingBannerComponent = ComposableContentComponent.EMPTY, onReadMoreClick = {}, ) }