Updated on 2026-08-14
This commit is contained in:
parent
3993e1d690
commit
bb58d027c6
19 changed files with 200 additions and 19 deletions
|
|
@ -123,6 +123,14 @@ internal object SettingsDomainModule {
|
|||
return ShouldShowSwapPromoWalletUseCase(promoSettingsRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideShouldShowRingPromoUseCase(
|
||||
promoSettingsRepository: PromoSettingsRepository,
|
||||
): ShouldShowRingPromoUseCase {
|
||||
return ShouldShowRingPromoUseCase(promoSettingsRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideShouldShowTravalaPromoWalletUseCase(
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
)
|
||||
}
|
||||
}
|
||||
BIN
core/ui/src/main/res/drawable/img_card_with_ring.webp
Normal file
BIN
core/ui/src/main/res/drawable/img_card_with_ring.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 58 KiB |
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
@ -17,6 +17,7 @@ dependencies {
|
|||
|
||||
implementation(projects.domain.balanceHiding.models)
|
||||
implementation(projects.domain.settings)
|
||||
implementation(projects.domain.wallets.models)
|
||||
|
||||
implementation(deps.androidx.datastore)
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -7,4 +7,5 @@ dependencies {
|
|||
implementation(deps.kotlin.coroutines)
|
||||
implementation(deps.arrow.core)
|
||||
implementation(projects.domain.balanceHiding.models)
|
||||
implementation(projects.domain.wallets.models)
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
|
|
@ -39,5 +39,6 @@ sealed class TokenSwapPromoAnalyticsEvent(
|
|||
enum class ProgramName {
|
||||
Travala,
|
||||
OKX,
|
||||
Ring,
|
||||
}
|
||||
}
|
||||
|
|
@ -9,4 +9,6 @@ interface PromoRepository {
|
|||
suspend fun getTravalaPromoBanner(): PromoBanner?
|
||||
|
||||
suspend fun getOkxPromoBanner(): PromoBanner?
|
||||
|
||||
suspend fun getRingPromoBanner(): PromoBanner?
|
||||
}
|
||||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 |
Loading…
Add table
Add a link
Reference in a new issue