From 304e6d0c5c9f2e6146e0903c89ac14814fbd985e Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 15 Apr 2026 11:21:53 +0300 Subject: [PATCH] Updated on 2026-08-14 --- .../components/feed/DefaultFeedComponent.kt | 7 +++ .../api/PromoBannersBlockComponent.kt | 3 ++ .../impl/DefaultPromoBannersBlockComponent.kt | 4 ++ .../impl/model/PromoBannersBlockModel.kt | 9 +++- .../impl/model/PromoBannersBlockUM.kt | 1 + .../promobanners/impl/ui/PromoBannersBlock.kt | 44 ++++++++----------- 6 files changed, 41 insertions(+), 27 deletions(-) diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/feed/DefaultFeedComponent.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/feed/DefaultFeedComponent.kt index 4f06480af4..a374e0faee 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/feed/DefaultFeedComponent.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/feed/DefaultFeedComponent.kt @@ -2,6 +2,7 @@ package com.tangem.features.feed.components.feed import androidx.compose.foundation.layout.PaddingValues import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.State import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier @@ -44,6 +45,7 @@ internal class DefaultFeedComponent( context = child("promoBannersBlockComponent"), params = PromoBannersBlockComponent.Params( placeholder = PromoBannersBlockComponent.Placeholder.FEED, + isInitiallyVisibleOnScreen = false, ), ) } @@ -70,6 +72,11 @@ internal class DefaultFeedComponent( contentPadding: PaddingValues, modifier: Modifier, ) { + val isExpanded = bottomSheetState.value == BottomSheetState.EXPANDED + LaunchedEffect(isExpanded) { + promoBannersBlockComponent?.setVisibleOnScreen(isExpanded) + } + LifecycleStartEffect(Unit) { feedComponentModel.isVisibleOnScreen.value = true onStopOrDispose { diff --git a/features/promo-banners/api/src/main/kotlin/com/tangem/features/promobanners/api/PromoBannersBlockComponent.kt b/features/promo-banners/api/src/main/kotlin/com/tangem/features/promobanners/api/PromoBannersBlockComponent.kt index bb50b2bcd2..e97e39f047 100644 --- a/features/promo-banners/api/src/main/kotlin/com/tangem/features/promobanners/api/PromoBannersBlockComponent.kt +++ b/features/promo-banners/api/src/main/kotlin/com/tangem/features/promobanners/api/PromoBannersBlockComponent.kt @@ -5,8 +5,11 @@ import com.tangem.core.ui.decompose.ComposableContentComponent interface PromoBannersBlockComponent : ComposableContentComponent { + fun setVisibleOnScreen(isVisible: Boolean) + data class Params( val placeholder: Placeholder, + val isInitiallyVisibleOnScreen: Boolean = true, ) enum class Placeholder { diff --git a/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/DefaultPromoBannersBlockComponent.kt b/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/DefaultPromoBannersBlockComponent.kt index 23ae1dce22..814031d197 100644 --- a/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/DefaultPromoBannersBlockComponent.kt +++ b/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/DefaultPromoBannersBlockComponent.kt @@ -20,6 +20,10 @@ internal class DefaultPromoBannersBlockComponent @AssistedInject constructor( private val model: PromoBannersBlockModel = getOrCreateModel(params) + override fun setVisibleOnScreen(isVisible: Boolean) { + model.setVisibleOnScreen(isVisible) + } + @Composable override fun Content(modifier: Modifier) { val state by model.uiState.collectAsStateWithLifecycle() diff --git a/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/model/PromoBannersBlockModel.kt b/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/model/PromoBannersBlockModel.kt index f4f2d505e5..89a157c634 100644 --- a/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/model/PromoBannersBlockModel.kt +++ b/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/model/PromoBannersBlockModel.kt @@ -36,6 +36,7 @@ internal class PromoBannersBlockModel @Inject constructor( private val placeholder: String = params.placeholder.name.lowercase() private val shownBannerIds: MutableSet = ConcurrentHashMap.newKeySet() + private var isVisibleOnScreen: Boolean = params.isInitiallyVisibleOnScreen private var wasCarouselScrolled = false private val savedDisplayIdByWalletId: MutableMap = mutableMapOf() @@ -46,6 +47,11 @@ internal class PromoBannersBlockModel @Inject constructor( subscribeOnSelectedWallet() } + fun setVisibleOnScreen(visible: Boolean) { + isVisibleOnScreen = visible + uiState.update { it.copy(isVisibleOnScreen = visible) } + } + private fun subscribeOnSelectedWallet() { modelScope.launch { userWalletsListRepository.selectedUserWallet @@ -53,7 +59,6 @@ internal class PromoBannersBlockModel @Inject constructor( .map { it.walletId.stringValue } .distinctUntilChanged() .onEach { - shownBannerIds.clear() wasCarouselScrolled = false } .collectLatest { walletId -> loadBanners(walletId) } @@ -85,6 +90,7 @@ internal class PromoBannersBlockModel @Inject constructor( userWalletId = walletId, initialPage = initialPage, banners = bannerUMs, + isVisibleOnScreen = isVisibleOnScreen, onBannerShown = ::onBannerShown, onCarouselScrolled = ::onCarouselScrolled, onPageChanged = { displayId -> savedDisplayIdByWalletId[walletId] = displayId }, @@ -116,6 +122,7 @@ internal class PromoBannersBlockModel @Inject constructor( userWalletId = "", initialPage = 0, banners = persistentListOf(), + isVisibleOnScreen = isVisibleOnScreen, onBannerShown = {}, onCarouselScrolled = {}, onPageChanged = {}, diff --git a/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/model/PromoBannersBlockUM.kt b/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/model/PromoBannersBlockUM.kt index 0b12f8cf7c..d46d64f0de 100644 --- a/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/model/PromoBannersBlockUM.kt +++ b/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/model/PromoBannersBlockUM.kt @@ -6,6 +6,7 @@ internal data class PromoBannersBlockUM( val userWalletId: String, val initialPage: Int, val banners: ImmutableList, + val isVisibleOnScreen: Boolean, val onBannerShown: (displayId: Int) -> Unit, val onCarouselScrolled: (displayId: Int) -> Unit, val onPageChanged: (displayId: Int) -> Unit, diff --git a/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/ui/PromoBannersBlock.kt b/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/ui/PromoBannersBlock.kt index 49cff50a0a..ad49debe78 100644 --- a/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/ui/PromoBannersBlock.kt +++ b/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/ui/PromoBannersBlock.kt @@ -28,8 +28,10 @@ internal fun PromoBannersBlock(state: PromoBannersBlockUM, modifier: Modifier = if (state.banners.size == 1) { val banner = state.banners.first() - LaunchedEffect(banner.displayId) { - state.onBannerShown(banner.displayId) + LaunchedEffect(banner.displayId, state.isVisibleOnScreen) { + if (state.isVisibleOnScreen) { + state.onBannerShown(banner.displayId) + } } SingleBanner( banner = banner, @@ -38,11 +40,7 @@ internal fun PromoBannersBlock(state: PromoBannersBlockUM, modifier: Modifier = } else { key(state.userWalletId) { BannersCarousel( - banners = state.banners, - initialPage = state.initialPage, - onBannerShown = state.onBannerShown, - onCarouselScroll = state.onCarouselScrolled, - onPageChange = state.onPageChanged, + state = state, modifier = modifier, ) } @@ -59,31 +57,25 @@ private fun SingleBanner(banner: PromoBannerNotificationUM, modifier: Modifier = } @Composable -private fun BannersCarousel( - banners: List, - initialPage: Int, - onBannerShown: (Int) -> Unit, - onCarouselScroll: (Int) -> Unit, - onPageChange: (Int) -> Unit, - modifier: Modifier = Modifier, -) { +private fun BannersCarousel(state: PromoBannersBlockUM, modifier: Modifier = Modifier) { val pagerState = rememberPagerState( - initialPage = initialPage, - pageCount = { banners.size }, + initialPage = state.initialPage, + pageCount = { state.banners.size }, ) - LaunchedEffect(pagerState, banners) { + LaunchedEffect(pagerState, state.banners, state.isVisibleOnScreen) { + if (!state.isVisibleOnScreen) return@LaunchedEffect + var previousPage = pagerState.currentPage snapshotFlow { pagerState.currentPage } .collect { page -> - banners.getOrNull(page)?.let { banner -> - onPageChange(banner.displayId) - onBannerShown(banner.displayId) - if (page == 1) { - // one-time event when user scrolls from first to second page, - // indicating that he has interacted with the carousel - onCarouselScroll(banner.displayId) + state.banners.getOrNull(page)?.let { banner -> + state.onPageChanged(banner.displayId) + state.onBannerShown(banner.displayId) + if (previousPage == 0 && page == 1) { + state.onCarouselScrolled(banner.displayId) } } + previousPage = page } } @@ -92,7 +84,7 @@ private fun BannersCarousel( horizontalAlignment = Alignment.CenterHorizontally, ) { SmoothHeightPager( - banners = banners, + banners = state.banners, pagerState = pagerState, modifier = Modifier.fillMaxWidth(), )