Updated on 2026-08-14
This commit is contained in:
parent
790fa03399
commit
50f30e9bbf
25 changed files with 583 additions and 42 deletions
|
|
@ -0,0 +1,39 @@
|
|||
package com.tangem.features.promobanners.impl
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.model.getOrCreateModel
|
||||
import com.tangem.features.promobanners.api.PromoBannersBlockComponent
|
||||
import com.tangem.features.promobanners.impl.model.PromoBannersBlockModel
|
||||
import com.tangem.features.promobanners.impl.ui.PromoBannersBlock
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
|
||||
internal class DefaultPromoBannersBlockComponent @AssistedInject constructor(
|
||||
@Assisted appComponentContext: AppComponentContext,
|
||||
@Assisted private val params: PromoBannersBlockComponent.Params,
|
||||
) : PromoBannersBlockComponent, AppComponentContext by appComponentContext {
|
||||
|
||||
private val model: PromoBannersBlockModel = getOrCreateModel(params)
|
||||
|
||||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
val state by model.uiState.collectAsStateWithLifecycle()
|
||||
PromoBannersBlock(
|
||||
state = state,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory : PromoBannersBlockComponent.Factory {
|
||||
override fun create(
|
||||
context: AppComponentContext,
|
||||
params: PromoBannersBlockComponent.Params,
|
||||
): DefaultPromoBannersBlockComponent
|
||||
}
|
||||
}
|
||||
|
|
@ -5,20 +5,37 @@ import com.tangem.core.analytics.models.AnalyticsEvent
|
|||
internal sealed class PromoBannerAnalyticsEvent(
|
||||
event: String,
|
||||
params: Map<String, String> = emptyMap(),
|
||||
) : AnalyticsEvent(category = "Banner", event = event, params = params) {
|
||||
) : AnalyticsEvent(category = "Promo Banner", event = event, params = params) {
|
||||
|
||||
data class Shown(val bannerId: String) : PromoBannerAnalyticsEvent(
|
||||
data class Shown(
|
||||
private val displayId: String,
|
||||
private val placeholder: String,
|
||||
) : PromoBannerAnalyticsEvent(
|
||||
event = "Banner Shown",
|
||||
params = mapOf("banner_id" to bannerId),
|
||||
params = mapOf("Display Id" to displayId, "Placeholder" to placeholder),
|
||||
)
|
||||
|
||||
data class Clicked(val bannerId: String) : PromoBannerAnalyticsEvent(
|
||||
event = "Banner Clicked",
|
||||
params = mapOf("banner_id" to bannerId),
|
||||
data class CarouselScrolled(
|
||||
private val displayId: String,
|
||||
private val placeholder: String,
|
||||
) : PromoBannerAnalyticsEvent(
|
||||
event = "Banner Carousel Scrolled",
|
||||
params = mapOf("Display Id" to displayId, "Placeholder" to placeholder),
|
||||
)
|
||||
|
||||
data class Dismissed(val bannerId: String) : PromoBannerAnalyticsEvent(
|
||||
data class Clicked(
|
||||
private val displayId: String,
|
||||
private val placeholder: String,
|
||||
) : PromoBannerAnalyticsEvent(
|
||||
event = "Banner Button Clicked",
|
||||
params = mapOf("Display Id" to displayId, "Placeholder" to placeholder),
|
||||
)
|
||||
|
||||
data class Dismissed(
|
||||
private val displayId: String,
|
||||
private val placeholder: String,
|
||||
) : PromoBannerAnalyticsEvent(
|
||||
event = "Banner Dismissed",
|
||||
params = mapOf("banner_id" to bannerId),
|
||||
params = mapOf("Display Id" to displayId, "Placeholder" to placeholder),
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
package com.tangem.features.promobanners.impl.di
|
||||
|
||||
import com.tangem.core.decompose.di.ModelComponent
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.datasource.local.datastore.RuntimeSharedStore
|
||||
import com.tangem.features.promobanners.api.NewPromoBannersFeatureToggles
|
||||
import com.tangem.features.promobanners.api.PromoBannersBlockComponent
|
||||
import com.tangem.features.promobanners.impl.DefaultPromoBannersBlockComponent
|
||||
import com.tangem.features.promobanners.impl.model.PromoBannersBlockModel
|
||||
import com.tangem.features.promobanners.impl.repository.DefaultPromoBannersRepository
|
||||
import com.tangem.features.promobanners.impl.repository.PromoBannersRepository
|
||||
import com.tangem.features.promobanners.impl.toggles.DefaultNewPromoBannersFeatureToggles
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import dagger.multibindings.ClassKey
|
||||
import dagger.multibindings.IntoMap
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal interface PromoBannersFeatureModule {
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindPromoBannersBlockComponentFactory(
|
||||
factory: DefaultPromoBannersBlockComponent.Factory,
|
||||
): PromoBannersBlockComponent.Factory
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindFeatureToggles(impl: DefaultNewPromoBannersFeatureToggles): NewPromoBannersFeatureToggles
|
||||
|
||||
companion object {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideRepository(
|
||||
tangemTechApi: TangemTechApi,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
): PromoBannersRepository {
|
||||
return DefaultPromoBannersRepository(
|
||||
tangemTechApi = tangemTechApi,
|
||||
dispatchers = dispatchers,
|
||||
cache = RuntimeSharedStore(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Module
|
||||
@InstallIn(ModelComponent::class)
|
||||
internal interface PromoBannersModelModule {
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ClassKey(PromoBannersBlockModel::class)
|
||||
fun bindPromoBannersBlockModel(model: PromoBannersBlockModel): Model
|
||||
}
|
||||
|
|
@ -0,0 +1,123 @@
|
|||
package com.tangem.features.promobanners.impl.model
|
||||
|
||||
import com.tangem.common.routing.LinkHandler
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||
import com.tangem.features.promobanners.api.PromoBannersBlockComponent
|
||||
import com.tangem.features.promobanners.impl.analytics.PromoBannerAnalyticsEvent
|
||||
import com.tangem.features.promobanners.impl.converters.PromoBannerDisplayToNotificationConverter
|
||||
import com.tangem.features.promobanners.impl.repository.PromoBannersRepository
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.coroutines.runSuspendCatching
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import java.util.Locale
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import javax.inject.Inject
|
||||
|
||||
@ModelScoped
|
||||
internal class PromoBannersBlockModel @Inject constructor(
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
paramsContainer: ParamsContainer,
|
||||
private val repository: PromoBannersRepository,
|
||||
private val linkHandler: LinkHandler,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
private val userWalletsListRepository: UserWalletsListRepository,
|
||||
) : Model() {
|
||||
|
||||
private val params = paramsContainer.require<PromoBannersBlockComponent.Params>()
|
||||
private val converter = PromoBannerDisplayToNotificationConverter()
|
||||
|
||||
private val placeholder: String = params.placeholder.name.lowercase()
|
||||
private val shownBannerIds: MutableSet<String> = ConcurrentHashMap.newKeySet()
|
||||
private var wasCarouselScrolled = false
|
||||
|
||||
val uiState: StateFlow<PromoBannersBlockUM>
|
||||
field = MutableStateFlow(PromoBannersBlockUM())
|
||||
|
||||
init {
|
||||
subscribeOnSelectedWallet()
|
||||
}
|
||||
|
||||
private fun subscribeOnSelectedWallet() {
|
||||
modelScope.launch {
|
||||
userWalletsListRepository.selectedUserWallet
|
||||
.filterNotNull()
|
||||
.map { it.walletId.stringValue }
|
||||
.distinctUntilChanged()
|
||||
.onEach {
|
||||
shownBannerIds.clear()
|
||||
wasCarouselScrolled = false
|
||||
}
|
||||
.collectLatest { walletId -> loadBanners(walletId) }
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun loadBanners(walletId: String) {
|
||||
val locale = Locale.getDefault().language
|
||||
|
||||
runSuspendCatching {
|
||||
repository.getBanners(walletId, params.placeholder, locale)
|
||||
}.onSuccess { banners ->
|
||||
uiState.value = PromoBannersBlockUM(
|
||||
banners = banners.map { banner ->
|
||||
converter.convert(
|
||||
banner = banner,
|
||||
onDeeplinkClick = { deeplink -> onButtonClick(banner.id, deeplink) },
|
||||
onDismiss = { displayId -> onBannerDismiss(walletId, displayId) },
|
||||
)
|
||||
}.toImmutableList(),
|
||||
onBannerShown = ::onBannerShown,
|
||||
onCarouselScrolled = ::onCarouselScrolled,
|
||||
)
|
||||
}.onFailure { error ->
|
||||
Timber.w(error, "Failed to load promo banners")
|
||||
}
|
||||
}
|
||||
|
||||
private fun onBannerShown(displayId: String) {
|
||||
if (shownBannerIds.add(displayId)) {
|
||||
analyticsEventHandler.send(PromoBannerAnalyticsEvent.Shown(displayId, placeholder))
|
||||
}
|
||||
}
|
||||
|
||||
private fun onCarouselScrolled(displayId: String) {
|
||||
if (!wasCarouselScrolled) {
|
||||
wasCarouselScrolled = true
|
||||
analyticsEventHandler.send(PromoBannerAnalyticsEvent.CarouselScrolled(displayId, placeholder))
|
||||
}
|
||||
}
|
||||
|
||||
private fun onButtonClick(displayId: String, deeplink: String?) {
|
||||
analyticsEventHandler.send(PromoBannerAnalyticsEvent.Clicked(displayId, placeholder))
|
||||
deeplink?.let { linkHandler.navigate(it) }
|
||||
}
|
||||
|
||||
private fun onBannerDismiss(walletId: String, displayId: String) {
|
||||
analyticsEventHandler.send(PromoBannerAnalyticsEvent.Dismissed(displayId, placeholder))
|
||||
uiState.update { state ->
|
||||
state.copy(
|
||||
banners = state.banners
|
||||
.filterNot { it.displayId == displayId }
|
||||
.toImmutableList(),
|
||||
)
|
||||
}
|
||||
modelScope.launch {
|
||||
runSuspendCatching {
|
||||
repository.dismissBanner(walletId, displayId)
|
||||
}.onFailure { error ->
|
||||
Timber.w(
|
||||
error,
|
||||
"Failed to dismiss promo banner %s for wallet %s",
|
||||
displayId,
|
||||
walletId,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,4 +5,6 @@ import kotlinx.collections.immutable.persistentListOf
|
|||
|
||||
internal data class PromoBannersBlockUM(
|
||||
val banners: ImmutableList<PromoBannerNotificationUM> = persistentListOf(),
|
||||
val onBannerShown: (displayId: String) -> Unit = {},
|
||||
val onCarouselScrolled: (displayId: String) -> Unit = {},
|
||||
)
|
||||
|
|
@ -3,26 +3,41 @@ package com.tangem.features.promobanners.impl.repository
|
|||
import com.tangem.datasource.api.common.response.getOrThrow
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.datasource.api.tangemTech.models.promobanners.DismissPromoBannerRequest
|
||||
import com.tangem.datasource.local.datastore.RuntimeSharedStore
|
||||
import com.tangem.features.promobanners.api.PromoBannersBlockComponent.Placeholder
|
||||
import com.tangem.features.promobanners.impl.converters.PromoBannerDisplayDTOConverter
|
||||
import com.tangem.features.promobanners.impl.model.PromoBannerDisplay
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.withContext
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
internal class DefaultPromoBannersRepository @Inject constructor(
|
||||
internal typealias BannersCache = Map<BannersCacheKey, List<PromoBannerDisplay>>
|
||||
|
||||
internal data class BannersCacheKey(
|
||||
val walletId: String,
|
||||
val placeholder: Placeholder,
|
||||
)
|
||||
|
||||
internal class DefaultPromoBannersRepository(
|
||||
private val tangemTechApi: TangemTechApi,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
private val cache: RuntimeSharedStore<BannersCache>,
|
||||
) : PromoBannersRepository {
|
||||
|
||||
private val converter = PromoBannerDisplayDTOConverter()
|
||||
|
||||
override suspend fun getBanners(walletId: String, placeholder: String, locale: String): List<PromoBannerDisplay> =
|
||||
withContext(dispatchers.io) {
|
||||
override suspend fun getBanners(
|
||||
walletId: String,
|
||||
placeholder: Placeholder,
|
||||
locale: String,
|
||||
): List<PromoBannerDisplay> {
|
||||
val key = BannersCacheKey(walletId, placeholder)
|
||||
|
||||
cache.getSyncOrNull()?.get(key)?.let { return it }
|
||||
|
||||
val banners = withContext(dispatchers.io) {
|
||||
tangemTechApi.getPromoBannerDisplays(
|
||||
walletId = walletId,
|
||||
placeholder = placeholder,
|
||||
placeholder = placeholder.toApiValue(),
|
||||
locale = locale,
|
||||
).getOrThrow()
|
||||
.items
|
||||
|
|
@ -30,7 +45,24 @@ internal class DefaultPromoBannersRepository @Inject constructor(
|
|||
.sortedBy { it.priority.order }
|
||||
}
|
||||
|
||||
cache.update(default = emptyMap()) { current ->
|
||||
current + (key to banners)
|
||||
}
|
||||
|
||||
return banners
|
||||
}
|
||||
|
||||
override suspend fun dismissBanner(walletId: String, displayId: String) {
|
||||
cache.update(default = emptyMap()) { current ->
|
||||
current.mapValues { (key, banners) ->
|
||||
if (key.walletId == walletId) {
|
||||
banners.filterNot { it.id == displayId }
|
||||
} else {
|
||||
banners
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
withContext(dispatchers.io) {
|
||||
val request = DismissPromoBannerRequest(
|
||||
walletId = walletId,
|
||||
|
|
@ -39,4 +71,9 @@ internal class DefaultPromoBannersRepository @Inject constructor(
|
|||
tangemTechApi.dismissPromoBannerDisplay(displayId, request).getOrThrow()
|
||||
}
|
||||
}
|
||||
|
||||
private fun Placeholder.toApiValue(): String = when (this) {
|
||||
Placeholder.MAIN -> "main"
|
||||
Placeholder.FEED -> "shtorka"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +1,11 @@
|
|||
package com.tangem.features.promobanners.impl.repository
|
||||
|
||||
import com.tangem.features.promobanners.api.PromoBannersBlockComponent.Placeholder
|
||||
import com.tangem.features.promobanners.impl.model.PromoBannerDisplay
|
||||
|
||||
internal interface PromoBannersRepository {
|
||||
|
||||
suspend fun getBanners(walletId: String, placeholder: String, locale: String): List<PromoBannerDisplay>
|
||||
suspend fun getBanners(walletId: String, placeholder: Placeholder, locale: String): List<PromoBannerDisplay>
|
||||
|
||||
suspend fun dismissBanner(walletId: String, displayId: String)
|
||||
}
|
||||
|
|
@ -0,0 +1,167 @@
|
|||
package com.tangem.features.promobanners.impl.ui
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.pager.HorizontalPager
|
||||
import androidx.compose.foundation.pager.PagerState
|
||||
import androidx.compose.foundation.pager.rememberPagerState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.snapshotFlow
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.layout.SubcomposeLayout
|
||||
import androidx.compose.ui.util.lerp
|
||||
import com.tangem.core.ui.components.SpacerH8
|
||||
import com.tangem.core.ui.components.notifications.Notification
|
||||
import com.tangem.core.ui.components.pager.PagerIndicator
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.features.promobanners.impl.model.PromoBannerNotificationUM
|
||||
import com.tangem.features.promobanners.impl.model.PromoBannersBlockUM
|
||||
import kotlin.math.ceil
|
||||
import kotlin.math.floor
|
||||
|
||||
@Composable
|
||||
internal fun PromoBannersBlock(state: PromoBannersBlockUM, modifier: Modifier = Modifier) {
|
||||
if (state.banners.isEmpty()) return
|
||||
|
||||
if (state.banners.size == 1) {
|
||||
val banner = state.banners.first()
|
||||
LaunchedEffect(banner.displayId) {
|
||||
state.onBannerShown(banner.displayId)
|
||||
}
|
||||
SingleBanner(
|
||||
banner = banner,
|
||||
modifier = modifier,
|
||||
)
|
||||
} else {
|
||||
BannersCarousel(
|
||||
banners = state.banners,
|
||||
onBannerShown = state.onBannerShown,
|
||||
onCarouselScroll = state.onCarouselScrolled,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SingleBanner(banner: PromoBannerNotificationUM, modifier: Modifier = Modifier) {
|
||||
Notification(
|
||||
config = banner.config,
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
containerColor = TangemTheme.colors.background.primary,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun BannersCarousel(
|
||||
banners: List<PromoBannerNotificationUM>,
|
||||
onBannerShown: (String) -> Unit,
|
||||
onCarouselScroll: (String) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val pagerState = rememberPagerState(pageCount = { banners.size })
|
||||
|
||||
LaunchedEffect(pagerState, banners) {
|
||||
snapshotFlow { pagerState.currentPage }
|
||||
.collect { page ->
|
||||
banners.getOrNull(page)?.let { banner ->
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
SmoothHeightPager(
|
||||
banners = banners,
|
||||
pagerState = pagerState,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
|
||||
SpacerH8()
|
||||
|
||||
PagerIndicator(
|
||||
pagerState = pagerState,
|
||||
hasBackground = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* HorizontalPager that smoothly interpolates its height between pages
|
||||
* based on the current scroll position, preventing layout jumps.
|
||||
*
|
||||
* Only the two adjacent pages (lower and upper of the current scroll position)
|
||||
* are measured per frame — O(1) instead of O(N).
|
||||
*/
|
||||
@Composable
|
||||
private fun SmoothHeightPager(
|
||||
banners: List<PromoBannerNotificationUM>,
|
||||
pagerState: PagerState,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
SubcomposeLayout(modifier = modifier) { constraints ->
|
||||
if (banners.isEmpty()) {
|
||||
return@SubcomposeLayout layout(constraints.maxWidth, 0) {}
|
||||
}
|
||||
|
||||
val pageConstraints = constraints.copy(minHeight = 0)
|
||||
|
||||
val scrollPosition = (pagerState.currentPage + pagerState.currentPageOffsetFraction)
|
||||
.coerceIn(0f, banners.lastIndex.toFloat())
|
||||
val lowerPage = floor(scrollPosition).toInt().coerceIn(banners.indices)
|
||||
val upperPage = ceil(scrollPosition).toInt().coerceIn(banners.indices)
|
||||
val fraction = scrollPosition - floor(scrollPosition)
|
||||
|
||||
val lowerHeight = subcompose(slotId = "measure_lower") {
|
||||
Notification(
|
||||
config = banners[lowerPage].config,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
containerColor = TangemTheme.colors.background.primary,
|
||||
)
|
||||
}.first().measure(pageConstraints).height
|
||||
|
||||
val upperHeight = if (upperPage != lowerPage) {
|
||||
subcompose(slotId = "measure_upper") {
|
||||
Notification(
|
||||
config = banners[upperPage].config,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
containerColor = TangemTheme.colors.background.primary,
|
||||
)
|
||||
}.first().measure(pageConstraints).height
|
||||
} else {
|
||||
lowerHeight
|
||||
}
|
||||
|
||||
val interpolatedHeight = lerp(lowerHeight, upperHeight, fraction)
|
||||
|
||||
val pagerPlaceable = subcompose(slotId = "pager") {
|
||||
HorizontalPager(
|
||||
state = pagerState,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
pageSpacing = TangemTheme.dimens.spacing16,
|
||||
) { page ->
|
||||
val banner = banners.getOrNull(page) ?: return@HorizontalPager
|
||||
Notification(
|
||||
config = banner.config,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
containerColor = TangemTheme.colors.background.primary,
|
||||
)
|
||||
}
|
||||
}.first().measure(
|
||||
constraints.copy(minHeight = interpolatedHeight, maxHeight = interpolatedHeight),
|
||||
)
|
||||
|
||||
layout(pagerPlaceable.width, interpolatedHeight) {
|
||||
pagerPlaceable.place(0, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,7 +4,7 @@ import com.tangem.core.ui.extensions.TextReference
|
|||
import com.tangem.features.promobanners.impl.model.PromoBannerDisplay
|
||||
import com.tangem.features.promobanners.impl.model.PromoBannerPriority
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import org.junit.Test
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class PromoBannerDisplayToNotificationConverterTest {
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue