Updated on 2026-08-14

This commit is contained in:
Tangem 2026-04-15 11:21:53 +03:00
parent 17add53bc0
commit 304e6d0c5c
6 changed files with 41 additions and 27 deletions

View file

@ -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 {

View file

@ -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()

View file

@ -36,6 +36,7 @@ internal class PromoBannersBlockModel @Inject constructor(
private val placeholder: String = params.placeholder.name.lowercase()
private val shownBannerIds: MutableSet<Int> = ConcurrentHashMap.newKeySet()
private var isVisibleOnScreen: Boolean = params.isInitiallyVisibleOnScreen
private var wasCarouselScrolled = false
private val savedDisplayIdByWalletId: MutableMap<String, Int> = 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 = {},

View file

@ -6,6 +6,7 @@ internal data class PromoBannersBlockUM(
val userWalletId: String,
val initialPage: Int,
val banners: ImmutableList<PromoBannerNotificationUM>,
val isVisibleOnScreen: Boolean,
val onBannerShown: (displayId: Int) -> Unit,
val onCarouselScrolled: (displayId: Int) -> Unit,
val onPageChanged: (displayId: Int) -> Unit,

View file

@ -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<PromoBannerNotificationUM>,
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(),
)