From aba2f43c348d169e713bbf1579d6b7adc18daa5b Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 15 Jul 2026 08:12:11 +0200 Subject: [PATCH] Updated on 2026-08-14 --- features/staking/impl/build.gradle.kts | 2 + .../staking/impl/DefaultStakingComponent.kt | 13 ++- .../impl/presentation/model/StakingModel.kt | 25 +++++- .../ui/StakingInitialInfoContent.kt | 12 +++ .../impl/presentation/ui/StakingScreen.kt | 11 ++- .../StakingModelMarketingDeeplinkTest.kt | 89 +++++++++++++++++++ features/tokendetails/impl/build.gradle.kts | 2 + .../DefaultTokenDetailsComponent.kt | 12 +++ .../tokendetails/model/TokenDetailsModel.kt | 39 ++++++++ .../tokendetails/ui/TokenDetailsScreen.kt | 11 +++ .../ui/TokenDetailsScreenLegacy.kt | 12 +++ 11 files changed, 224 insertions(+), 4 deletions(-) create mode 100644 features/staking/impl/src/test/kotlin/com/tangem/features/staking/impl/presentation/model/StakingModelMarketingDeeplinkTest.kt diff --git a/features/staking/impl/build.gradle.kts b/features/staking/impl/build.gradle.kts index 3613b3df3d..51b09ce693 100644 --- a/features/staking/impl/build.gradle.kts +++ b/features/staking/impl/build.gradle.kts @@ -68,6 +68,7 @@ dependencies { implementation(projects.domain.notifications.models) implementation(projects.domain.account) implementation(projects.domain.account.status) + implementation(projects.domain.marketing.models) /** Common */ implementation(projects.common.ui) @@ -80,6 +81,7 @@ dependencies { implementation(projects.features.staking.api) implementation(projects.features.txhistory.api) implementation(projects.features.approval.api) + implementation(projects.features.marketing.api) /** Decompose */ implementation(deps.decompose.ext.compose) diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/DefaultStakingComponent.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/DefaultStakingComponent.kt index f4f55c926f..d40c7091af 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/DefaultStakingComponent.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/DefaultStakingComponent.kt @@ -7,9 +7,11 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.arkivanov.decompose.extensions.compose.subscribeAsState import com.arkivanov.decompose.router.slot.childSlot import com.tangem.core.decompose.context.AppComponentContext +import com.tangem.core.decompose.context.child import com.tangem.core.decompose.context.childByContext import com.tangem.core.decompose.model.getOrCreateModel import com.tangem.features.approval.api.GiveApprovalComponent +import com.tangem.features.marketing.api.MarketingBannerComponent import com.tangem.features.staking.api.StakingComponent import com.tangem.features.staking.impl.presentation.model.StakingModel import com.tangem.features.staking.impl.presentation.ui.StakingScreen @@ -21,10 +23,19 @@ internal class DefaultStakingComponent @AssistedInject constructor( @Assisted appComponentContext: AppComponentContext, @Assisted params: StakingComponent.Params, private val giveApprovalComponentFactory: GiveApprovalComponent.Factory, + private val marketingBannerComponentFactory: MarketingBannerComponent.Factory, ) : StakingComponent, AppComponentContext by appComponentContext { private val model: StakingModel = getOrCreateModel(params) + private val marketingBannerComponent: MarketingBannerComponent = marketingBannerComponentFactory.create( + context = child("stakingMarketingBanner"), + params = MarketingBannerComponent.Params.Standalone( + requestFlow = model.marketingRequest, + onDeeplinkClick = model::onMarketingBannerDeeplink, + ), + ) + private val approvalSlot = childSlot( key = "stakingApprovalSlot", source = model.approvalSlotNavigation, @@ -45,7 +56,7 @@ internal class DefaultStakingComponent @AssistedInject constructor( val currentState by model.uiState.collectAsStateWithLifecycle() val approvalSlotState by approvalSlot.subscribeAsState() - StakingScreen(currentState) + StakingScreen(currentState, marketingBannerComponent) approvalSlotState.child?.instance?.BottomSheet() } diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/model/StakingModel.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/model/StakingModel.kt index 7074a6dfb0..dee5741fd8 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/model/StakingModel.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/model/StakingModel.kt @@ -10,6 +10,8 @@ import com.tangem.blockchain.common.transaction.Fee import com.tangem.common.TangemBlogUrlBuilder import com.tangem.common.getValidatorsCount import com.tangem.common.routing.AppRouter +import com.tangem.common.routing.deeplink.resolveMarketingDeeplink +import com.tangem.common.routing.deeplink.toContextualRoute import com.tangem.common.ui.amountScreen.converters.AmountReduceByTransformer.ReduceByData import com.tangem.common.ui.amountScreen.models.AmountState import com.tangem.common.ui.amountScreen.models.EnterAmountBoundary @@ -42,6 +44,7 @@ import com.tangem.domain.feedback.SaveBlockchainErrorUseCase import com.tangem.domain.feedback.SendFeedbackEmailUseCase import com.tangem.domain.feedback.models.BlockchainErrorInfo import com.tangem.domain.feedback.models.FeedbackEmailType +import com.tangem.domain.marketing.models.MarketingScreen import com.tangem.domain.models.account.Account import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrencyStatus @@ -67,6 +70,7 @@ import com.tangem.domain.transaction.usecase.SendTransactionUseCase import com.tangem.domain.utils.convertToSdkAmount import com.tangem.domain.wallets.usecase.GetUserWalletUseCase import com.tangem.features.approval.api.GiveApprovalComponent +import com.tangem.features.marketing.api.MarketingBannerRequest import com.tangem.features.staking.api.StakingComponent import com.tangem.features.staking.impl.R import com.tangem.features.staking.impl.analytics.StakingParamsInterceptor @@ -153,7 +157,7 @@ internal class StakingModel @Inject constructor( private val innerRouter: InnerStakingRouter, private val messageSender: UiMessageSender, private val stakingFeatureToggles: StakingFeatureToggles, - appRouter: AppRouter, + private val appRouter: AppRouter, ) : Model(), StakingClickIntents { val uiState: StateFlow = stateController.uiState @@ -163,6 +167,15 @@ internal class StakingModel @Inject constructor( private val params = paramsContainer.require() + val marketingRequest: Flow = flowOf( + MarketingBannerRequest( + screen = MarketingScreen.Staking( + networkId = params.cryptoCurrency.network.rawId, + contractAddress = (params.cryptoCurrency as? CryptoCurrency.Token)?.contractAddress.orEmpty(), + ), + ), + ) + private val stakingStateRouter: StakingStateRouter = StakingStateRouter( appRouter = appRouter, stateController = stateController, @@ -317,6 +330,16 @@ internal class StakingModel @Inject constructor( stateController.initializeWithUserWallet(userWallet) } + fun onMarketingBannerDeeplink(deeplink: String): Boolean { + val route = resolveMarketingDeeplink(deeplink).toContextualRoute( + userWalletId = params.userWalletId, + currency = params.cryptoCurrency, + screenSource = AnalyticsParam.ScreensSources.Staking, + ) ?: return false + appRouter.push(route) + return true + } + override fun onDestroy() { super.onDestroy() paramsInterceptorHolder.removeParamsInterceptor(StakingParamsInterceptor.ID) diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingInitialInfoContent.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingInitialInfoContent.kt index 66cb40cb89..d967eabfa8 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingInitialInfoContent.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingInitialInfoContent.kt @@ -33,6 +33,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.unit.Density +import androidx.compose.ui.unit.dp import com.tangem.common.ui.navigationButtons.NavigationButtonsState import com.tangem.common.ui.navigationButtons.NavigationPrimaryButton import com.tangem.core.ui.components.SpacerH12 @@ -51,6 +52,7 @@ import com.tangem.core.ui.test.StakingDetailsScreenTestTags import com.tangem.domain.models.staking.BalanceType import com.tangem.domain.models.staking.RewardBlockType import com.tangem.domain.staking.model.common.RewardType +import com.tangem.features.marketing.api.MarketingBannerComponent import com.tangem.features.staking.impl.R import com.tangem.features.staking.impl.presentation.model.StakingClickIntents import com.tangem.features.staking.impl.presentation.state.BalanceState @@ -67,6 +69,7 @@ private const val BANNER_BLOCK_KEY = "BannerBlock" private const val STAKING_REWARD_BLOCK_KEY = "StakingRewardBlock" private const val ACTIVE_STAKING_BLOCK_KEY = "ActiveStakingBlock" private const val STAKE_PRIMARY_BUTTON_KEY = "StakePrimaryButton" +private const val MARKETING_BANNER_BLOCK_KEY = "MarketingBannerBlock" @Composable internal fun StakingInitialInfoContent( @@ -74,6 +77,7 @@ internal fun StakingInitialInfoContent( buttonState: NavigationButtonsState, clickIntents: StakingClickIntents, isBalanceHidden: Boolean, + marketingBannerComponent: MarketingBannerComponent, ) { if (state !is StakingStates.InitialInfoState.Data) return @@ -104,6 +108,10 @@ internal fun StakingInitialInfoContent( hideEndText = isBalanceHidden, ) + item(key = MARKETING_BANNER_BLOCK_KEY) { + marketingBannerComponent.Content(Modifier.fillMaxWidth().padding(bottom = 12.dp)) + } + activeStakingBlock( state = state, clickIntents = clickIntents, @@ -491,6 +499,10 @@ private fun StakingInitialInfoContent_Preview( buttonState = NavigationButtonsState.Empty, clickIntents = StakingClickIntentsStub, isBalanceHidden = false, + marketingBannerComponent = object : MarketingBannerComponent { + @Composable + override fun Content(modifier: Modifier) = Unit + }, ) } } diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingScreen.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingScreen.kt index 52778ebcf3..078cc6fca3 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingScreen.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingScreen.kt @@ -19,6 +19,7 @@ import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.test.SendScreenTestTags +import com.tangem.features.marketing.api.MarketingBannerComponent import com.tangem.features.staking.impl.R import com.tangem.features.staking.impl.presentation.state.StakingStates import com.tangem.features.staking.impl.presentation.state.StakingStep @@ -35,7 +36,7 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.withIndex @Composable -internal fun StakingScreen(uiState: StakingUiState) { +internal fun StakingScreen(uiState: StakingUiState, marketingBannerComponent: MarketingBannerComponent) { val confirmationState = uiState.confirmationState as? StakingStates.ConfirmationState.Data BackHandler(onBack = uiState.clickIntents::onPrevClick) @@ -53,6 +54,7 @@ internal fun StakingScreen(uiState: StakingUiState) { ) StakingScreenContent( uiState = uiState, + marketingBannerComponent = marketingBannerComponent, modifier = Modifier.weight(1f), ) NavigationButtonsBlock( @@ -104,7 +106,11 @@ private fun StakingAppBar(uiState: StakingUiState) { @Suppress("LongMethod") @Composable -private fun StakingScreenContent(uiState: StakingUiState, modifier: Modifier = Modifier) { +private fun StakingScreenContent( + uiState: StakingUiState, + marketingBannerComponent: MarketingBannerComponent, + modifier: Modifier = Modifier, +) { val currentScreen = uiState.currentStep var currentStateProxy by remember { mutableStateOf(currentScreen) } var isTransitionAnimationRunning by remember { mutableStateOf(false) } @@ -149,6 +155,7 @@ private fun StakingScreenContent(uiState: StakingUiState, modifier: Modifier = M buttonState = uiState.buttonsState, clickIntents = uiState.clickIntents, isBalanceHidden = uiState.isBalanceHidden, + marketingBannerComponent = marketingBannerComponent, ) StakingStep.RewardsValidators -> { StakingClaimRewardsValidatorContent( diff --git a/features/staking/impl/src/test/kotlin/com/tangem/features/staking/impl/presentation/model/StakingModelMarketingDeeplinkTest.kt b/features/staking/impl/src/test/kotlin/com/tangem/features/staking/impl/presentation/model/StakingModelMarketingDeeplinkTest.kt new file mode 100644 index 0000000000..2d4f6ec276 --- /dev/null +++ b/features/staking/impl/src/test/kotlin/com/tangem/features/staking/impl/presentation/model/StakingModelMarketingDeeplinkTest.kt @@ -0,0 +1,89 @@ +package com.tangem.features.staking.impl.presentation.model + +import com.google.common.truth.Truth.assertThat +import com.tangem.common.routing.AppRoute +import com.tangem.core.analytics.models.AnalyticsParam +import com.tangem.domain.onramp.model.OnrampSource +import io.mockk.Runs +import io.mockk.every +import io.mockk.just +import io.mockk.verify +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.advanceUntilIdle +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.Test + +@OptIn(ExperimentalCoroutinesApi::class) +internal class StakingModelMarketingDeeplinkTest : StakingModelTestBase() { + + @Test + fun `GIVEN swap deeplink WHEN onMarketingBannerDeeplink THEN pushes Swap for current token`() = runTest { + // Arrange + every { appRouter.push(any(), any()) } just Runs + val model = createModel(testScope = this) + advanceUntilIdle() + + // Act + val handled = model.onMarketingBannerDeeplink("tangem://swap") + + // Assert + assertThat(handled).isTrue() + verify { + appRouter.push( + match { + it is AppRoute.Swap && + it.userWalletId == testUserWalletId && + it.fromCryptoCurrency == testCryptoCurrency && + it.screenSource == AnalyticsParam.ScreensSources.Staking.value + }, + any(), + ) + } + + model.onDestroy() + } + + @Test + fun `GIVEN buy deeplink WHEN onMarketingBannerDeeplink THEN pushes Onramp for current token`() = runTest { + // Arrange + every { appRouter.push(any(), any()) } just Runs + val model = createModel(testScope = this) + advanceUntilIdle() + + // Act + val handled = model.onMarketingBannerDeeplink("tangem://buy") + + // Assert + assertThat(handled).isTrue() + verify { + appRouter.push( + match { + it is AppRoute.Onramp && + it.userWalletId == testUserWalletId && + it.currency == testCryptoCurrency && + it.source == OnrampSource.MARKETING_BANNER + }, + any(), + ) + } + + model.onDestroy() + } + + @Test + fun `GIVEN external deeplink WHEN onMarketingBannerDeeplink THEN not handled and no navigation`() = runTest { + // Arrange + every { appRouter.push(any(), any()) } just Runs + val model = createModel(testScope = this) + advanceUntilIdle() + + // Act + val handled = model.onMarketingBannerDeeplink("https://tangem.com/promo") + + // Assert + assertThat(handled).isFalse() + verify(exactly = 0) { appRouter.push(any(), any()) } + + model.onDestroy() + } +} \ No newline at end of file diff --git a/features/tokendetails/impl/build.gradle.kts b/features/tokendetails/impl/build.gradle.kts index a2bcd8d884..12796f51d1 100644 --- a/features/tokendetails/impl/build.gradle.kts +++ b/features/tokendetails/impl/build.gradle.kts @@ -70,6 +70,7 @@ dependencies { implementation(projects.domain.dynamicAddresses.models) implementation(projects.domain.feedback) implementation(projects.domain.markets.models) + implementation(projects.domain.marketing.models) implementation(projects.domain.models) implementation(projects.domain.notifications.models) implementation(projects.domain.offramp) @@ -101,6 +102,7 @@ dependencies { implementation(projects.features.wallet.api) implementation(projects.features.staking.api) implementation(projects.features.markets.api) + implementation(projects.features.marketing.api) implementation(projects.features.onramp.api) implementation(projects.features.pushNotifications.api) implementation(projects.features.swap.api) diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/DefaultTokenDetailsComponent.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/DefaultTokenDetailsComponent.kt index b8a431f274..6b28aba27f 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/DefaultTokenDetailsComponent.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/DefaultTokenDetailsComponent.kt @@ -27,6 +27,7 @@ import com.tangem.feature.tokendetails.presentation.tokendetails.ui.bottomsheet. import com.tangem.feature.tokendetails.presentation.tokendetails.ui.bottomsheet.DynamicAddressesBottomSheetComponent import com.tangem.feature.tokendetails.presentation.tokendetails.ui.bottomsheet.TransferBottomSheetComponent import com.tangem.features.commonfeatures.api.managefunds.ManageFundsComponent +import com.tangem.features.marketing.api.MarketingBannerComponent import com.tangem.features.markets.token.block.TokenMarketBlockComponent import com.tangem.features.rating.RatingComponent import com.tangem.features.tokendetails.ExpressTransactionsComponent @@ -54,6 +55,7 @@ internal class DefaultTokenDetailsComponent @AssistedInject constructor( private val manageFundsComponentFactory: ManageFundsComponent.Factory, yieldSupplyComponentFactory: YieldSupplyComponent.Factory, private val ratingComponentFactory: RatingComponent.Factory, + marketingBannerComponentFactory: MarketingBannerComponent.Factory, ) : TokenDetailsComponent, AppComponentContext by appComponentContext { private val model: TokenDetailsModel = getOrCreateModel(params) @@ -136,6 +138,14 @@ internal class DefaultTokenDetailsComponent @AssistedInject constructor( ), ) + private val marketingBannerComponent = marketingBannerComponentFactory.create( + context = child("tokenDetailsMarketingBanner"), + params = MarketingBannerComponent.Params.Standalone( + requestFlow = model.marketingRequest, + onDeeplinkClick = model::onMarketingBannerDeeplink, + ), + ) + @Composable override fun Content(modifier: Modifier) { val bottomSheet by bottomSheetSlot.subscribeAsState() @@ -153,6 +163,7 @@ internal class DefaultTokenDetailsComponent @AssistedInject constructor( txHistoryComponent = txHistoryComponent, expressTransactionsComponent = expressTransactionsComponent, ratingComponent = ratingSlotState.child?.instance, + marketingBannerComponent = marketingBannerComponent, modifier = modifier, ) } else { @@ -164,6 +175,7 @@ internal class DefaultTokenDetailsComponent @AssistedInject constructor( yieldSupplyComponent = yieldSupplyComponent, expressTransactionsComponent = expressTransactionsComponent, ratingComponent = ratingSlotState.child?.instance, + marketingBannerComponent = marketingBannerComponent, ) } diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/model/TokenDetailsModel.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/model/TokenDetailsModel.kt index 72736bf01e..e44ae5445f 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/model/TokenDetailsModel.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/model/TokenDetailsModel.kt @@ -12,8 +12,11 @@ import com.tangem.common.extensions.hexToBytes import com.tangem.common.extensions.toHexString import com.tangem.common.routing.AppRoute import com.tangem.common.routing.AppRouter +import com.tangem.common.routing.deeplink.MarketingDeeplink +import com.tangem.common.routing.deeplink.resolveMarketingDeeplink import com.tangem.common.ui.bottomsheet.receive.AddressModel import com.tangem.common.ui.bottomsheet.receive.mapToAddressModels +import com.tangem.features.marketing.api.MarketingBannerRequest import com.tangem.features.rating.RatingComponent import com.tangem.feature.swap.domain.SwapFeedbackUseCase import com.tangem.feature.swap.domain.models.domain.SwapFeedbackParams @@ -58,6 +61,7 @@ import com.tangem.domain.dynamicaddresses.IsXpubSupportedUseCase import com.tangem.domain.dynamicaddresses.repository.DynamicAddressesRepository import com.tangem.domain.feedback.SendBackupProblemEmailUseCase import com.tangem.domain.models.StatusSource +import com.tangem.domain.marketing.models.MarketingScreen import com.tangem.domain.models.TokenReceiveNotification import com.tangem.domain.models.account.Account import com.tangem.domain.models.currency.CryptoCurrency @@ -192,6 +196,16 @@ internal class TokenDetailsModel @Inject constructor( private val userWalletId: UserWalletId = params.userWalletId private val cryptoCurrency: CryptoCurrency = params.currency + /** Token details context is static (single currency) — no amount filter. */ + val marketingRequest: Flow = flowOf( + MarketingBannerRequest( + screen = MarketingScreen.TokenDetails( + networkId = cryptoCurrency.network.rawId, + contractAddress = (cryptoCurrency as? CryptoCurrency.Token)?.contractAddress.orEmpty(), + ), + ), + ) + private val userWallet: UserWallet = getUserWalletUseCase(userWalletId).getOrNull() ?: error("UserWallet not found") @@ -208,6 +222,7 @@ internal class TokenDetailsModel @Inject constructor( private var cryptoCurrencyStatus: CryptoCurrencyStatus? = null private var account: Account.CryptoPortfolio? = null private var isBalanceLoadedEventSent = false + private var latestTokenActions: List = emptyList() val bottomSheetNavigation: SlotNavigation = SlotNavigation() val ratingSlotNavigation = SlotNavigation() @@ -346,6 +361,7 @@ internal class TokenDetailsModel @Inject constructor( .conflate() .distinctUntilChanged() .onEach { state -> + latestTokenActions = state.states sendButtonsEvents(state.states) uiState.value = stateFactory.getManageButtonsState(actions = state.states) if (designFeatureToggles.isRedesignEnabled) { @@ -818,6 +834,29 @@ internal class TokenDetailsModel @Inject constructor( handleSwap(unavailabilityReason, AppRoute.Swap.CurrencyPosition.ANY, checkYieldSupply = true) } + /** + * Routes a tapped marketing-banner deeplink contextually for the current token. Reuses the regular + * swap/buy intents (availability checks, yield-supply warning, analytics). Returns `false` for + * external links so the banner falls back to the generic deeplink launcher. + */ + fun onMarketingBannerDeeplink(deeplink: String): Boolean = when (resolveMarketingDeeplink(deeplink)) { + MarketingDeeplink.SWAP -> { + val reason = latestTokenActions + .filterIsInstance() + .firstOrNull()?.unavailabilityReason ?: ScenarioUnavailabilityReason.None + onSwapClick(reason) + true + } + MarketingDeeplink.BUY -> { + val reason = latestTokenActions + .filterIsInstance() + .firstOrNull()?.unavailabilityReason ?: ScenarioUnavailabilityReason.None + onBuyClick(reason) + true + } + MarketingDeeplink.EXTERNAL -> false + } + override fun onSwapFromClick(unavailabilityReason: ScenarioUnavailabilityReason) { handleSwap(unavailabilityReason, AppRoute.Swap.CurrencyPosition.FROM, checkYieldSupply = true) } diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/TokenDetailsScreen.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/TokenDetailsScreen.kt index 5fb601334f..38d0061d02 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/TokenDetailsScreen.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/TokenDetailsScreen.kt @@ -46,6 +46,7 @@ import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDeta import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.TokenDetailsBalanceBlock import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.ZeroBalanceActionsBlock import com.tangem.features.markets.token.block.TokenMarketBlockComponent +import com.tangem.features.marketing.api.MarketingBannerComponent import com.tangem.features.rating.RatingComponent import com.tangem.features.tokendetails.ExpressTransactionsComponent import com.tangem.features.txhistory.component.TxHistoryComponent @@ -73,6 +74,7 @@ internal fun TokenDetailsScreen( txHistoryComponent: TxHistoryComponent, expressTransactionsComponent: ExpressTransactionsComponent, ratingComponent: RatingComponent?, + marketingBannerComponent: MarketingBannerComponent, modifier: Modifier = Modifier, ) { val expressState by expressTransactionsComponent.state.collectAsStateWithLifecycle() @@ -100,6 +102,7 @@ internal fun TokenDetailsScreen( txHistoryComponent = txHistoryComponent, expressTransactionsComponent = expressTransactionsComponent, expressTransactionsToDisplay = expressState.transactionsToDisplay, + marketingBannerComponent = marketingBannerComponent, rootBackground = rootBackground, topContentPadding = topBarTotalHeight, bottomContentPadding = effectiveBottomPadding, @@ -161,6 +164,7 @@ private fun TokenDetailsBody( txHistoryComponent: TxHistoryComponent, expressTransactionsComponent: ExpressTransactionsComponent, expressTransactionsToDisplay: PersistentList, + marketingBannerComponent: MarketingBannerComponent, rootBackground: Color, topContentPadding: Dp, bottomContentPadding: Dp, @@ -230,6 +234,9 @@ private fun TokenDetailsBody( ) } } + item(key = "marketing_banner_block") { + marketingBannerComponent.Content(modifier = itemModifier.padding(vertical = 8.dp)) + } if (balance is TokenDetailsBalanceBlockUM.Content && balance.isBalanceZero) { item(key = "zero_balance_actions") { ZeroBalanceActionsBlock( @@ -317,6 +324,10 @@ private fun TokenDetailsScreen_Preview() { }, expressTransactionsComponent = PreviewExpressTransactionsComponent, ratingComponent = null, + marketingBannerComponent = object : MarketingBannerComponent { + @Composable + override fun Content(modifier: Modifier) = Unit + }, ) } } diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/TokenDetailsScreenLegacy.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/TokenDetailsScreenLegacy.kt index 4dbe6900a9..8c4a831d23 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/TokenDetailsScreenLegacy.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/TokenDetailsScreenLegacy.kt @@ -34,6 +34,7 @@ import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.T import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.TokenInfoBlock import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.staking.TokenStakingBlockLegacy import com.tangem.features.markets.token.block.TokenMarketBlockComponent +import com.tangem.features.marketing.api.MarketingBannerComponent import com.tangem.features.tokendetails.ExpressTransactionsComponent import com.tangem.features.txhistory.component.TxHistoryComponent import com.tangem.features.txhistory.entity.TxHistoryItemsUM @@ -54,6 +55,7 @@ internal fun TokenDetailsScreenLegacy( yieldSupplyComponent: YieldSupplyComponent, expressTransactionsComponent: ExpressTransactionsComponent, ratingComponent: RatingComponent?, + marketingBannerComponent: MarketingBannerComponent, ) { val bottomBarHeight = with(LocalDensity.current) { WindowInsets.systemBars.getBottom(this).toDp() } @@ -154,6 +156,12 @@ internal fun TokenDetailsScreenLegacy( yieldSupplyComponent.Content(modifier = itemModifier) } + item(key = "marketing_banner_block") { + TangemThemeRedesign { + marketingBannerComponent.Content(modifier = itemModifier) + } + } + with(expressTransactionsComponent) { expressTransactionsContentLegacy( state = expressState.transactionsToDisplay, @@ -215,6 +223,10 @@ private fun TokenDetailsScreenPreview( }, expressTransactionsComponent = PreviewExpressTransactionsComponent, ratingComponent = null, + marketingBannerComponent = object : MarketingBannerComponent { + @Composable + override fun Content(modifier: Modifier) = Unit + }, ) } }