Updated on 2026-08-14

This commit is contained in:
Tangem 2025-01-22 16:01:20 +05:00
parent bb541e6a3f
commit ec9bd02e35
23 changed files with 42 additions and 722 deletions

View file

@ -19,12 +19,6 @@ internal object PromoDomainModule {
return ShouldShowSwapPromoWalletUseCase(promoSettingsRepository)
}
@Provides
@Singleton
fun provideShouldShowRingPromoUseCase(promoRepository: PromoRepository): ShouldShowRingPromoUseCase {
return ShouldShowRingPromoUseCase(promoRepository)
}
@Provides
@Singleton
fun provideShouldShowSwapPromoTokenUseCase(promoRepository: PromoRepository): ShouldShowSwapPromoTokenUseCase {

View file

@ -11,6 +11,9 @@ import androidx.datastore.preferences.core.emptyPreferences
import androidx.datastore.preferences.preferencesDataStoreFile
import com.tangem.datasource.local.preferences.PreferencesDataStore.INSTANCE
import com.tangem.datasource.local.preferences.PreferencesKeys.APP_LOGS_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.SHOULD_SHOW_RING_PROMO_KEY
import com.tangem.datasource.local.preferences.utils.CleanupKeyMigration
import com.tangem.datasource.local.preferences.utils.SharedPreferencesKeyMigration
import kotlinx.coroutines.CoroutineScope
@ -80,6 +83,9 @@ internal object PreferencesDataStore {
keyName = PreferencesKeys.BALANCE_HIDING_SETTINGS_KEY.name,
),
CleanupKeyMigration(key = APP_LOGS_KEY),
CleanupKeyMigration(key = IS_WALLET_SWAP_PROMO_OKX_SHOW_KEY),
CleanupKeyMigration(key = IS_TOKEN_SWAP_PROMO_OKX_SHOW_KEY),
CleanupKeyMigration(key = SHOULD_SHOW_RING_PROMO_KEY),
)
}
}

View file

@ -17,6 +17,9 @@ import com.tangem.datasource.local.preferences.PreferencesKeys.WAS_TWINS_ONBOARD
/**
* All preferences keys that DataStore<Preferences> is stored.
*
* !!!IMPORTANT!!!
* Before DELETING active key do [CleanupKeyMigration] in [PreferencesDataStore]
*
[REDACTED_AUTHOR]
*/
object PreferencesKeys {
@ -87,10 +90,12 @@ object PreferencesKeys {
val UNSUBMITTED_TRANSACTIONS_KEY by lazy { stringPreferencesKey(name = "unsubmittedTransactions") }
@Deprecated("Remove after CleanupKeyMigration")
val IS_WALLET_SWAP_PROMO_OKX_SHOW_KEY by lazy {
booleanPreferencesKey(name = "isWalletSwapPromoOkxShown")
}
@Deprecated("Remove after CleanupKeyMigration")
val IS_TOKEN_SWAP_PROMO_OKX_SHOW_KEY by lazy {
booleanPreferencesKey(name = "isTokenSwapPromoOkxShown")
}
@ -99,6 +104,7 @@ object PreferencesKeys {
val ADDED_WALLETS_WITH_RING_KEY by lazy { stringSetPreferencesKey(name = "addedWalletsWithRing") }
@Deprecated("Remove after CleanupKeyMigration")
val SHOULD_SHOW_RING_PROMO_KEY by lazy { booleanPreferencesKey(name = "shouldShowRingPromo") }
val ONRAMP_DEFAULT_COUNTRY by lazy { stringPreferencesKey(name = "onrampDefaultCountry") }

View file

@ -1,206 +0,0 @@
package com.tangem.core.ui.components.notifications
import android.content.res.Configuration
import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.size
import androidx.compose.material3.ButtonColors
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.material3.ripple
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
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.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import androidx.constraintlayout.compose.ConstraintLayout
import androidx.constraintlayout.compose.Dimension
import androidx.constraintlayout.compose.Visibility
import com.tangem.core.ui.R
import com.tangem.core.ui.components.buttons.common.TangemButton
import com.tangem.core.ui.components.buttons.common.TangemButtonIconPosition
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.core.ui.res.LocalIsInDarkTheme
import com.tangem.core.ui.res.TangemColorPalette.Dark6
import com.tangem.core.ui.res.TangemColorPalette.Light4
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
/**
* Custom notification with image background
* @see [Swap Promo](https://www.figma.com/file/Vs6SkVsFnUPsSCNwlnVf5U/Android-%E2%80%93-UI?type=design&node-id=8713-6307&mode=dev)
*/
@Suppress("LongMethod", "DestructuringDeclarationWithTooManyEntries")
@Composable
fun NotificationWithBackground(config: NotificationConfig, modifier: Modifier = Modifier) {
val button = config.buttonsState as? NotificationConfig.ButtonsState.SecondaryButtonConfig
ConstraintLayout(
modifier = modifier
.defaultMinSize(minHeight = TangemTheme.dimens.size62)
.fillMaxWidth()
.clip(TangemTheme.shapes.roundedCornersXMedium)
.clickable(
enabled = config.onClick != null,
onClick = config.onClick ?: {},
),
) {
val (iconRef, titleRef, subtitleRef, closeIconRef, buttonRef, backgroundRef) = createRefs()
val spacing2 = TangemTheme.dimens.spacing2
val spacing12 = TangemTheme.dimens.spacing12
val spacing14 = TangemTheme.dimens.spacing14
Image(
painter = painterResource(config.backgroundResId ?: R.drawable.img_swap_promo_blue_banner_background),
contentDescription = null,
contentScale = ContentScale.Crop,
modifier = Modifier.constrainAs(backgroundRef) {
top.linkTo(parent.top)
start.linkTo(parent.start)
end.linkTo(parent.end)
bottom.linkTo(parent.bottom)
width = Dimension.fillToConstraints
height = Dimension.fillToConstraints
visibility = if (config.backgroundResId == null) Visibility.Gone else Visibility.Visible
},
)
Image(
painter = painterResource(config.iconResId),
contentDescription = null,
modifier = Modifier
.size(TangemTheme.dimens.size34)
.constrainAs(iconRef) {
start.linkTo(parent.start, spacing12)
linkTo(titleRef.top, subtitleRef.bottom, bias = 0.1f)
},
)
val titleText = config.title?.resolveReference()
if (titleText != null) {
Text(
text = titleText,
style = TangemTheme.typography.button,
color = TangemTheme.colors.text.constantWhite,
modifier = Modifier.constrainAs(titleRef) {
top.linkTo(parent.top, spacing12)
start.linkTo(iconRef.end, spacing12)
end.linkTo(closeIconRef.start, spacing2)
width = Dimension.fillToConstraints
},
)
}
Text(
text = config.subtitle.resolveReference(),
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.constantWhite,
modifier = Modifier.constrainAs(subtitleRef) {
top.linkTo(titleRef.bottom, spacing2)
start.linkTo(iconRef.end, spacing12)
end.linkTo(parent.end, spacing12)
bottom.linkTo(buttonRef.top, spacing12, spacing14)
width = Dimension.fillToConstraints
},
)
Icon(
painter = painterResource(id = R.drawable.ic_close_24),
contentDescription = null,
tint = TangemTheme.colors.text.constantWhite,
modifier = Modifier
.size(TangemTheme.dimens.size16)
.constrainAs(closeIconRef) {
top.linkTo(parent.top, spacing12)
end.linkTo(parent.end, spacing12)
}
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = ripple(bounded = false),
) {
config.onCloseClick?.invoke()
},
)
val isDarkMode = LocalIsInDarkTheme.current
TangemButton(
modifier = Modifier.constrainAs(buttonRef) {
start.linkTo(parent.start, spacing12)
end.linkTo(parent.end, spacing12)
bottom.linkTo(parent.bottom, spacing12)
width = Dimension.fillToConstraints
visibility = if (button == null) {
Visibility.Gone
} else {
Visibility.Visible
}
},
text = button?.text?.resolveReference().orEmpty(),
icon = TangemButtonIconPosition.Start(button?.iconResId ?: R.drawable.ic_exchange_vertical_24),
onClick = button?.onClick ?: {},
colors = ButtonColors(
containerColor = if (isDarkMode) Light4 else TangemTheme.colors.button.secondary,
contentColor = Dark6,
disabledContainerColor = TangemTheme.colors.button.disabled,
disabledContentColor = TangemTheme.colors.text.disabled,
),
textStyle = TangemTheme.typography.subtitle1,
enabled = true,
showProgress = false,
)
}
}
//region preview
@Preview
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun NotificationWithBackgroundPreview(
@PreviewParameter(NotificationWithBackgroundPreviewProvider::class) config: NotificationConfig,
) {
TangemThemePreview {
NotificationWithBackground(config = config)
}
}
private class NotificationWithBackgroundPreviewProvider : PreviewParameterProvider<NotificationConfig> {
override val values: Sequence<NotificationConfig>
get() = sequenceOf(
NotificationConfig(
title = resourceReference(id = R.string.main_swap_changelly_promotion_title),
subtitle = resourceReference(
id = R.string.main_swap_changelly_promotion_message,
formatArgs = wrappedList("1", "2", "3"),
),
iconResId = R.drawable.img_swap_promo,
backgroundResId = R.drawable.img_swap_promo_blue_banner_background,
),
NotificationConfig(
title = resourceReference(
id = R.string.token_swap_changelly_promotion_title,
formatArgs = wrappedList("1"),
),
subtitle = stringReference(
"Swap multiple currencies between any chains you wish. Swap multiple " +
"currencies between any chains you wish. Swap multiple " +
"currencies between any chains you wish. Commission free period " +
"till Dec 31.",
),
iconResId = R.drawable.img_swap_promo,
backgroundResId = R.drawable.img_swap_promo_blue_banner_background,
buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig(
text = resourceReference(id = R.string.token_swap_promotion_button),
onClick = {},
),
),
)
}
//endregion

View file

@ -1,169 +0,0 @@
package com.tangem.core.ui.components.notifications
import android.content.res.Configuration
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
import androidx.compose.material3.ButtonColors
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.material3.ripple
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import com.tangem.core.ui.R
import com.tangem.core.ui.components.buttons.common.TangemButton
import com.tangem.core.ui.components.buttons.common.TangemButtonIconPosition
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.res.LocalIsInDarkTheme
import com.tangem.core.ui.res.TangemColorPalette.Dark6
import com.tangem.core.ui.res.TangemColorPalette.Light4
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
private val OxkPromoColor = Color(0xFFBCFF2F)
@Composable
fun OkxPromoNotification(config: NotificationConfig, modifier: Modifier = Modifier) {
Column(
modifier
.clip(TangemTheme.shapes.roundedCornersXMedium)
.background(Dark6),
) {
Content(config = config)
Button(config = config)
}
}
@Composable
private fun Content(config: NotificationConfig) {
Row {
Icon(
painter = rememberVectorPainter(image = ImageVector.vectorResource(id = R.drawable.img_okx_dex_logo)),
contentDescription = null,
tint = TangemTheme.colors.icon.constant,
modifier = Modifier
.padding(start = TangemTheme.dimens.spacing12)
.align(Alignment.CenterVertically),
)
Column(
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing2),
modifier = Modifier
.weight(1f)
.padding(TangemTheme.dimens.spacing12),
) {
val titleText = config.title?.resolveReference()
if (titleText != null) {
Text(
text = titleText,
style = TangemTheme.typography.button,
color = OxkPromoColor,
)
}
Text(
text = config.subtitle.resolveReference(),
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.constantWhite,
)
}
config.onCloseClick?.let {
Icon(
painter = rememberVectorPainter(
image = ImageVector.vectorResource(R.drawable.ic_close_24),
),
contentDescription = null,
tint = TangemTheme.colors.icon.constant,
modifier =
Modifier
.padding(
top = TangemTheme.dimens.spacing8,
end = TangemTheme.dimens.spacing8,
)
.size(TangemTheme.dimens.size20)
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = ripple(radius = TangemTheme.dimens.radius10),
onClick = it,
),
)
}
}
}
@Composable
private fun Button(config: NotificationConfig) {
val button = config.buttonsState as? NotificationConfig.ButtonsState.SecondaryButtonConfig
button?.let {
val isDarkMode = LocalIsInDarkTheme.current
TangemButton(
modifier = Modifier
.fillMaxWidth()
.padding(
start = TangemTheme.dimens.spacing12,
end = TangemTheme.dimens.spacing12,
bottom = TangemTheme.dimens.spacing12,
),
text = button.text.resolveReference(),
icon = TangemButtonIconPosition.Start(button.iconResId ?: R.drawable.ic_exchange_vertical_24),
onClick = button.onClick,
colors = ButtonColors(
containerColor = if (isDarkMode) Light4 else TangemTheme.colors.button.secondary,
contentColor = Dark6,
disabledContainerColor = TangemTheme.colors.button.disabled,
disabledContentColor = TangemTheme.colors.text.disabled,
),
textStyle = TangemTheme.typography.subtitle1,
enabled = true,
showProgress = false,
)
}
}
// region Preview
@Preview(widthDp = 360)
@Preview(widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun OkxPromoNotification_Preview(
@PreviewParameter(OkxPromoNotificationPreviewProvider::class) data: NotificationConfig,
) {
TangemThemePreview {
OkxPromoNotification(data)
}
}
private class OkxPromoNotificationPreviewProvider : PreviewParameterProvider<NotificationConfig> {
override val values: Sequence<NotificationConfig>
get() = sequenceOf(
NotificationConfig(
title = resourceReference(R.string.swap_promo_title),
subtitle = resourceReference(R.string.swap_promo_text),
iconResId = R.drawable.img_okx_dex_logo,
onCloseClick = {},
),
NotificationConfig(
title = resourceReference(R.string.swap_promo_title),
subtitle = resourceReference(R.string.swap_promo_text),
iconResId = R.drawable.img_okx_dex_logo,
onCloseClick = {},
buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig(
text = resourceReference(R.string.token_swap_promotion_button),
onClick = {},
),
),
)
}
// endregion

View file

@ -1,131 +0,0 @@
package com.tangem.core.ui.components.notifications
import android.content.res.Configuration
import androidx.annotation.DrawableRes
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.runtime.*
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.layout.FixedScale
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.constraintlayout.compose.ConstraintLayout
import com.tangem.core.ui.R
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.res.TangemColorPalette
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
@Composable
fun RingPromoNotification(config: NotificationConfig, modifier: Modifier = Modifier) {
var textHeightDp by remember { mutableStateOf(0.dp) }
Box(
modifier = modifier
.fillMaxWidth()
.clip(shape = TangemTheme.shapes.roundedCornersXMedium)
.background(TangemColorPalette.Dark6),
) {
PromoImage(
backgroundRes = config.backgroundResId,
iconRes = config.iconResId,
modifier = Modifier.height(textHeightDp),
)
PromoText(
title = config.title,
subtitle = config.subtitle,
onSizeChange = { textHeightDp = it },
)
CloseableIconButton(
onClick = config.onCloseClick,
modifier = Modifier.align(alignment = Alignment.TopEnd),
iconTint = TangemTheme.colors.icon.informative,
)
}
}
@Composable
private fun PromoImage(@DrawableRes backgroundRes: Int?, @DrawableRes iconRes: Int, modifier: Modifier = Modifier) {
if (backgroundRes == null) return
ConstraintLayout(
modifier = modifier,
) {
val (background, image) = createRefs()
Image(
painter = painterResource(backgroundRes),
contentDescription = null,
contentScale = FixedScale(2f),
modifier = Modifier
.constrainAs(background) {
top.linkTo(parent.top)
start.linkTo(image.start)
end.linkTo(image.end)
},
)
Image(
painter = painterResource(id = iconRes),
contentDescription = null,
contentScale = ContentScale.FillWidth,
modifier = Modifier
.requiredWidth(width = 60.dp)
.wrapContentHeight(Alignment.Top, unbounded = true)
.constrainAs(image) {
top.linkTo(parent.top, 11.dp)
start.linkTo(parent.start, 9.dp)
},
)
}
}
@Composable
private fun PromoText(title: TextReference?, subtitle: TextReference, onSizeChange: (Dp) -> Unit) {
val density = LocalDensity.current
Box(
modifier = Modifier.onSizeChanged {
with(density) { onSizeChange(it.height.toDp()) }
},
) {
TextsBlock(
title = title,
subtitle = subtitle,
subtitleColor = TangemTheme.colors.text.tertiary,
titleColor = TangemTheme.colors.text.constantWhite,
modifier = Modifier
.wrapContentHeight()
.align(Alignment.CenterStart)
.padding(start = 80.dp, top = 12.dp, end = 12.dp, bottom = 12.dp),
)
}
}
// region Preview
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, locale = "fr")
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun RingPromoNotification_Preview() {
TangemThemePreview {
RingPromoNotification(
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 = { },
),
)
}
}
// endregion

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

View file

@ -1,94 +1,32 @@
package com.tangem.data.promo
import com.tangem.data.promo.converters.PromoResponseConverter
import com.tangem.datasource.api.common.response.getOrThrow
import com.tangem.datasource.api.tangemTech.TangemTechApi
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.SHOULD_SHOW_RING_PROMO_KEY
import com.tangem.datasource.local.preferences.PreferencesKeys.SHOULD_SHOW_SWAP_STORIES_KEY
import com.tangem.datasource.local.preferences.utils.get
import com.tangem.datasource.local.preferences.utils.getSyncOrDefault
import com.tangem.datasource.local.preferences.utils.store
import com.tangem.domain.promo.PromoRepository
import com.tangem.domain.promo.models.PromoBanner
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.coroutines.runCatching
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.flowOf
internal class DefaultPromoRepository(
private val tangemApi: TangemTechApi,
private val appPreferencesStore: AppPreferencesStore,
private val dispatchers: CoroutineDispatcherProvider,
) : PromoRepository {
private val promoResponseConverter = PromoResponseConverter()
override suspend fun getChangellyPromoBanner(): PromoBanner? {
return runCatching(dispatchers.io) {
promoResponseConverter.convert(
tangemApi.getPromotionInfo(CHANGELLY_NAME)
.getOrThrow(),
)
}.getOrNull()
}
override suspend fun getOkxPromoBanner(): PromoBanner? {
return runCatching(dispatchers.io) {
promoResponseConverter.convert(
tangemApi.getPromotionInfo(OKX)
.getOrThrow(),
)
}.getOrNull()
}
override suspend fun getRingPromoBanner(): PromoBanner? {
return runCatching(dispatchers.io) {
promoResponseConverter.convert(
tangemApi.getPromotionInfo(RING)
.getOrThrow(),
)
}.getOrNull()
}
override fun isReadyToShowWalletSwapPromo(): Flow<Boolean> {
return appPreferencesStore.get(IS_WALLET_SWAP_PROMO_OKX_SHOW_KEY, true)
return flowOf(false) // Use it on new promo action
}
override fun isReadyToShowTokenSwapPromo(): Flow<Boolean> {
return appPreferencesStore.get(IS_TOKEN_SWAP_PROMO_OKX_SHOW_KEY, true)
return flowOf(false) // Use it on new promo action
}
override suspend fun setNeverToShowWalletSwapPromo() {
appPreferencesStore.store(
key = IS_WALLET_SWAP_PROMO_OKX_SHOW_KEY,
value = false,
)
// Use it on new promo action
}
override suspend fun setNeverToShowTokenSwapPromo() {
appPreferencesStore.store(
key = IS_TOKEN_SWAP_PROMO_OKX_SHOW_KEY,
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)
// Use it on new promo action
}
override fun isReadyToShowSwapStories(): Flow<Boolean> {
@ -105,10 +43,4 @@ internal class DefaultPromoRepository(
value = false,
)
}
private companion object {
private const val CHANGELLY_NAME = "changelly"
private const val OKX = "okx"
private const val RING = "ring"
}
}

View file

@ -1,10 +1,8 @@
package com.tangem.data.promo.di
import com.tangem.data.promo.DefaultPromoRepository
import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.domain.promo.PromoRepository
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
@ -17,11 +15,7 @@ internal object PromoDataModule {
@Provides
@Singleton
fun providePromoRepository(
tangemTechApi: TangemTechApi,
appPreferencesStore: AppPreferencesStore,
dispatchers: CoroutineDispatcherProvider,
): PromoRepository {
return DefaultPromoRepository(tangemTechApi, appPreferencesStore, dispatchers)
fun providePromoRepository(appPreferencesStore: AppPreferencesStore): PromoRepository {
return DefaultPromoRepository(appPreferencesStore)
}
}

View file

@ -1,17 +1,9 @@
package com.tangem.domain.promo
import com.tangem.domain.promo.models.PromoBanner
import com.tangem.domain.wallets.models.UserWalletId
import kotlinx.coroutines.flow.Flow
interface PromoRepository {
suspend fun getChangellyPromoBanner(): PromoBanner?
suspend fun getOkxPromoBanner(): PromoBanner?
suspend fun getRingPromoBanner(): PromoBanner?
fun isReadyToShowWalletSwapPromo(): Flow<Boolean>
fun isReadyToShowTokenSwapPromo(): Flow<Boolean>
@ -20,10 +12,6 @@ interface PromoRepository {
suspend fun setNeverToShowTokenSwapPromo()
fun isReadyToShowRingPromo(userWalletId: UserWalletId): Flow<Boolean>
suspend fun setNeverToShowRingPromo()
fun isReadyToShowSwapStories(): Flow<Boolean>
suspend fun isReadyToShowSwapStoriesSync(): Boolean

View file

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

View file

@ -36,8 +36,8 @@ sealed class TokenSwapPromoAnalyticsEvent(
}
}
// Use it on new promo action
enum class ProgramName {
OKX,
Ring,
Empty,
}
}

View file

@ -38,7 +38,7 @@ internal class TokenDetailsNotificationsAnalyticsSender(
currency = cryptoCurrency,
)
is TokenDetailsNotification.SwapPromo -> TokenSwapPromoAnalyticsEvent.NoticePromotionBanner(
programName = TokenSwapPromoAnalyticsEvent.ProgramName.OKX,
programName = TokenSwapPromoAnalyticsEvent.ProgramName.Empty, // Use it on new promo action
source = AnalyticsParam.ScreensSources.Token,
)
is TokenDetailsNotification.KaspaIncompleteTransactionWarning -> TokenDetailsAnalyticsEvent.Notice.Reveal(

View file

@ -23,7 +23,6 @@ import com.tangem.core.ui.components.bottomsheets.tokenreceive.TokenReceiveBotto
import com.tangem.core.ui.components.marketprice.MarketPriceBlock
import com.tangem.core.ui.components.marketprice.MarketPriceBlockState
import com.tangem.core.ui.components.notifications.Notification
import com.tangem.core.ui.components.notifications.OkxPromoNotification
import com.tangem.core.ui.components.transactions.state.TxHistoryState
import com.tangem.core.ui.components.transactions.txHistoryItems
import com.tangem.core.ui.components.containers.pullToRefresh.TangemPullToRefreshContainer
@ -92,21 +91,14 @@ internal fun TokenDetailsScreen(state: TokenDetailsState, tokenMarketBlockCompon
key = { it::class.java },
contentType = { it.config::class.java },
itemContent = {
if (it is TokenDetailsNotification.SwapPromo) {
OkxPromoNotification(
config = it.config,
modifier = itemModifier.animateItem(),
)
} else {
Notification(
modifier = itemModifier.animateItem(),
config = it.config,
iconTint = when (it) {
is TokenDetailsNotification.Informational -> TangemTheme.colors.icon.accent
else -> null
},
)
}
Notification(
modifier = itemModifier.animateItem(),
config = it.config,
iconTint = when (it) {
is TokenDetailsNotification.Informational -> TangemTheme.colors.icon.accent
else -> null
},
)
},
)

View file

@ -36,7 +36,6 @@ import com.tangem.domain.card.NetworkHasDerivationUseCase
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.demo.IsDemoCardUseCase
import com.tangem.domain.onramp.model.OnrampSource
import com.tangem.domain.promo.ShouldShowSwapPromoTokenUseCase
import com.tangem.domain.redux.ReduxStateHolder
import com.tangem.domain.staking.GetStakingAvailabilityUseCase
import com.tangem.domain.staking.GetStakingEntryInfoUseCase
@ -74,6 +73,7 @@ import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenBala
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsState
import com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.TokenDetailsStateFactory
import com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.express.ExpressStatusFactory
import com.tangem.domain.promo.ShouldShowSwapPromoTokenUseCase
import com.tangem.features.onramp.OnrampFeatureToggles
import com.tangem.features.tokendetails.impl.R
import com.tangem.utils.Provider
@ -807,7 +807,7 @@ internal class TokenDetailsViewModel @Inject constructor(
analyticsEventsHandler.send(
TokenSwapPromoAnalyticsEvent.PromotionBannerClicked(
source = AnalyticsParam.ScreensSources.Token,
programName = TokenSwapPromoAnalyticsEvent.ProgramName.OKX,
programName = TokenSwapPromoAnalyticsEvent.ProgramName.Empty, // Use it on new promo action
action = TokenSwapPromoAnalyticsEvent.PromotionBannerClicked.BannerAction.Closed,
),
)
@ -820,7 +820,7 @@ internal class TokenDetailsViewModel @Inject constructor(
analyticsEventsHandler.send(
TokenSwapPromoAnalyticsEvent.PromotionBannerClicked(
source = AnalyticsParam.ScreensSources.Token,
programName = TokenSwapPromoAnalyticsEvent.ProgramName.OKX,
programName = TokenSwapPromoAnalyticsEvent.ProgramName.Empty, // Use it on new promo action
action = TokenSwapPromoAnalyticsEvent.PromotionBannerClicked.BannerAction.Clicked,
),
)

View file

@ -4,6 +4,7 @@ import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.AnalyticsEvent
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.domain.tokens.model.analytics.TokenSwapPromoAnalyticsEvent
import com.tangem.domain.tokens.model.analytics.TokenSwapPromoAnalyticsEvent.ProgramName
import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent.MainScreen
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNotification
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
@ -50,7 +51,7 @@ internal class WalletWarningsAnalyticsSender @Inject constructor(
is WalletNotification.NoteMigration -> MainScreen.NotePromo
is WalletNotification.SwapPromo -> TokenSwapPromoAnalyticsEvent.NoticePromotionBanner(
source = AnalyticsParam.ScreensSources.Main,
programName = TokenSwapPromoAnalyticsEvent.ProgramName.OKX,
programName = TokenSwapPromoAnalyticsEvent.ProgramName.Empty, // Use it on new promo action
)
is WalletNotification.UnlockWallets -> null // See [SelectedWalletAnalyticsSender]
is WalletNotification.Informational.NoAccount,
@ -58,10 +59,6 @@ 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,
)
is WalletNotification.Critical.SeedPhraseNotification -> MainScreen.NoticeSeedPhraseSupport
}
}

View file

@ -4,9 +4,6 @@ import com.tangem.domain.common.CardTypesResolver
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.core.lce.Lce
import com.tangem.domain.demo.IsDemoCardUseCase
import com.tangem.domain.promo.PromoRepository
import com.tangem.domain.promo.ShouldShowRingPromoUseCase
import com.tangem.domain.promo.models.PromoBanner
import com.tangem.domain.settings.IsReadyToShowRateAppUseCase
import com.tangem.domain.tokens.error.TokenListError
import com.tangem.domain.tokens.model.CryptoCurrency
@ -22,7 +19,6 @@ import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flow
import javax.inject.Inject
import kotlin.collections.count
@ -32,8 +28,6 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
private val tokenListStore: MultiWalletTokenListStore,
private val isDemoCardUseCase: IsDemoCardUseCase,
private val isReadyToShowRateAppUseCase: IsReadyToShowRateAppUseCase,
private val shouldShowRingPromoUseCase: ShouldShowRingPromoUseCase,
private val promoRepository: PromoRepository,
private val isNeedToBackupUseCase: IsNeedToBackupUseCase,
private val backupValidator: BackupValidator,
private val seedPhraseNotificationUseCase: SeedPhraseNotificationUseCase,
@ -43,25 +37,13 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
fun create(userWallet: UserWallet, clickIntents: WalletClickIntents): Flow<ImmutableList<WalletNotification>> {
val cardTypesResolver = userWallet.scanResponse.cardTypesResolver
val promoFlow = flow { emit(promoRepository.getRingPromoBanner()) }
return combine(
tokenListStore.getOrThrow(userWallet.walletId),
isReadyToShowRateAppUseCase(),
isNeedToBackupUseCase(userWallet.walletId),
shouldShowRingPromoUseCase(userWalletId = userWallet.walletId),
promoFlow,
seedPhraseNotificationUseCase(userWalletId = userWallet.walletId),
) {
val maybeTokenList = it[0] as Lce<TokenListError, TokenList>
val isReadyToShowRating = it[1] as Boolean
val isNeedToBackup = it[2] as Boolean
val shouldShowPromo = it[3] as Boolean
val promoBanner = it[4] as? PromoBanner
val seedPhraseIssueStatus = it[5] as Boolean
flow = tokenListStore.getOrThrow(userWallet.walletId),
flow2 = isReadyToShowRateAppUseCase(),
flow3 = isNeedToBackupUseCase(userWallet.walletId),
flow4 = seedPhraseNotificationUseCase(userWalletId = userWallet.walletId),
) { maybeTokenList, isReadyToShowRating, isNeedToBackup, seedPhraseIssueStatus ->
buildList {
addRingPromoNotification(shouldShowPromo, promoBanner, clickIntents)
addCriticalNotifications(userWallet, seedPhraseIssueStatus, clickIntents)
addInformationalNotifications(cardTypesResolver, maybeTokenList, clickIntents)
@ -79,19 +61,6 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
}
}
private fun MutableList<WalletNotification>.addRingPromoNotification(
shouldShowPromo: Boolean,
promoBanner: PromoBanner?,
clickIntents: WalletClickIntents,
) {
promoBanner ?: return
val promoNotification = WalletNotification.RingPromo(onCloseClick = clickIntents::onCloseRingPromoClick)
addIf(
element = promoNotification,
condition = shouldShowPromo && promoBanner.isActive,
)
}
private fun MutableList<WalletNotification>.addCriticalNotifications(
userWallet: UserWallet,
seedPhraseIssueStatus: Boolean,

View file

@ -206,16 +206,6 @@ sealed class WalletNotification(val config: NotificationConfig) {
),
)
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,
),
)
data class NoteMigration(val onClick: () -> Unit) : WalletNotification(
config = NotificationConfig(
title = resourceReference(R.string.wallet_promo_banner_title),

View file

@ -5,8 +5,6 @@ import androidx.compose.foundation.lazy.items
import androidx.compose.ui.Modifier
import com.tangem.core.ui.components.notifications.NoteMigrationNotification
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
@ -28,16 +26,7 @@ internal fun LazyListScope.notifications(configs: ImmutableList<WalletNotificati
// TODO develop promo banner general component
when (it) {
is WalletNotification.SwapPromo -> {
OkxPromoNotification(
config = it.config,
modifier = modifier.animateItem(),
)
}
is WalletNotification.RingPromo -> {
RingPromoNotification(
config = it.config,
modifier = modifier.animateItem(),
)
// Use it on new promo action
}
is WalletNotification.NoteMigration -> {
NoteMigrationNotification(

View file

@ -11,7 +11,6 @@ import com.tangem.domain.card.SetCardWasScannedUseCase
import com.tangem.domain.feedback.GetCardInfoUseCase
import com.tangem.domain.feedback.SendFeedbackEmailUseCase
import com.tangem.domain.feedback.models.FeedbackEmailType
import com.tangem.domain.promo.ShouldShowRingPromoUseCase
import com.tangem.domain.promo.ShouldShowSwapPromoWalletUseCase
import com.tangem.domain.redux.LegacyAction
import com.tangem.domain.redux.ReduxStateHolder
@ -66,8 +65,6 @@ internal interface WalletWarningsClickIntents {
fun onCloseSwapPromoClick()
fun onCloseRingPromoClick()
fun onSupportClick()
fun onNoteMigrationButtonClick(url: String)
@ -94,7 +91,6 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor(
private val reduxStateHolder: ReduxStateHolder,
private val dispatchers: CoroutineDispatcherProvider,
private val shouldShowSwapPromoWalletUseCase: ShouldShowSwapPromoWalletUseCase,
private val shouldShowRingPromoUseCase: ShouldShowRingPromoUseCase,
private val getCardInfoUseCase: GetCardInfoUseCase,
private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase,
private val seedPhraseNotificationUseCase: SeedPhraseNotificationUseCase,
@ -249,7 +245,7 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor(
analyticsEventHandler.send(
TokenSwapPromoAnalyticsEvent.PromotionBannerClicked(
source = AnalyticsParam.ScreensSources.Main,
programName = TokenSwapPromoAnalyticsEvent.ProgramName.OKX,
programName = TokenSwapPromoAnalyticsEvent.ProgramName.Empty, // Use it on new promo action
action = TokenSwapPromoAnalyticsEvent.PromotionBannerClicked.BannerAction.Closed,
),
)
@ -258,20 +254,6 @@ 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 onSupportClick() {
val scanResponse = getSelectedUserWallet()?.scanResponse ?: return
val cardInfo = getCardInfoUseCase(scanResponse).getOrNull() ?: return