Updated on 2026-08-14

This commit is contained in:
Tangem 2024-05-14 13:40:11 +01:00
parent c1c244753e
commit 7653ea926d
27 changed files with 473 additions and 69 deletions

View file

@ -7,8 +7,8 @@ import com.tangem.domain.balancehiding.UpdateBalanceHidingSettingsUseCase
import com.tangem.domain.balancehiding.repositories.BalanceHidingRepository
import com.tangem.domain.settings.*
import com.tangem.domain.settings.repositories.AppRatingRepository
import com.tangem.domain.settings.repositories.PromoSettingsRepository
import com.tangem.domain.settings.repositories.SettingsRepository
import com.tangem.domain.settings.repositories.SwapPromoRepository
import com.tangem.tap.domain.TangemSdkManager
import com.tangem.tap.domain.settings.DefaultLegacySettingsRepository
import dagger.Module
@ -108,17 +108,25 @@ internal object SettingsDomainModule {
@Provides
@ViewModelScoped
fun provideShouldShowSwapPromoWalletUseCase(
swapPromoRepository: SwapPromoRepository,
promoSettingsRepository: PromoSettingsRepository,
): ShouldShowSwapPromoWalletUseCase {
return ShouldShowSwapPromoWalletUseCase(swapPromoRepository)
return ShouldShowSwapPromoWalletUseCase(promoSettingsRepository)
}
@Provides
@ViewModelScoped
fun provideShouldShowTravalaPromoWalletUseCase(
promoSettingsRepository: PromoSettingsRepository,
): ShouldShowTravalaPromoWalletUseCase {
return ShouldShowTravalaPromoWalletUseCase(promoSettingsRepository)
}
@Provides
@ViewModelScoped
fun provideShouldShowSwapPromoTokenUseCase(
swapPromoRepository: SwapPromoRepository,
promoSettingsRepository: PromoSettingsRepository,
): ShouldShowSwapPromoTokenUseCase {
return ShouldShowSwapPromoTokenUseCase(swapPromoRepository)
return ShouldShowSwapPromoTokenUseCase(promoSettingsRepository)
}
@Provides

View file

@ -13,6 +13,7 @@ data class PromotionInfoResponse(
data class BannerState(
@Json(name = "timeline") val timeline: Timeline,
@Json(name = "status") val status: String,
@Json(name = "link") val link: String?,
)
@JsonClass(generateAdapter = true)

View file

@ -55,6 +55,10 @@ object PreferencesKeys {
booleanPreferencesKey(name = "isTokenSwapPromoChangellyShown")
}
val IS_WALLET_TRAVALA_PROMO_SHOWN_KEY by lazy {
booleanPreferencesKey(name = "isWalletTravalaPromoShown")
}
val FEATURE_TOGGLES_KEY by lazy { stringPreferencesKey(name = "featureToggles") }
val WAS_TWINS_ONBOARDING_SHOWN by lazy { booleanPreferencesKey(name = "twinsOnboardingShown") }

View file

@ -267,6 +267,9 @@
<string name="main_swap_changelly_promotion_message">Обменивайте свои токены с %1$s комиссии провайдера через Changelly с %2$s по %3$s февраля.</string>
<string name="main_swap_changelly_promotion_title">Обмен с Changelly, %s комиссии</string>
<string name="main_tokens">Токены</string>
<string name="main_travala_promotion_button">Забронировать</string>
<string name="main_travala_promotion_description" formatted="false">Оплатите его криптой и сэкономьте **50 долларов** через нашего партнера Travala: **%1s - %2s**</string>
<string name="main_travala_promotion_title">Забронируйте отпуск с Tangem</string>
<string name="manage_tokens_add">Добавить</string>
<string name="manage_tokens_edit">Изменить</string>
<string name="manage_tokens_list_header_title">Рыночная капитализация</string>

View file

@ -267,6 +267,9 @@
<string name="main_swap_changelly_promotion_message">Enjoy %1$s service fees on swaps via Changelly from February %2$s-%3$s</string>
<string name="main_swap_changelly_promotion_title">Swap with Changelly, %s fees</string>
<string name="main_tokens">Tokens</string>
<string name="main_travala_promotion_button">Book now</string>
<string name="main_travala_promotion_description" formatted="false">Save **50 USDT** while booking via our partner Travala: **%1s - %2s**</string>
<string name="main_travala_promotion_title">Book your holidays with Tangem and pay in crypto</string>
<string name="manage_tokens_add">Add</string>
<string name="manage_tokens_edit">Edit</string>
<string name="manage_tokens_list_header_title">Coin market cap</string>

View file

@ -47,7 +47,11 @@ fun NotificationWithBackground(config: NotificationConfig, modifier: Modifier =
modifier = modifier
.defaultMinSize(minHeight = TangemTheme.dimens.size62)
.fillMaxWidth()
.clip(TangemTheme.shapes.roundedCornersXMedium),
.clip(TangemTheme.shapes.roundedCornersXMedium)
.clickable(
enabled = config.onClick != null,
onClick = config.onClick ?: {},
),
) {
val (iconRef, titleRef, subtitleRef, closeIconRef, buttonRef, backgroundRef) = createRefs()

View file

@ -0,0 +1,215 @@
package com.tangem.core.ui.components.notifications
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
import androidx.compose.material.Icon
import androidx.compose.material.Text
import androidx.compose.material.ripple.rememberRipple
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.layout.ScaleFactor
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.style.LineBreak
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.R
import com.tangem.core.ui.components.SpacerH8
import com.tangem.core.ui.components.buttons.common.TangemButton
import com.tangem.core.ui.components.buttons.common.TangemButtonColors
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.wrappedList
import com.tangem.core.ui.res.TangemColorPalette.White
import com.tangem.core.ui.res.TangemTheme
/**
* Travala notification with image background
* @see <a href="https://www.figma.com/file/Vs6SkVsFnUPsSCNwlnVf5U/Android-%E2%80%93-UI?type=design&node-id=11690-12057&mode=design&t=eFnsA9sNytcQIoQ4-4">Travala Promo</a>
*/
@Suppress("LongMethod", "DestructuringDeclarationWithTooManyEntries")
@Composable
fun TravalaNotificationWithBackground(config: NotificationConfig, modifier: Modifier = Modifier) {
val button = config.buttonsState as? NotificationConfig.ButtonsState.SecondaryButtonConfig
Box(
modifier = modifier
.defaultMinSize(minHeight = TangemTheme.dimens.size62)
.background(Color.Black)
.fillMaxWidth()
.clip(TangemTheme.shapes.roundedCornersXMedium)
.clickable(
enabled = config.onClick != null,
onClick = config.onClick ?: {},
),
propagateMinConstraints = true,
contentAlignment = Alignment.TopStart,
) {
val density = LocalDensity.current
Image(
painter = painterResource(R.drawable.img_travala_banner_promo_background),
contentDescription = null,
contentScale = TravalaBackgroundScale(density),
alignment = Alignment.TopStart,
modifier = Modifier
.matchParentSize()
.wrapContentSize(unbounded = true, align = Alignment.TopStart)
.align(Alignment.TopStart),
)
Image(
painter = painterResource(R.drawable.img_travala_banner_promo_background_2),
contentDescription = null,
contentScale = TravalaBackgroundScale(density),
alignment = Alignment.TopStart,
modifier = Modifier
.matchParentSize()
.wrapContentSize(unbounded = true, align = Alignment.TopEnd)
.align(Alignment.TopEnd),
)
Column {
Row {
Box(modifier = Modifier.size(87.dp))
Column(
Modifier
.weight(1f)
.padding(top = TangemTheme.dimens.spacing12),
) {
Text(
text = config.title.resolveReference(),
style = TangemTheme.typography.button.copy(
lineBreak = LineBreak.Heading,
),
color = TangemTheme.colors.text.constantWhite,
)
SpacerH8()
Text(
text = formatSubtitle(config.subtitle.resolveReference()),
style = TangemTheme.typography.caption2.copy(
lineBreak = LineBreak.Heading,
),
color = TangemTheme.colors.text.constantWhite,
)
}
Icon(
painter = painterResource(id = R.drawable.ic_close_24),
contentDescription = null,
tint = TangemTheme.colors.text.constantWhite,
modifier = Modifier
.padding(
top = TangemTheme.dimens.spacing12,
end = TangemTheme.dimens.spacing12,
start = TangemTheme.dimens.spacing2,
)
.size(TangemTheme.dimens.size16)
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = rememberRipple(bounded = false),
) {
config.onCloseClick?.invoke()
},
)
}
TangemButton(
text = button?.text?.resolveReference().orEmpty(),
icon = TangemButtonIconPosition.None,
onClick = button?.onClick ?: {},
colors = TangemButtonColors(
backgroundColor = White.copy(alpha = 0.3f),
contentColor = White,
disabledBackgroundColor = TangemTheme.colors.button.disabled,
disabledContentColor = TangemTheme.colors.text.disabled,
),
enabled = true,
showProgress = false,
modifier = Modifier
.padding(TangemTheme.dimens.spacing12)
.fillMaxWidth(),
)
}
}
}
private const val TRAVALA_BACKGROUND_SRC_IMG_SCALE = 4
private class TravalaBackgroundScale(
val density: Density,
) : ContentScale {
override fun computeScaleFactor(srcSize: Size, dstSize: Size): ScaleFactor {
with(density) {
val originalWidth = (srcSize.width / TRAVALA_BACKGROUND_SRC_IMG_SCALE).dp.toPx()
val widthScale = originalWidth / srcSize.width
val originalHeight = (srcSize.height / TRAVALA_BACKGROUND_SRC_IMG_SCALE).dp.toPx()
val heightScale = originalHeight / srcSize.height
return ScaleFactor(widthScale, heightScale)
}
}
}
@Composable
private fun formatSubtitle(subtitle: String): AnnotatedString {
val pattern = Regex("\\*\\*(.*?)\\*\\*")
var startIndex = 0
val annotatedString = buildAnnotatedString {
pattern.findAll(subtitle).forEach { matchResult ->
val index = matchResult.range.first
val matchedValue = matchResult.groups[1]?.value ?: ""
// appends unformatted part
append(subtitle.substring(startIndex, index))
// applies style on ^^-wrapped parts
withStyle(SpanStyle(fontWeight = TangemTheme.typography.caption1.fontWeight)) {
append(matchedValue)
}
// goes to next part
startIndex = matchResult.range.last + 1
}
// appends remaining ending if exists
append(subtitle.substring(startIndex))
}
return annotatedString
}
//region preview
@Preview
@Composable
private fun TravalaNotificationWithBackgroundPreview() {
TangemTheme {
TravalaNotificationWithBackground(
config = NotificationConfig(
title = resourceReference(
id = R.string.main_travala_promotion_title,
),
subtitle = resourceReference(
id = R.string.main_travala_promotion_description,
formatArgs = wrappedList("May 13", "June 12"),
),
iconResId = R.drawable.img_swap_promo,
backgroundResId = R.drawable.img_travala_banner_promo_background,
buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig(
text = resourceReference(id = R.string.token_swap_promotion_button),
onClick = {},
),
),
)
}
}
//endregion

View file

@ -1,5 +1,6 @@
package com.tangem.core.ui.utils
import android.text.format.DateFormat
import org.joda.time.DateTime
import org.joda.time.format.DateTimeFormat
import org.joda.time.format.DateTimeFormatter
@ -55,6 +56,16 @@ object DateTimeFormatters {
.withLocale(Locale.getDefault())
}
/**
* In API version < 24, there may be some problems with getting the best date and time format pattern.
*/
val dateMMMMd: DateTimeFormatter by lazy {
DateTimeFormatterBuilder()
.appendPattern(DateFormat.getBestDateTimePattern(Locale.getDefault(), "MMMM d"))
.toFormatter()
.withLocale(Locale.getDefault())
}
val dateTimeFormatter: DateTimeFormatter by lazy {
DateTimeFormat.forPattern("dd.MM.yyyy HH:mm")
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 441 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 409 KiB

View file

@ -23,7 +23,17 @@ internal class DefaultPromoRepository(
}.getOrNull()
}
override suspend fun getTravalaPromoBanner(): PromoBanner? {
return runCatching(dispatchers.io) {
promoResponseConverter.convert(
tangemApi.getPromotionInfo(TRAVALA)
.getOrThrow(),
)
}.getOrNull()
}
private companion object {
private const val CHANGELLY_NAME = "changelly"
private const val TRAVALA = "travala"
}
}

View file

@ -13,6 +13,7 @@ class PromoResponseConverter : Converter<PromotionInfoResponse, PromoBanner?> {
name = value.name,
bannerState = PromoBanner.BannerState(
status = bannerState.status,
link = bannerState.link,
timeline = PromoBanner.Timeline(
start = DateTime.parse(bannerState.timeline.start),
end = DateTime.parse(bannerState.timeline.end),

View file

@ -3,36 +3,48 @@ package com.tangem.data.settings
import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.datasource.local.preferences.PreferencesKeys.IS_TOKEN_SWAP_PROMO_CHANGELLY_SHOW_KEY
import com.tangem.datasource.local.preferences.PreferencesKeys.IS_WALLET_SWAP_PROMO_CHANGELLY_SHOW_KEY
import com.tangem.datasource.local.preferences.PreferencesKeys.IS_WALLET_TRAVALA_PROMO_SHOWN_KEY
import com.tangem.datasource.local.preferences.utils.get
import com.tangem.datasource.local.preferences.utils.store
import com.tangem.domain.settings.repositories.SwapPromoRepository
import com.tangem.domain.settings.repositories.PromoSettingsRepository
import kotlinx.coroutines.flow.Flow
/**
* Repository for showing swap promo notification.
*/
class DefaultSwapPromoRepository(
class DefaultPromoSettingsRepository(
private val appPreferencesStore: AppPreferencesStore,
) : SwapPromoRepository {
override fun isReadyToShowWalletPromo(): Flow<Boolean> {
) : PromoSettingsRepository {
override fun isReadyToShowWalletSwapPromo(): Flow<Boolean> {
return appPreferencesStore.get(IS_WALLET_SWAP_PROMO_CHANGELLY_SHOW_KEY, true)
}
override fun isReadyToShowTokenPromo(): Flow<Boolean> {
override fun isReadyToShowTokenSwapPromo(): Flow<Boolean> {
return appPreferencesStore.get(IS_TOKEN_SWAP_PROMO_CHANGELLY_SHOW_KEY, true)
}
override suspend fun setNeverToShowWalletPromo() {
override suspend fun setNeverToShowWalletSwapPromo() {
appPreferencesStore.store(
key = IS_WALLET_SWAP_PROMO_CHANGELLY_SHOW_KEY,
value = false,
)
}
override suspend fun setNeverToShowTokenPromo() {
override suspend fun setNeverToShowTokenSwapPromo() {
appPreferencesStore.store(
key = IS_TOKEN_SWAP_PROMO_CHANGELLY_SHOW_KEY,
value = false,
)
}
override fun isReadyToShowWalletTravalaPromo(): Flow<Boolean> {
return appPreferencesStore.get(IS_WALLET_TRAVALA_PROMO_SHOWN_KEY, true)
}
override suspend fun setNeverToShowWalletTravalaPromo() {
appPreferencesStore.store(
key = IS_WALLET_TRAVALA_PROMO_SHOWN_KEY,
value = false,
)
}
}

View file

@ -2,12 +2,12 @@ package com.tangem.data.settings.di
import com.tangem.data.settings.DefaultAppRatingRepository
import com.tangem.data.settings.DefaultSettingsRepository
import com.tangem.data.settings.DefaultSwapPromoRepository
import com.tangem.data.settings.DefaultPromoSettingsRepository
import com.tangem.data.source.preferences.PreferencesDataSource
import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.domain.settings.repositories.AppRatingRepository
import com.tangem.domain.settings.repositories.SettingsRepository
import com.tangem.domain.settings.repositories.SwapPromoRepository
import com.tangem.domain.settings.repositories.PromoSettingsRepository
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.Module
import dagger.Provides
@ -41,7 +41,7 @@ internal object SettingsDataModule {
@Provides
@Singleton
fun provideSwapPromoRepository(appPreferencesStore: AppPreferencesStore): SwapPromoRepository {
return DefaultSwapPromoRepository(appPreferencesStore = appPreferencesStore)
fun providePromoSettingsSettingsRepository(appPreferencesStore: AppPreferencesStore): PromoSettingsRepository {
return DefaultPromoSettingsRepository(appPreferencesStore = appPreferencesStore)
}
}

View file

@ -1,11 +1,11 @@
package com.tangem.domain.settings
import com.tangem.domain.settings.repositories.SwapPromoRepository
import com.tangem.domain.settings.repositories.PromoSettingsRepository
import kotlinx.coroutines.flow.Flow
class ShouldShowSwapPromoTokenUseCase(private val swapPromoRepository: SwapPromoRepository) {
class ShouldShowSwapPromoTokenUseCase(private val promoSettingsRepository: PromoSettingsRepository) {
operator fun invoke(): Flow<Boolean> = swapPromoRepository.isReadyToShowTokenPromo()
operator fun invoke(): Flow<Boolean> = promoSettingsRepository.isReadyToShowTokenSwapPromo()
suspend fun neverToShow() = swapPromoRepository.setNeverToShowTokenPromo()
suspend fun neverToShow() = promoSettingsRepository.setNeverToShowTokenSwapPromo()
}

View file

@ -1,11 +1,11 @@
package com.tangem.domain.settings
import com.tangem.domain.settings.repositories.SwapPromoRepository
import com.tangem.domain.settings.repositories.PromoSettingsRepository
import kotlinx.coroutines.flow.Flow
class ShouldShowSwapPromoWalletUseCase(private val swapPromoRepository: SwapPromoRepository) {
class ShouldShowSwapPromoWalletUseCase(private val promoSettingsRepository: PromoSettingsRepository) {
operator fun invoke(): Flow<Boolean> = swapPromoRepository.isReadyToShowWalletPromo()
operator fun invoke(): Flow<Boolean> = promoSettingsRepository.isReadyToShowWalletSwapPromo()
suspend fun neverToShow() = swapPromoRepository.setNeverToShowWalletPromo()
suspend fun neverToShow() = promoSettingsRepository.setNeverToShowWalletSwapPromo()
}

View file

@ -0,0 +1,11 @@
package com.tangem.domain.settings
import com.tangem.domain.settings.repositories.PromoSettingsRepository
import kotlinx.coroutines.flow.Flow
class ShouldShowTravalaPromoWalletUseCase(private val promoSettingsRepository: PromoSettingsRepository) {
operator fun invoke(): Flow<Boolean> = promoSettingsRepository.isReadyToShowWalletTravalaPromo()
suspend fun neverToShow() = promoSettingsRepository.setNeverToShowWalletTravalaPromo()
}

View file

@ -0,0 +1,17 @@
package com.tangem.domain.settings.repositories
import kotlinx.coroutines.flow.Flow
interface PromoSettingsRepository {
fun isReadyToShowWalletSwapPromo(): Flow<Boolean>
fun isReadyToShowTokenSwapPromo(): Flow<Boolean>
suspend fun setNeverToShowWalletSwapPromo()
suspend fun setNeverToShowTokenSwapPromo()
fun isReadyToShowWalletTravalaPromo(): Flow<Boolean>
suspend fun setNeverToShowWalletTravalaPromo()
}

View file

@ -1,13 +0,0 @@
package com.tangem.domain.settings.repositories
import kotlinx.coroutines.flow.Flow
interface SwapPromoRepository {
fun isReadyToShowWalletPromo(): Flow<Boolean>
fun isReadyToShowTokenPromo(): Flow<Boolean>
suspend fun setNeverToShowWalletPromo()
suspend fun setNeverToShowTokenPromo()
}

View file

@ -12,6 +12,7 @@ data class PromoBanner(
data class BannerState(
val timeline: Timeline,
val status: String,
val link: String?,
)
data class Timeline(

View file

@ -5,4 +5,6 @@ import com.tangem.domain.promo.PromoBanner
interface PromoRepository {
suspend fun getChangellyPromoBanner(): PromoBanner?
suspend fun getTravalaPromoBanner(): PromoBanner?
}

View file

@ -121,4 +121,38 @@ sealed class WalletScreenAnalyticsEvent {
data object DeleteWalletTapped : MainScreen(event = "Button - Delete Wallet Tapped")
}
sealed class Promotion(
event: String,
params: Map<String, String> = mapOf(),
) : AnalyticsEvent(category = "Promotion", event = event, params = params) {
class NoticePromotionBanner(
source: AnalyticsParam.ScreensSources,
programName: String,
) : Promotion(
event = "Notice - Promotion Banner",
params = mapOf(
AnalyticsParam.SOURCE to source.value,
"Program Name" to programName,
),
)
class PromotionBannerClicked(
source: AnalyticsParam.ScreensSources,
programName: String,
action: BannerAction,
) : Promotion(
event = "Promo Banner Clicked",
params = mapOf(
AnalyticsParam.SOURCE to source.value,
"Program Name" to programName,
"Action" to action.action,
),
) {
sealed class BannerAction(val action: String) {
data object Clicked : BannerAction(action = "Clicked")
data object Closed : BannerAction(action = "Closed")
}
}
}
}

View file

@ -2,6 +2,8 @@ package com.tangem.feature.wallet.presentation.wallet.analytics.utils
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.AnalyticsEvent
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent
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
@ -44,6 +46,10 @@ internal class WalletWarningsAnalyticsSender @Inject constructor(
is WalletNotification.Informational.MissingAddresses -> MainScreen.MissingAddresses
is WalletNotification.RateApp -> MainScreen.HowDoYouLikeTangem
is WalletNotification.Critical.BackupError -> MainScreen.BackupError
is WalletNotification.TravalaPromo -> WalletScreenAnalyticsEvent.Promotion.NoticePromotionBanner(
source = AnalyticsParam.ScreensSources.Main,
programName = "Travala",
)
is WalletNotification.UnlockWallets -> null // See [SelectedWalletAnalyticsSender]
is WalletNotification.Informational.NoAccount,
is WalletNotification.Warning.LowSignatures,

View file

@ -6,7 +6,7 @@ import com.tangem.domain.common.util.cardTypesResolver
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.ShouldShowTravalaPromoWalletUseCase
import com.tangem.domain.tokens.GetTokenListUseCase
import com.tangem.domain.tokens.error.TokenListError
import com.tangem.domain.tokens.model.CryptoCurrency
@ -34,7 +34,7 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
private val getTokenListUseCase: GetTokenListUseCase,
private val isDemoCardUseCase: IsDemoCardUseCase,
private val isReadyToShowRateAppUseCase: IsReadyToShowRateAppUseCase,
private val shouldShowSwapPromoWalletUseCase: ShouldShowSwapPromoWalletUseCase,
private val shouldShowTravalaPromoWalletUseCase: ShouldShowTravalaPromoWalletUseCase,
private val promoRepository: PromoRepository,
private val isNeedToBackupUseCase: IsNeedToBackupUseCase,
private val backupValidator: BackupValidator,
@ -45,18 +45,18 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
fun create(userWallet: UserWallet, clickIntents: WalletClickIntents): Flow<ImmutableList<WalletNotification>> {
val cardTypesResolver = userWallet.scanResponse.cardTypesResolver
val promoFlow = flow { emit(promoRepository.getChangellyPromoBanner()) }
val travalaPromoFlow = flow { emit(promoRepository.getTravalaPromoBanner()) }
return combine(
flow = getTokenListUseCase(userWallet.walletId).conflate(),
flow2 = isReadyToShowRateAppUseCase().conflate(),
flow3 = isNeedToBackupUseCase(userWallet.walletId).conflate(),
flow4 = shouldShowSwapPromoWalletUseCase().conflate(),
flow5 = promoFlow,
) { maybeTokenList, isReadyToShowRating, isNeedToBackup, shouldShowPromo, promoBanner ->
flow4 = shouldShowTravalaPromoWalletUseCase().conflate(),
flow5 = travalaPromoFlow.conflate(),
) { maybeTokenList, isReadyToShowRating, isNeedToBackup, shouldShowTravalaPromo, promoBanner ->
readyForRateAppNotification = true
buildList {
addSwapPromoNotification(shouldShowPromo, promoBanner, clickIntents)
addTravalaPromoNotification(shouldShowTravalaPromo, promoBanner, clickIntents)
addCriticalNotifications(userWallet)
@ -69,16 +69,18 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
}
}
private fun MutableList<WalletNotification>.addSwapPromoNotification(
private fun MutableList<WalletNotification>.addTravalaPromoNotification(
shouldShowPromo: Boolean,
promoBanner: PromoBanner?,
clickIntents: WalletClickIntents,
) {
promoBanner ?: return
val promoNotification = WalletNotification.SwapPromo(
val promoNotification = WalletNotification.TravalaPromo(
startDateTime = promoBanner.bannerState.timeline.start,
endDateTime = promoBanner.bannerState.timeline.end,
onCloseClick = clickIntents::onCloseSwapPromoClick,
bannerLink = promoBanner.bannerState.link,
onBookNowButtonClick = clickIntents::onTravalaPromoClick,
onCloseClick = clickIntents::onCloseTravalaPromoClick,
)
addIf(
element = promoNotification,

View file

@ -2,10 +2,8 @@ package com.tangem.feature.wallet.presentation.wallet.state.model
import androidx.compose.runtime.Immutable
import com.tangem.core.ui.components.notifications.NotificationConfig
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.pluralReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.core.ui.extensions.*
import com.tangem.core.ui.utils.DateTimeFormatters
import com.tangem.feature.wallet.impl.R
import org.joda.time.DateTime
@ -173,6 +171,34 @@ sealed class WalletNotification(val config: NotificationConfig) {
),
)
data class TravalaPromo(
val startDateTime: DateTime,
val endDateTime: DateTime,
val bannerLink: String?,
val onBookNowButtonClick: (String?) -> Unit,
val onCloseClick: () -> Unit,
) : WalletNotification(
config = NotificationConfig(
title = resourceReference(id = R.string.main_travala_promotion_title),
subtitle = resourceReference(
id = R.string.main_travala_promotion_description,
wrappedList(
DateTimeFormatters.formatDate(startDateTime, DateTimeFormatters.dateMMMMd),
DateTimeFormatters.formatDate(endDateTime, DateTimeFormatters.dateMMMMd),
),
),
// Stub. Travala has its own Composable implementation with correct img
iconResId = R.drawable.ic_star_24,
// Stub. Travala has its own Composable implementation with correct img
backgroundResId = R.drawable.ic_star_24,
buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig(
onClick = { onBookNowButtonClick(bannerLink) },
text = resourceReference(R.string.main_travala_promotion_button),
),
onCloseClick = onCloseClick,
),
)
data class SwapPromo(
val startDateTime: DateTime,
val endDateTime: DateTime,

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.NotificationWithBackground
import com.tangem.core.ui.components.notifications.TravalaNotificationWithBackground
import com.tangem.core.ui.res.TangemTheme
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNotification
import kotlinx.collections.immutable.ImmutableList
@ -25,12 +26,21 @@ internal fun LazyListScope.notifications(configs: ImmutableList<WalletNotificati
key = { it::class.java },
contentType = { it::class.java },
itemContent = {
if (it is WalletNotification.SwapPromo) {
// TODO develop promo banner general component
when (it) {
is WalletNotification.SwapPromo -> {
NotificationWithBackground(
config = it.config,
modifier = modifier.animateItemPlacement(),
)
} else {
}
is WalletNotification.TravalaPromo -> {
TravalaNotificationWithBackground(
config = it.config,
modifier = modifier.animateItemPlacement(),
)
}
else -> {
Notification(
config = it.config,
modifier = modifier.animateItemPlacement(),
@ -43,6 +53,7 @@ internal fun LazyListScope.notifications(configs: ImmutableList<WalletNotificati
},
)
}
}
},
)
}

View file

@ -11,6 +11,7 @@ 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.tokens.FetchTokenListUseCase
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.wallets.legacy.UserWalletsListManager.Lockable.UnlockType
@ -19,6 +20,7 @@ import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
import com.tangem.domain.wallets.usecase.UnlockWalletsUseCase
import com.tangem.feature.wallet.impl.R
import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent
import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent.Basic
import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent.MainScreen
import com.tangem.feature.wallet.presentation.wallet.domain.ScanCardToUnlockWalletClickHandler
@ -56,6 +58,10 @@ internal interface WalletWarningsClickIntents {
fun onCloseRateAppWarningClick()
fun onCloseSwapPromoClick()
fun onTravalaPromoClick(link: String?)
fun onCloseTravalaPromoClick()
}
@Suppress("LongParameterList")
@ -75,6 +81,7 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor(
private val reduxStateHolder: ReduxStateHolder,
private val dispatchers: CoroutineDispatcherProvider,
private val shouldShowSwapPromoWalletUseCase: ShouldShowSwapPromoWalletUseCase,
private val shouldShowTravalaPromoWalletUseCase: ShouldShowTravalaPromoWalletUseCase,
) : BaseWalletClickIntents(), WalletWarningsClickIntents {
override fun onAddBackupCardClick() {
@ -212,6 +219,34 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor(
}
}
override fun onTravalaPromoClick(link: String?) {
analyticsEventHandler.send(
WalletScreenAnalyticsEvent.Promotion.PromotionBannerClicked(
source = AnalyticsParam.ScreensSources.Main,
programName = "Travala",
action = WalletScreenAnalyticsEvent.Promotion.PromotionBannerClicked.BannerAction.Clicked,
),
)
link?.let {
viewModelScope.launch(dispatchers.main) {
router.openUrl(link)
}
}
}
override fun onCloseTravalaPromoClick() {
analyticsEventHandler.send(
WalletScreenAnalyticsEvent.Promotion.PromotionBannerClicked(
source = AnalyticsParam.ScreensSources.Main,
programName = "Travala",
action = WalletScreenAnalyticsEvent.Promotion.PromotionBannerClicked.BannerAction.Closed,
),
)
viewModelScope.launch(dispatchers.main) {
shouldShowTravalaPromoWalletUseCase.neverToShow()
}
}
private suspend fun getSelectedUserWallet(): UserWallet? {
val userWalletId = stateHolder.getSelectedWalletId()
return getUserWalletUseCase(userWalletId).getOrElse {