Updated on 2026-08-14

This commit is contained in:
Tangem 2024-10-07 15:32:56 +04:00
parent 3993e1d690
commit bb58d027c6
19 changed files with 200 additions and 19 deletions

View file

@ -123,6 +123,14 @@ internal object SettingsDomainModule {
return ShouldShowSwapPromoWalletUseCase(promoSettingsRepository)
}
@Provides
@Singleton
fun provideShouldShowRingPromoUseCase(
promoSettingsRepository: PromoSettingsRepository,
): ShouldShowRingPromoUseCase {
return ShouldShowRingPromoUseCase(promoSettingsRepository)
}
@Provides
@Singleton
fun provideShouldShowTravalaPromoWalletUseCase(

View file

@ -101,6 +101,8 @@ object PreferencesKeys {
val ADDED_WALLETS_WITH_RING_KEY by lazy { stringSetPreferencesKey(name = "addedWalletsWithRing") }
val SHOULD_SHOW_RING_PROMO_KEY by lazy { booleanPreferencesKey(name = "shouldShowRingPromo") }
// region Permission
fun getShouldShowPermission(permission: String) = booleanPreferencesKey("shouldShowPushPermission_$permission")

View file

@ -179,8 +179,13 @@ private fun Icon(@DrawableRes iconResId: Int, tint: Color?, modifier: Modifier =
}
@Composable
private fun TextsBlock(title: TextReference?, subtitle: TextReference, subtitleColor: Color) {
Column {
internal fun TextsBlock(
title: TextReference?,
subtitle: TextReference,
subtitleColor: Color,
modifier: Modifier = Modifier,
) {
Column(modifier = modifier) {
val titleText = title?.resolveReference()
if (titleText != null) {
@ -269,7 +274,12 @@ private fun PairButtons(config: NotificationButtonsState.PairButtonsConfig, isEn
}
@Composable
private fun CloseableIconButton(onClick: (() -> Unit)?, modifier: Modifier = Modifier, isEnabled: Boolean = true) {
internal fun CloseableIconButton(
onClick: (() -> Unit)?,
modifier: Modifier = Modifier,
isEnabled: Boolean = true,
iconTint: Color = TangemTheme.colors.icon.inactive,
) {
AnimatedVisibility(visible = onClick != null, modifier = modifier) {
onClick ?: return@AnimatedVisibility
@ -296,7 +306,7 @@ private fun CloseableIconButton(onClick: (() -> Unit)?, modifier: Modifier = Mod
modifier = Modifier
.size(size = TangemTheme.dimens.size16)
.align(alignment = Alignment.Center),
tint = TangemTheme.colors.icon.inactive,
tint = iconTint,
)
}
}

View file

@ -0,0 +1,80 @@
package com.tangem.core.ui.components.notifications
import androidx.annotation.DrawableRes
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.R
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.res.TangemTheme
@Composable
fun RingPromoNotification(config: NotificationConfig, modifier: Modifier = Modifier) {
NotificationBaseContainer(
backgroundResId = requireNotNull(config.backgroundResId),
onCloseClick = config.onCloseClick,
modifier = modifier,
) {
MainContent(title = config.title, subtitle = config.subtitle)
}
}
@Composable
private fun NotificationBaseContainer(
@DrawableRes backgroundResId: Int,
onCloseClick: (() -> Unit)?,
modifier: Modifier = Modifier,
content: @Composable BoxScope.() -> Unit,
) {
Box(
modifier = modifier
.requiredHeight(height = 78.dp)
.fillMaxWidth()
.clip(shape = TangemTheme.shapes.roundedCornersXMedium),
) {
Image(
painter = painterResource(backgroundResId),
contentDescription = null,
contentScale = ContentScale.Crop,
modifier = Modifier.fillMaxSize(),
)
content()
CloseableIconButton(
onClick = onCloseClick,
modifier = Modifier.align(alignment = Alignment.TopEnd),
iconTint = TangemTheme.colors.icon.informative,
)
}
}
@Composable
private fun MainContent(title: TextReference?, subtitle: TextReference) {
Box {
Image(
painter = painterResource(id = R.drawable.img_card_with_ring),
contentDescription = null,
alignment = Alignment.TopStart,
contentScale = ContentScale.Crop,
modifier = Modifier
.padding(start = 9.dp, top = 11.dp)
.requiredWidth(width = 60.dp),
)
TextsBlock(
title = title,
subtitle = subtitle,
subtitleColor = TangemTheme.colors.text.tertiary,
modifier = Modifier
.align(Alignment.CenterStart)
.padding(start = 80.dp, top = 12.dp, end = 12.dp, bottom = 12.dp),
)
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

View file

@ -41,9 +41,19 @@ internal class DefaultPromoRepository(
}.getOrNull()
}
override suspend fun getRingPromoBanner(): PromoBanner? {
return runCatching(dispatchers.io) {
promoResponseConverter.convert(
tangemApi.getPromotionInfo(RING)
.getOrThrow(),
)
}.getOrNull()
}
private companion object {
private const val CHANGELLY_NAME = "changelly"
private const val TRAVALA = "travala"
private const val OKX = "okx"
private const val RING = "ring"
}
}

View file

@ -17,6 +17,7 @@ dependencies {
implementation(projects.domain.balanceHiding.models)
implementation(projects.domain.settings)
implementation(projects.domain.wallets.models)
implementation(deps.androidx.datastore)

View file

@ -1,13 +1,18 @@
package com.tangem.data.settings
import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.datasource.local.preferences.PreferencesKeys.ADDED_WALLETS_WITH_RING_KEY
import com.tangem.datasource.local.preferences.PreferencesKeys.IS_TOKEN_SWAP_PROMO_OKX_SHOW_KEY
import com.tangem.datasource.local.preferences.PreferencesKeys.IS_WALLET_SWAP_PROMO_OKX_SHOW_KEY
import com.tangem.datasource.local.preferences.PreferencesKeys.IS_WALLET_TRAVALA_PROMO_SHOWN_KEY
import com.tangem.datasource.local.preferences.PreferencesKeys.SHOULD_SHOW_RING_PROMO_KEY
import com.tangem.datasource.local.preferences.utils.get
import com.tangem.datasource.local.preferences.utils.store
import com.tangem.domain.settings.repositories.PromoSettingsRepository
import com.tangem.domain.wallets.models.UserWalletId
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
/**
* Repository for showing swap promo notification.
@ -47,4 +52,18 @@ class DefaultPromoSettingsRepository(
value = false,
)
}
override fun isReadyToShowRingPromo(userWalletId: UserWalletId): Flow<Boolean> {
return combine(
flow = appPreferencesStore
.get(key = ADDED_WALLETS_WITH_RING_KEY, default = emptySet())
.map { userWalletId.stringValue in it },
flow2 = appPreferencesStore.get(key = SHOULD_SHOW_RING_PROMO_KEY, default = true),
transform = { isRingAdded, shouldShowRingPromo -> isRingAdded && shouldShowRingPromo },
)
}
override suspend fun setNeverToShowRingPromo() {
appPreferencesStore.store(key = SHOULD_SHOW_RING_PROMO_KEY, value = false)
}
}

View file

@ -7,4 +7,5 @@ dependencies {
implementation(deps.kotlin.coroutines)
implementation(deps.arrow.core)
implementation(projects.domain.balanceHiding.models)
implementation(projects.domain.wallets.models)
}

View file

@ -0,0 +1,14 @@
package com.tangem.domain.settings
import com.tangem.domain.settings.repositories.PromoSettingsRepository
import com.tangem.domain.wallets.models.UserWalletId
import kotlinx.coroutines.flow.Flow
class ShouldShowRingPromoUseCase(private val promoSettingsRepository: PromoSettingsRepository) {
operator fun invoke(userWalletId: UserWalletId): Flow<Boolean> {
return promoSettingsRepository.isReadyToShowRingPromo(userWalletId)
}
suspend fun neverToShow() = promoSettingsRepository.setNeverToShowRingPromo()
}

View file

@ -1,5 +1,6 @@
package com.tangem.domain.settings.repositories
import com.tangem.domain.wallets.models.UserWalletId
import kotlinx.coroutines.flow.Flow
interface PromoSettingsRepository {
@ -14,4 +15,8 @@ interface PromoSettingsRepository {
fun isReadyToShowWalletTravalaPromo(): Flow<Boolean>
suspend fun setNeverToShowWalletTravalaPromo()
fun isReadyToShowRingPromo(userWalletId: UserWalletId): Flow<Boolean>
suspend fun setNeverToShowRingPromo()
}

View file

@ -39,5 +39,6 @@ sealed class TokenSwapPromoAnalyticsEvent(
enum class ProgramName {
Travala,
OKX,
Ring,
}
}

View file

@ -9,4 +9,6 @@ interface PromoRepository {
suspend fun getTravalaPromoBanner(): PromoBanner?
suspend fun getOkxPromoBanner(): PromoBanner?
suspend fun getRingPromoBanner(): PromoBanner?
}

View file

@ -56,6 +56,10 @@ internal class WalletWarningsAnalyticsSender @Inject constructor(
is WalletNotification.Warning.SomeNetworksUnreachable,
is WalletNotification.Warning.NetworksUnreachable,
-> null
is WalletNotification.RingPromo -> TokenSwapPromoAnalyticsEvent.NoticePromotionBanner(
source = AnalyticsParam.ScreensSources.Main,
programName = TokenSwapPromoAnalyticsEvent.ProgramName.Ring,
)
}
}
}

View file

@ -6,7 +6,7 @@ import com.tangem.domain.core.lce.Lce
import com.tangem.domain.demo.IsDemoCardUseCase
import com.tangem.domain.promo.PromoBanner
import com.tangem.domain.settings.IsReadyToShowRateAppUseCase
import com.tangem.domain.settings.ShouldShowSwapPromoWalletUseCase
import com.tangem.domain.settings.ShouldShowRingPromoUseCase
import com.tangem.domain.tokens.GetTokenListUseCase
import com.tangem.domain.tokens.error.TokenListError
import com.tangem.domain.tokens.model.CryptoCurrency
@ -33,7 +33,7 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
private val getTokenListUseCase: GetTokenListUseCase,
private val isDemoCardUseCase: IsDemoCardUseCase,
private val isReadyToShowRateAppUseCase: IsReadyToShowRateAppUseCase,
private val shouldShowSwapPromoWalletUseCase: ShouldShowSwapPromoWalletUseCase,
private val shouldShowRingPromoUseCase: ShouldShowRingPromoUseCase,
private val promoRepository: PromoRepository,
private val isNeedToBackupUseCase: IsNeedToBackupUseCase,
private val backupValidator: BackupValidator,
@ -42,16 +42,16 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
fun create(userWallet: UserWallet, clickIntents: WalletClickIntents): Flow<ImmutableList<WalletNotification>> {
val cardTypesResolver = userWallet.scanResponse.cardTypesResolver
val promoFlow = flow { emit(promoRepository.getOkxPromoBanner()) }
val promoFlow = flow { emit(promoRepository.getRingPromoBanner()) }
return combine(
flow = getTokenListUseCase.launch(userWallet.walletId),
flow2 = isReadyToShowRateAppUseCase(),
flow3 = isNeedToBackupUseCase(userWallet.walletId),
flow4 = shouldShowSwapPromoWalletUseCase(),
flow4 = shouldShowRingPromoUseCase(userWalletId = userWallet.walletId),
flow5 = promoFlow,
) { maybeTokenList, isReadyToShowRating, isNeedToBackup, shouldShowPromo, promoBanner ->
buildList {
addSwapPromoNotification(shouldShowPromo, promoBanner, clickIntents)
addRingPromoNotification(shouldShowPromo, promoBanner, clickIntents)
addCriticalNotifications(userWallet, clickIntents)
@ -70,17 +70,13 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
}
}
private fun MutableList<WalletNotification>.addSwapPromoNotification(
private fun MutableList<WalletNotification>.addRingPromoNotification(
shouldShowPromo: Boolean,
promoBanner: PromoBanner?,
clickIntents: WalletClickIntents,
) {
promoBanner ?: return
val promoNotification = WalletNotification.SwapPromo(
startDateTime = promoBanner.bannerState.timeline.start,
endDateTime = promoBanner.bannerState.timeline.end,
onCloseClick = clickIntents::onCloseSwapPromoClick,
)
val promoNotification = WalletNotification.RingPromo(onCloseClick = clickIntents::onCloseRingPromoClick)
addIf(
element = promoNotification,
condition = shouldShowPromo && promoBanner.isActive,

View file

@ -194,4 +194,14 @@ sealed class WalletNotification(val config: NotificationConfig) {
onCloseClick = onCloseClick,
),
)
data class RingPromo(val onCloseClick: () -> Unit) : WalletNotification(
config = NotificationConfig(
title = resourceReference(id = R.string.ring_promo_title),
subtitle = resourceReference(id = R.string.ring_promo_text),
iconResId = R.drawable.img_card_with_ring,
backgroundResId = R.drawable.img_ring_promo_background,
onCloseClick = onCloseClick,
),
)
}

View file

@ -6,6 +6,7 @@ import androidx.compose.foundation.lazy.items
import androidx.compose.ui.Modifier
import com.tangem.core.ui.components.notifications.Notification
import com.tangem.core.ui.components.notifications.OkxPromoNotification
import com.tangem.core.ui.components.notifications.RingPromoNotification
import com.tangem.core.ui.res.TangemTheme
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNotification
import kotlinx.collections.immutable.ImmutableList
@ -33,6 +34,9 @@ internal fun LazyListScope.notifications(configs: ImmutableList<WalletNotificati
modifier = modifier.animateItemPlacement(),
)
}
is WalletNotification.RingPromo -> {
RingPromoNotification(config = it.config, modifier = modifier.animateItemPlacement())
}
else -> {
Notification(
config = it.config,

View file

@ -8,10 +8,7 @@ import com.tangem.domain.card.DerivePublicKeysUseCase
import com.tangem.domain.card.SetCardWasScannedUseCase
import com.tangem.domain.redux.LegacyAction
import com.tangem.domain.redux.ReduxStateHolder
import com.tangem.domain.settings.NeverToSuggestRateAppUseCase
import com.tangem.domain.settings.RemindToRateAppLaterUseCase
import com.tangem.domain.settings.ShouldShowSwapPromoWalletUseCase
import com.tangem.domain.settings.ShouldShowTravalaPromoWalletUseCase
import com.tangem.domain.settings.*
import com.tangem.domain.tokens.FetchTokenListUseCase
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.analytics.TokenSwapPromoAnalyticsEvent
@ -59,6 +56,8 @@ internal interface WalletWarningsClickIntents {
fun onCloseSwapPromoClick()
fun onCloseRingPromoClick()
fun onTravalaPromoClick(link: String?)
fun onCloseTravalaPromoClick()
@ -83,6 +82,7 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor(
private val reduxStateHolder: ReduxStateHolder,
private val dispatchers: CoroutineDispatcherProvider,
private val shouldShowSwapPromoWalletUseCase: ShouldShowSwapPromoWalletUseCase,
private val shouldShowRingPromoUseCase: ShouldShowRingPromoUseCase,
private val shouldShowTravalaPromoWalletUseCase: ShouldShowTravalaPromoWalletUseCase,
) : BaseWalletClickIntents(), WalletWarningsClickIntents {
@ -239,6 +239,20 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor(
}
}
override fun onCloseRingPromoClick() {
analyticsEventHandler.send(
TokenSwapPromoAnalyticsEvent.PromotionBannerClicked(
source = AnalyticsParam.ScreensSources.Main,
programName = TokenSwapPromoAnalyticsEvent.ProgramName.Ring,
action = TokenSwapPromoAnalyticsEvent.PromotionBannerClicked.BannerAction.Closed,
),
)
viewModelScope.launch {
shouldShowRingPromoUseCase.neverToShow()
}
}
override fun onTravalaPromoClick(link: String?) {
analyticsEventHandler.send(
TokenSwapPromoAnalyticsEvent.PromotionBannerClicked(

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 KiB