Updated on 2026-08-14

This commit is contained in:
Tangem 2024-02-08 11:17:26 +03:00
parent 8106b36fd6
commit 1e0876eea6
45 changed files with 301 additions and 292 deletions

View file

@ -80,6 +80,7 @@ dependencies {
implementation(projects.data.analytics) implementation(projects.data.analytics)
implementation(projects.data.transaction) implementation(projects.data.transaction)
implementation(projects.data.visa) implementation(projects.data.visa)
implementation(projects.data.promo)
/** Features */ /** Features */
implementation(projects.features.onboarding) implementation(projects.features.onboarding)

View file

@ -100,6 +100,7 @@ internal object TokensDomainModule {
marketCryptoCurrencyRepository: MarketCryptoCurrencyRepository, marketCryptoCurrencyRepository: MarketCryptoCurrencyRepository,
swapRepository: SwapRepository, swapRepository: SwapRepository,
showSwapPromoTokenUseCase: ShouldShowSwapPromoTokenUseCase, showSwapPromoTokenUseCase: ShouldShowSwapPromoTokenUseCase,
promoRepository: PromoRepository,
dispatchers: CoroutineDispatcherProvider, dispatchers: CoroutineDispatcherProvider,
): GetCurrencyWarningsUseCase { ): GetCurrencyWarningsUseCase {
return GetCurrencyWarningsUseCase( return GetCurrencyWarningsUseCase(
@ -110,6 +111,7 @@ internal object TokensDomainModule {
marketCryptoCurrencyRepository = marketCryptoCurrencyRepository, marketCryptoCurrencyRepository = marketCryptoCurrencyRepository,
swapRepository = swapRepository, swapRepository = swapRepository,
showSwapPromoTokenUseCase = showSwapPromoTokenUseCase, showSwapPromoTokenUseCase = showSwapPromoTokenUseCase,
promoRepository = promoRepository,
dispatchers = dispatchers, dispatchers = dispatchers,
) )
} }

View file

@ -1,29 +0,0 @@
package com.tangem.datasource.api.promotion
import com.tangem.datasource.api.promotion.models.*
import retrofit2.http.*
/**
*
* Promotion API
* @see <a href = "https://www.notion.so/tangem/Promotion-Program-API-0907159c3fdb4975aac761be632f44da">Documentation<a/>
[REDACTED_AUTHOR]
*/
interface PromotionApi {
@Headers("Cache-Control: max-age=3600")
@GET("promotion")
suspend fun getPromotionInfo(@Query("programName") name: String): PromotionInfoResponse
@POST("promotion/code/validate")
suspend fun validateCode(@Body request: CodeValidateRequestBody): CodeValidateResponse
@POST("promotion/code/award")
suspend fun requestAwardByCode(@Body request: CodeAwardRequestBody): CodeAwardResponse
@POST("promotion/validate")
suspend fun validate(@Body request: ValidateRequestBody): ValidateResponse
@POST("promotion/award")
suspend fun requestAward(@Body request: AwardRequestBody): AwardResponse
}

View file

@ -1,18 +0,0 @@
package com.tangem.datasource.api.promotion.models
import com.squareup.moshi.Json
/**
[REDACTED_AUTHOR]
*/
abstract class AbstractPromotionResponse {
abstract val error: Error?
fun isError(): Boolean = error != null
data class Error(
@Json(name = "code") val code: Int,
@Json(name = "message") val message: String,
)
}

View file

@ -1,12 +0,0 @@
package com.tangem.datasource.api.promotion.models
import com.squareup.moshi.Json
/**
[REDACTED_AUTHOR]
*/
data class AwardRequestBody(
@Json(name = "walletId") val walletId: String,
@Json(name = "address") val address: String,
@Json(name = "programName") val programName: String,
)

View file

@ -1,11 +0,0 @@
package com.tangem.datasource.api.promotion.models
import com.squareup.moshi.Json
/**
[REDACTED_AUTHOR]
*/
data class AwardResponse(
@Json(name = "status") val status: Boolean?,
@Json(name = "error") override val error: Error? = null,
) : AbstractPromotionResponse()

View file

@ -1,12 +0,0 @@
package com.tangem.datasource.api.promotion.models
import com.squareup.moshi.Json
/**
[REDACTED_AUTHOR]
*/
data class CodeAwardRequestBody(
@Json(name = "walletId") val walletId: String,
@Json(name = "address") val address: String,
@Json(name = "code") val code: String?,
)

View file

@ -1,11 +0,0 @@
package com.tangem.datasource.api.promotion.models
import com.squareup.moshi.Json
/**
[REDACTED_AUTHOR]
*/
data class CodeAwardResponse(
@Json(name = "status") val status: Boolean?,
@Json(name = "error") override val error: Error? = null,
) : AbstractPromotionResponse()

View file

@ -1,11 +0,0 @@
package com.tangem.datasource.api.promotion.models
import com.squareup.moshi.Json
/**
[REDACTED_AUTHOR]
*/
data class CodeValidateRequestBody(
@Json(name = "walletId") val walletId: String,
@Json(name = "code") val code: String?,
)

View file

@ -1,11 +0,0 @@
package com.tangem.datasource.api.promotion.models
import com.squareup.moshi.Json
/**
[REDACTED_AUTHOR]
*/
data class CodeValidateResponse(
@Json(name = "valid") val valid: Boolean?,
@Json(name = "error") override val error: Error? = null,
) : AbstractPromotionResponse()

View file

@ -1,40 +1,23 @@
package com.tangem.datasource.api.promotion.models package com.tangem.datasource.api.promotion.models
import com.squareup.moshi.Json import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
/** @JsonClass(generateAdapter = true)
[REDACTED_AUTHOR]
*/
data class PromotionInfoResponse( data class PromotionInfoResponse(
@Json(name = "newCard") val newCard: Data?,
@Json(name = "oldCard") val oldCard: Data?,
@Json(name = "awardPaymentToken") val awardPaymentToken: TokenData?,
@Json(name = "error") override val error: Error? = null,
) : AbstractPromotionResponse() {
data class Data(
@Json(name = "status") val status: Status,
@Json(name = "award") val award: Double,
)
enum class Status(val value: String) {
@Json(name = "pending")
PENDING("pending"),
@Json(name = "active")
ACTIVE("active"),
@Json(name = "finished")
FINISHED("finished"),
}
data class TokenData(
@Json(name = "id") val id: String,
@Json(name = "name") val name: String, @Json(name = "name") val name: String,
@Json(name = "symbol") val symbol: String, @Json(name = "all") val bannerState: BannerState?,
@Json(name = "active") val active: Boolean, ) {
@Json(name = "networkId") val networkId: String,
@Json(name = "contractAddress") val contractAddress: String, @JsonClass(generateAdapter = true)
@Json(name = "decimalCount") val decimalCount: Int, data class BannerState(
@Json(name = "timeline") val timeline: Timeline,
@Json(name = "status") val status: String,
)
@JsonClass(generateAdapter = true)
data class Timeline(
@Json(name = "start") val start: String,
@Json(name = "end") val end: String,
) )
} }

View file

@ -1,11 +0,0 @@
package com.tangem.datasource.api.promotion.models
import com.squareup.moshi.Json
/**
[REDACTED_AUTHOR]
*/
data class ValidateRequestBody(
@Json(name = "walletId") val walletId: String,
@Json(name = "programName") val programName: String,
)

View file

@ -1,11 +0,0 @@
package com.tangem.datasource.api.promotion.models
import com.squareup.moshi.Json
/**
[REDACTED_AUTHOR]
*/
data class ValidateResponse(
@Json(name = "valid") val valid: Boolean?,
@Json(name = "error") override val error: Error? = null,
) : AbstractPromotionResponse()

View file

@ -1,6 +1,7 @@
package com.tangem.datasource.api.tangemTech package com.tangem.datasource.api.tangemTech
import com.tangem.datasource.api.common.response.ApiResponse import com.tangem.datasource.api.common.response.ApiResponse
import com.tangem.datasource.api.promotion.models.PromotionInfoResponse
import com.tangem.datasource.api.tangemTech.models.* import com.tangem.datasource.api.tangemTech.models.*
import retrofit2.http.* import retrofit2.http.*
@ -71,4 +72,7 @@ interface TangemTechApi {
@Query("coinIds") coinIds: String, @Query("coinIds") coinIds: String,
@Query("fields") fields: String = "price,priceChange24h,lastUpdatedAt", @Query("fields") fields: String = "price,priceChange24h,lastUpdatedAt",
): ApiResponse<QuotesResponse> ): ApiResponse<QuotesResponse>
@GET("promotion")
suspend fun getPromotionInfo(@Query("programName") name: String): ApiResponse<PromotionInfoResponse>
} }

View file

@ -5,4 +5,4 @@ import javax.inject.Qualifier
@Qualifier @Qualifier
@MustBeDocumented @MustBeDocumented
@Retention(AnnotationRetention.RUNTIME) @Retention(AnnotationRetention.RUNTIME)
annotation class PromotionOneInch annotation class DevTangemApi

View file

@ -5,12 +5,10 @@ import com.squareup.moshi.Moshi
import com.tangem.datasource.BuildConfig import com.tangem.datasource.BuildConfig
import com.tangem.datasource.api.common.response.ApiResponseCallAdapterFactory import com.tangem.datasource.api.common.response.ApiResponseCallAdapterFactory
import com.tangem.datasource.api.express.TangemExpressApi import com.tangem.datasource.api.express.TangemExpressApi
import com.tangem.datasource.api.promotion.PromotionApi
import com.tangem.datasource.api.tangemTech.TangemTechApi import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.utils.RequestHeader.* import com.tangem.datasource.utils.RequestHeader.*
import com.tangem.datasource.utils.addHeaders import com.tangem.datasource.utils.addHeaders
import com.tangem.datasource.utils.addLoggers import com.tangem.datasource.utils.addLoggers
import com.tangem.lib.auth.AuthProvider
import com.tangem.lib.auth.ExpressAuthProvider import com.tangem.lib.auth.ExpressAuthProvider
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
@ -20,7 +18,6 @@ import dagger.hilt.components.SingletonComponent
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import retrofit2.Retrofit import retrofit2.Retrofit
import retrofit2.converter.moshi.MoshiConverterFactory import retrofit2.converter.moshi.MoshiConverterFactory
import java.util.concurrent.TimeUnit
import javax.inject.Singleton import javax.inject.Singleton
@Module @Module
@ -75,31 +72,25 @@ class NetworkModule {
} }
@Provides @Provides
@DevTangemApi
@Singleton @Singleton
@PromotionOneInch fun provideTangemTechDevApi(@NetworkMoshi moshi: Moshi, @ApplicationContext context: Context): TangemTechApi {
fun providePromotionOneInchApi(
authProvider: AuthProvider,
@NetworkMoshi moshi: Moshi,
@ApplicationContext context: Context,
): PromotionApi {
val okClient = OkHttpClient.Builder()
.addHeaders(AuthenticationHeader(authProvider))
.addLoggers(context)
.callTimeout(API_ONE_INCH_TIMEOUT_MS, TimeUnit.MILLISECONDS)
.connectTimeout(API_ONE_INCH_TIMEOUT_MS, TimeUnit.MILLISECONDS)
.readTimeout(API_ONE_INCH_TIMEOUT_MS, TimeUnit.MILLISECONDS)
.writeTimeout(API_ONE_INCH_TIMEOUT_MS, TimeUnit.MILLISECONDS)
.build()
return createBasePromotionRetrofit(okClient, moshi)
}
private fun createBasePromotionRetrofit(okHttpClient: OkHttpClient, moshi: Moshi): PromotionApi {
return Retrofit.Builder() return Retrofit.Builder()
.addConverterFactory(MoshiConverterFactory.create(moshi)) .addConverterFactory(MoshiConverterFactory.create(moshi))
.baseUrl(PROD_TANGEM_TECH_BASE_URL) .addCallAdapterFactory(ApiResponseCallAdapterFactory.create())
.client(okHttpClient) .baseUrl(DEV_TANGEM_TECH_BASE_URL)
.client(
OkHttpClient.Builder()
.addHeaders(
CacheControlHeader,
// TODO("refactor header init") get auth data after biometric auth to avoid race condition
// AuthenticationHeader(authProvider),
)
.addLoggers(context)
.build(),
)
.build() .build()
.create(PromotionApi::class.java) .create(TangemTechApi::class.java)
} }
private companion object { private companion object {

View file

@ -45,9 +45,13 @@ object PreferencesKeys {
val LAST_SWAPPED_CRYPTOCURRENCY_ID_KEY by lazy { stringPreferencesKey(name = "lastSwappedCryptoCurrency") } val LAST_SWAPPED_CRYPTOCURRENCY_ID_KEY by lazy { stringPreferencesKey(name = "lastSwappedCryptoCurrency") }
val IS_WALLET_SWAP_PROMO_SHOW_KEY by lazy { booleanPreferencesKey(name = "isWalletSwapPromoShown") } val IS_WALLET_SWAP_PROMO_CHANGELLY_SHOW_KEY by lazy {
booleanPreferencesKey(name = "isWalletSwapPromoChangellyShown")
}
val IS_TOKEN_SWAP_PROMO_SHOW_KEY by lazy { booleanPreferencesKey(name = "isTokenSwapPromoShown") } val IS_TOKEN_SWAP_PROMO_CHANGELLY_SHOW_KEY by lazy {
booleanPreferencesKey(name = "isTokenSwapPromoChangellyShown")
}
} }
/** Preferences keys set that should be migrated from "PreferencesDataSource" to a new DataStore<Preferences> */ /** Preferences keys set that should be migrated from "PreferencesDataSource" to a new DataStore<Preferences> */

View file

@ -273,8 +273,8 @@
<string name="main_promotion_credited">1INCH токены будут зачислены на адрес вашего кошелька в сети %s в течение 48 часов</string> <string name="main_promotion_credited">1INCH токены будут зачислены на адрес вашего кошелька в сети %s в течение 48 часов</string>
<string name="main_scan_card_warning_view_subtitle">Чтобы получить доступ ко всем сетям, вам необходимо отсканировать карту</string> <string name="main_scan_card_warning_view_subtitle">Чтобы получить доступ ко всем сетям, вам необходимо отсканировать карту</string>
<string name="main_scan_card_warning_view_title">Отсканируйте карту</string> <string name="main_scan_card_warning_view_title">Отсканируйте карту</string>
<string name="main_swap_promotion_message">Обменивайте свои цифровые активы между различными сетями</string> <string name="main_swap_changelly_promotion_message">Обменивайте свои токены с %1$s комиссии провайдера через Changelly с %2$s по %3$s февраля.</string>
<string name="main_swap_promotion_title">Кроссчейн-своп теперь доступен</string> <string name="main_swap_changelly_promotion_title">Обмен с Changelly, %s комиссии</string>
<string name="main_tokens">Токены</string> <string name="main_tokens">Токены</string>
<string name="manage_tokens_add">Добавить</string> <string name="manage_tokens_add">Добавить</string>
<string name="manage_tokens_edit">Изменить</string> <string name="manage_tokens_edit">Изменить</string>
@ -575,9 +575,9 @@
<string name="token_details_unable_hide_alert_message">Токен %1$s является основной валютой в сети %2$s и не может быть скрыт до тех пор, пока у вас в списке есть другие токены этой сети.</string> <string name="token_details_unable_hide_alert_message">Токен %1$s является основной валютой в сети %2$s и не может быть скрыт до тех пор, пока у вас в списке есть другие токены этой сети.</string>
<string name="token_details_unable_hide_alert_title">Невозможно скрыть %s</string> <string name="token_details_unable_hide_alert_title">Невозможно скрыть %s</string>
<string name="token_item_no_rate">Нет цены</string> <string name="token_item_no_rate">Нет цены</string>
<string name="token_swap_changelly_promotion_message">Обменивайте этот токен на другие с %1$s комиссии за обслуживание с %2$s по %3$s февраля.</string>
<string name="token_swap_changelly_promotion_title">Обмен с Changelly, %s комиссии</string>
<string name="token_swap_promotion_button">Обменять</string> <string name="token_swap_promotion_button">Обменять</string>
<string name="token_swap_promotion_message">Обменяйте этот токен на другие активы в вашем портфеле</string>
<string name="token_swap_promotion_title">Представляем кроссчейн-своп</string>
<string name="transaction_history_contract_address">контракт: %s</string> <string name="transaction_history_contract_address">контракт: %s</string>
<string name="transaction_history_empty_transactions">У вас еще нет транзакций</string> <string name="transaction_history_empty_transactions">У вас еще нет транзакций</string>
<string name="transaction_history_error_failed_to_load">Не удалось загрузить историю транзакций.\nНажмите на кнопку перезагрузки, чтобы обновить информацию.</string> <string name="transaction_history_error_failed_to_load">Не удалось загрузить историю транзакций.\nНажмите на кнопку перезагрузки, чтобы обновить информацию.</string>

View file

@ -273,8 +273,8 @@
<string name="main_promotion_credited">1INCH tokens will be credited to your %s wallet address within 48 hours</string> <string name="main_promotion_credited">1INCH tokens will be credited to your %s wallet address within 48 hours</string>
<string name="main_scan_card_warning_view_subtitle">To access all the networks you need to scan the card</string> <string name="main_scan_card_warning_view_subtitle">To access all the networks you need to scan the card</string>
<string name="main_scan_card_warning_view_title">Scan your card</string> <string name="main_scan_card_warning_view_title">Scan your card</string>
<string name="main_swap_promotion_message">Swap multiple currencies across several blockchains</string> <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_promotion_title">Cross-chain swaps are now available</string> <string name="main_swap_changelly_promotion_title">Swap with Changelly, %s fees</string>
<string name="main_tokens">Tokens</string> <string name="main_tokens">Tokens</string>
<string name="manage_tokens_add">Add</string> <string name="manage_tokens_add">Add</string>
<string name="manage_tokens_edit">Edit</string> <string name="manage_tokens_edit">Edit</string>
@ -587,8 +587,8 @@
<string name="token_details_unable_hide_alert_message">The %1$s token is the main currency on the %2$s network and cannot be hidden as long as you have other tokens on this network in the list.</string> <string name="token_details_unable_hide_alert_message">The %1$s token is the main currency on the %2$s network and cannot be hidden as long as you have other tokens on this network in the list.</string>
<string name="token_details_unable_hide_alert_title">Unable to hide %s</string> <string name="token_details_unable_hide_alert_title">Unable to hide %s</string>
<string name="token_item_no_rate">No rate</string> <string name="token_item_no_rate">No rate</string>
<string name="token_swap_promotion_message">Exchange this token for other assets in your portfolio</string> <string name="token_swap_changelly_promotion_message">Exchange this token for another at %1$s service fees from February %2$s-%3$s.</string>
<string name="token_swap_promotion_title">Introducing cross-chain swaps</string> <string name="token_swap_changelly_promotion_title">Swap with Changelly, %s fees</string>
<string name="token_swap_promotion_button">Swap now</string> <string name="token_swap_promotion_button">Swap now</string>
<string name="transaction_history_contract_address">contract: %s</string> <string name="transaction_history_contract_address">contract: %s</string>
<string name="transaction_history_empty_transactions">You don\'t have any transactions yet</string> <string name="transaction_history_empty_transactions">You don\'t have any transactions yet</string>

View file

@ -28,6 +28,7 @@ import com.tangem.core.ui.components.buttons.common.TangemButtonIconPosition
import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference 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.LocalIsInDarkTheme
import com.tangem.core.ui.res.TangemColorPalette.Dark6 import com.tangem.core.ui.res.TangemColorPalette.Dark6
import com.tangem.core.ui.res.TangemColorPalette.Light4 import com.tangem.core.ui.res.TangemColorPalette.Light4
@ -55,7 +56,7 @@ fun NotificationWithBackground(config: NotificationConfig, modifier: Modifier =
val spacing14 = TangemTheme.dimens.spacing14 val spacing14 = TangemTheme.dimens.spacing14
Image( Image(
painter = painterResource(config.backgroundResId ?: R.drawable.img_swap_promo_banner_background), painter = painterResource(config.backgroundResId ?: R.drawable.img_swap_promo_blue_banner_background),
contentDescription = null, contentDescription = null,
contentScale = ContentScale.Crop, contentScale = ContentScale.Crop,
modifier = Modifier.constrainAs(backgroundRef) { modifier = Modifier.constrainAs(backgroundRef) {
@ -171,13 +172,19 @@ private class NotificationWithBackgroundPreviewProvider : PreviewParameterProvid
override val values: Sequence<NotificationConfig> override val values: Sequence<NotificationConfig>
get() = sequenceOf( get() = sequenceOf(
NotificationConfig( NotificationConfig(
title = resourceReference(id = R.string.main_swap_promotion_title), title = resourceReference(id = R.string.main_swap_changelly_promotion_title),
subtitle = resourceReference(id = R.string.main_swap_promotion_message), subtitle = resourceReference(
id = R.string.main_swap_changelly_promotion_message,
formatArgs = wrappedList("1", "2"),
),
iconResId = R.drawable.img_swap_promo, iconResId = R.drawable.img_swap_promo,
backgroundResId = R.drawable.img_swap_promo_banner_background, backgroundResId = R.drawable.img_swap_promo_blue_banner_background,
), ),
NotificationConfig( NotificationConfig(
title = resourceReference(id = R.string.token_swap_promotion_title), title = resourceReference(
id = R.string.token_swap_changelly_promotion_title,
formatArgs = wrappedList("1", "2"),
),
subtitle = stringReference( subtitle = stringReference(
"Swap multiple currencies between any chains you wish. Swap multiple " + "Swap multiple currencies between any chains you wish. Swap multiple " +
"currencies between any chains you wish. Swap multiple " + "currencies between any chains you wish. Swap multiple " +
@ -185,7 +192,7 @@ private class NotificationWithBackgroundPreviewProvider : PreviewParameterProvid
"till Dec 31.", "till Dec 31.",
), ),
iconResId = R.drawable.img_swap_promo, iconResId = R.drawable.img_swap_promo,
backgroundResId = R.drawable.img_swap_promo_banner_background, backgroundResId = R.drawable.img_swap_promo_blue_banner_background,
buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig( buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig(
text = resourceReference(id = R.string.token_swap_promotion_button), text = resourceReference(id = R.string.token_swap_promotion_button),
onClick = {}, onClick = {},

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

1
data/promo/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/build

View file

@ -0,0 +1,25 @@
plugins {
alias(deps.plugins.android.library)
alias(deps.plugins.kotlin.android)
alias(deps.plugins.kotlin.kapt)
alias(deps.plugins.hilt.android)
id("configuration")
}
android {
namespace = "com.tangem.data.promo"
}
dependencies {
implementation(deps.androidx.datastore)
implementation(deps.jodatime)
implementation(deps.hilt.android)
kapt(deps.hilt.kapt)
implementation(projects.domain.tokens)
implementation(projects.domain.tokens.models)
implementation(projects.core.datasource)
implementation(projects.core.utils)
}

View file

@ -0,0 +1,29 @@
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.domain.promo.PromoBanner
import com.tangem.domain.tokens.repository.PromoRepository
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.coroutines.runCatching
internal class DefaultPromoRepository(
private val tangemApi: TangemTechApi,
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()
}
private companion object {
private const val CHANGELLY_NAME = "changelly"
}
}

View file

@ -0,0 +1,23 @@
package com.tangem.data.promo.converters
import com.tangem.datasource.api.promotion.models.PromotionInfoResponse
import com.tangem.domain.promo.PromoBanner
import com.tangem.utils.converter.Converter
import org.joda.time.DateTime
class PromoResponseConverter : Converter<PromotionInfoResponse, PromoBanner?> {
override fun convert(value: PromotionInfoResponse): PromoBanner? {
val bannerState = value.bannerState ?: return null
return PromoBanner(
name = value.name,
bannerState = PromoBanner.BannerState(
status = bannerState.status,
timeline = PromoBanner.Timeline(
start = DateTime.parse(bannerState.timeline.start),
end = DateTime.parse(bannerState.timeline.end),
),
),
)
}
}

View file

@ -0,0 +1,26 @@
package com.tangem.data.promo.di
import com.tangem.data.promo.DefaultPromoRepository
import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.di.DevTangemApi
import com.tangem.domain.tokens.repository.PromoRepository
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
internal object PromoDataModule {
@Provides
@Singleton
fun providePromoRepository(
@DevTangemApi tangemTechApi: TangemTechApi,
dispatchers: CoroutineDispatcherProvider,
): PromoRepository {
return DefaultPromoRepository(tangemTechApi, dispatchers)
}
}

View file

@ -1,14 +1,12 @@
package com.tangem.data.settings package com.tangem.data.settings
import com.tangem.datasource.local.preferences.AppPreferencesStore import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.datasource.local.preferences.PreferencesKeys.IS_TOKEN_SWAP_PROMO_SHOW_KEY 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_SHOW_KEY import com.tangem.datasource.local.preferences.PreferencesKeys.IS_WALLET_SWAP_PROMO_CHANGELLY_SHOW_KEY
import com.tangem.datasource.local.preferences.utils.get import com.tangem.datasource.local.preferences.utils.get
import com.tangem.datasource.local.preferences.utils.store import com.tangem.datasource.local.preferences.utils.store
import com.tangem.domain.settings.repositories.SwapPromoRepository import com.tangem.domain.settings.repositories.SwapPromoRepository
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import java.util.Calendar
/** /**
* Repository for showing swap promo notification. * Repository for showing swap promo notification.
@ -16,47 +14,25 @@ import java.util.Calendar
class DefaultSwapPromoRepository( class DefaultSwapPromoRepository(
private val appPreferencesStore: AppPreferencesStore, private val appPreferencesStore: AppPreferencesStore,
) : SwapPromoRepository { ) : SwapPromoRepository {
override fun isReadyToShowWallet(): Flow<Boolean> { override fun isReadyToShowWalletPromo(): Flow<Boolean> {
return appPreferencesStore.get(IS_WALLET_SWAP_PROMO_SHOW_KEY, true) return appPreferencesStore.get(IS_WALLET_SWAP_PROMO_CHANGELLY_SHOW_KEY, true)
.map { it && checkPromoPeriod() }
} }
override fun isReadyToShowToken(): Flow<Boolean> { override fun isReadyToShowTokenPromo(): Flow<Boolean> {
return appPreferencesStore.get(IS_TOKEN_SWAP_PROMO_SHOW_KEY, true) return appPreferencesStore.get(IS_TOKEN_SWAP_PROMO_CHANGELLY_SHOW_KEY, true)
.map { it && checkPromoPeriod() }
} }
override suspend fun setNeverToShowWallet() { override suspend fun setNeverToShowWalletPromo() {
appPreferencesStore.store( appPreferencesStore.store(
key = IS_WALLET_SWAP_PROMO_SHOW_KEY, key = IS_WALLET_SWAP_PROMO_CHANGELLY_SHOW_KEY,
value = false, value = false,
) )
} }
override suspend fun setNeverToShowToken() { override suspend fun setNeverToShowTokenPromo() {
appPreferencesStore.store( appPreferencesStore.store(
key = IS_TOKEN_SWAP_PROMO_SHOW_KEY, key = IS_TOKEN_SWAP_PROMO_CHANGELLY_SHOW_KEY,
value = false, value = false,
) )
} }
private suspend fun checkPromoPeriod(): Boolean {
val calendar = Calendar.getInstance()
val currentTime = calendar.timeInMillis
calendar.set(END_YEAR_KEY, END_MONTH_KEY, END_DAY_KEY, 0, 0, 0)
val endTime = calendar.timeInMillis
val shouldShow = endTime - currentTime > 0
if (!shouldShow) {
setNeverToShowToken()
setNeverToShowWallet()
}
return shouldShow
}
companion object {
private const val END_DAY_KEY = 1
private const val END_MONTH_KEY = 1 // February
private const val END_YEAR_KEY = 2024
}
} }

View file

@ -5,7 +5,7 @@ import kotlinx.coroutines.flow.Flow
class ShouldShowSwapPromoTokenUseCase(private val swapPromoRepository: SwapPromoRepository) { class ShouldShowSwapPromoTokenUseCase(private val swapPromoRepository: SwapPromoRepository) {
operator fun invoke(): Flow<Boolean> = swapPromoRepository.isReadyToShowToken() operator fun invoke(): Flow<Boolean> = swapPromoRepository.isReadyToShowTokenPromo()
suspend fun neverToShow() = swapPromoRepository.setNeverToShowToken() suspend fun neverToShow() = swapPromoRepository.setNeverToShowTokenPromo()
} }

View file

@ -5,7 +5,7 @@ import kotlinx.coroutines.flow.Flow
class ShouldShowSwapPromoWalletUseCase(private val swapPromoRepository: SwapPromoRepository) { class ShouldShowSwapPromoWalletUseCase(private val swapPromoRepository: SwapPromoRepository) {
operator fun invoke(): Flow<Boolean> = swapPromoRepository.isReadyToShowWallet() operator fun invoke(): Flow<Boolean> = swapPromoRepository.isReadyToShowWalletPromo()
suspend fun neverToShow() = swapPromoRepository.setNeverToShowWallet() suspend fun neverToShow() = swapPromoRepository.setNeverToShowWalletPromo()
} }

View file

@ -3,11 +3,11 @@ package com.tangem.domain.settings.repositories
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
interface SwapPromoRepository { interface SwapPromoRepository {
fun isReadyToShowWallet(): Flow<Boolean> fun isReadyToShowWalletPromo(): Flow<Boolean>
fun isReadyToShowToken(): Flow<Boolean> fun isReadyToShowTokenPromo(): Flow<Boolean>
suspend fun setNeverToShowWallet() suspend fun setNeverToShowWalletPromo()
suspend fun setNeverToShowToken() suspend fun setNeverToShowTokenPromo()
} }

View file

@ -15,4 +15,5 @@ dependencies {
implementation(deps.tangem.blockchain) { implementation(deps.tangem.blockchain) {
exclude(module = "joda-time") exclude(module = "joda-time")
} }
implementation(deps.jodatime)
} }

View file

@ -0,0 +1,25 @@
package com.tangem.domain.promo
import org.joda.time.DateTime
data class PromoBanner(
val name: String,
val bannerState: BannerState,
) {
val isActive = bannerState.status == ACTIVE_STATUS && bannerState.timeline.end.isAfterNow
data class BannerState(
val timeline: Timeline,
val status: String,
)
data class Timeline(
val start: DateTime,
val end: DateTime,
)
private companion object {
const val ACTIVE_STATUS = "active"
}
}

View file

@ -1,6 +1,7 @@
package com.tangem.domain.tokens.model.warnings package com.tangem.domain.tokens.model.warnings
import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrency
import org.joda.time.DateTime
import java.math.BigDecimal import java.math.BigDecimal
sealed class CryptoCurrencyWarning { sealed class CryptoCurrencyWarning {
@ -42,5 +43,8 @@ sealed class CryptoCurrencyWarning {
data class HasPendingTransactions(val blockchainSymbol: String) : CryptoCurrencyWarning() data class HasPendingTransactions(val blockchainSymbol: String) : CryptoCurrencyWarning()
object SwapPromo : CryptoCurrencyWarning() data class SwapPromo(
val startDateTime: DateTime,
val endDateTime: DateTime,
) : CryptoCurrencyWarning()
} }

View file

@ -7,10 +7,7 @@ import com.tangem.domain.tokens.model.FeePaidCurrency
import com.tangem.domain.tokens.model.Network import com.tangem.domain.tokens.model.Network
import com.tangem.domain.tokens.model.warnings.CryptoCurrencyWarning import com.tangem.domain.tokens.model.warnings.CryptoCurrencyWarning
import com.tangem.domain.tokens.operations.CurrenciesStatusesOperations import com.tangem.domain.tokens.operations.CurrenciesStatusesOperations
import com.tangem.domain.tokens.repository.CurrenciesRepository import com.tangem.domain.tokens.repository.*
import com.tangem.domain.tokens.repository.MarketCryptoCurrencyRepository
import com.tangem.domain.tokens.repository.NetworksRepository
import com.tangem.domain.tokens.repository.QuotesRepository
import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.feature.swap.domain.api.SwapRepository import com.tangem.feature.swap.domain.api.SwapRepository
@ -29,6 +26,7 @@ class GetCurrencyWarningsUseCase(
private val networksRepository: NetworksRepository, private val networksRepository: NetworksRepository,
private val swapRepository: SwapRepository, private val swapRepository: SwapRepository,
private val marketCryptoCurrencyRepository: MarketCryptoCurrencyRepository, private val marketCryptoCurrencyRepository: MarketCryptoCurrencyRepository,
private val promoRepository: PromoRepository,
private val showSwapPromoTokenUseCase: ShouldShowSwapPromoTokenUseCase, private val showSwapPromoTokenUseCase: ShouldShowSwapPromoTokenUseCase,
private val dispatchers: CoroutineDispatcherProvider, private val dispatchers: CoroutineDispatcherProvider,
) { ) {
@ -90,7 +88,9 @@ class GetCurrencyWarningsUseCase(
showSwapPromoTokenUseCase().conflate(), showSwapPromoTokenUseCase().conflate(),
flowOf(marketCryptoCurrencyRepository.isExchangeable(userWalletId, currency)).conflate(), flowOf(marketCryptoCurrencyRepository.isExchangeable(userWalletId, currency)).conflate(),
) { shouldShowSwapPromo, isExchangeable -> ) { shouldShowSwapPromo, isExchangeable ->
if (shouldShowSwapPromo && isExchangeable && currencyStatus.value !is CryptoCurrencyStatus.Unreachable) { val promoBanner = promoRepository.getChangellyPromoBanner() ?: return@combine null
val showPromo = promoBanner.isActive && shouldShowSwapPromo
if (showPromo && isExchangeable && currencyStatus.value !is CryptoCurrencyStatus.Unreachable) {
cryptoStatuses.fold( cryptoStatuses.fold(
ifLeft = { null }, ifLeft = { null },
ifRight = { cryptoCurrencyStatuses -> ifRight = { cryptoCurrencyStatuses ->
@ -124,7 +124,10 @@ class GetCurrencyWarningsUseCase(
} }
} }
if (showPromo) { if (showPromo) {
CryptoCurrencyWarning.SwapPromo CryptoCurrencyWarning.SwapPromo(
startDateTime = promoBanner.bannerState.timeline.start,
endDateTime = promoBanner.bannerState.timeline.end,
)
} else { } else {
null null
} }

View file

@ -0,0 +1,8 @@
package com.tangem.domain.tokens.repository
import com.tangem.domain.promo.PromoBanner
interface PromoRepository {
suspend fun getChangellyPromoBanner(): PromoBanner?
}

View file

@ -9,6 +9,7 @@ import com.tangem.core.ui.extensions.wrappedList
import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.warnings.CryptoCurrencyWarning import com.tangem.domain.tokens.model.warnings.CryptoCurrencyWarning
import com.tangem.features.tokendetails.impl.R import com.tangem.features.tokendetails.impl.R
import org.joda.time.DateTime
@Immutable @Immutable
internal sealed class TokenDetailsNotification(val config: NotificationConfig) { internal sealed class TokenDetailsNotification(val config: NotificationConfig) {
@ -41,14 +42,22 @@ internal sealed class TokenDetailsNotification(val config: NotificationConfig) {
) )
data class SwapPromo( data class SwapPromo(
val startDateTime: DateTime,
val endDateTime: DateTime,
val onSwapClick: () -> Unit, val onSwapClick: () -> Unit,
val onCloseClick: () -> Unit, val onCloseClick: () -> Unit,
) : TokenDetailsNotification( ) : TokenDetailsNotification(
config = NotificationConfig( config = NotificationConfig(
title = resourceReference(id = R.string.token_swap_promotion_title), title = resourceReference(
subtitle = resourceReference(id = R.string.token_swap_promotion_message), id = R.string.token_swap_changelly_promotion_title,
formatArgs = wrappedList("0%"),
),
subtitle = resourceReference(
id = R.string.token_swap_changelly_promotion_message,
formatArgs = wrappedList("0%", startDateTime.dayOfMonth, endDateTime.dayOfMonth),
),
iconResId = R.drawable.img_swap_promo, iconResId = R.drawable.img_swap_promo,
backgroundResId = R.drawable.img_swap_promo_banner_background, backgroundResId = R.drawable.img_swap_promo_green_banner_background,
onCloseClick = onCloseClick, onCloseClick = onCloseClick,
buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig( buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig(
text = resourceReference(id = com.tangem.core.ui.R.string.token_swap_promotion_button), text = resourceReference(id = com.tangem.core.ui.R.string.token_swap_promotion_button),

View file

@ -68,6 +68,8 @@ internal class TokenDetailsNotificationConverter(
coinSymbol = warning.blockchainSymbol, coinSymbol = warning.blockchainSymbol,
) )
is CryptoCurrencyWarning.SwapPromo -> SwapPromo( is CryptoCurrencyWarning.SwapPromo -> SwapPromo(
startDateTime = warning.startDateTime,
endDateTime = warning.endDateTime,
onSwapClick = clickIntents::onSwapPromoClick, onSwapClick = clickIntents::onSwapPromoClick,
onCloseClick = clickIntents::onSwapPromoDismiss, onCloseClick = clickIntents::onSwapPromoDismiss,
) )

View file

@ -4,13 +4,16 @@ import arrow.core.Either
import com.tangem.domain.common.CardTypesResolver import com.tangem.domain.common.CardTypesResolver
import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.demo.IsDemoCardUseCase import com.tangem.domain.demo.IsDemoCardUseCase
import com.tangem.domain.promo.PromoBanner
import com.tangem.domain.settings.IsReadyToShowRateAppUseCase import com.tangem.domain.settings.IsReadyToShowRateAppUseCase
import com.tangem.domain.settings.ShouldShowSwapPromoWalletUseCase
import com.tangem.domain.tokens.GetTokenListUseCase import com.tangem.domain.tokens.GetTokenListUseCase
import com.tangem.domain.tokens.error.TokenListError import com.tangem.domain.tokens.error.TokenListError
import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.tokens.model.NetworkGroup import com.tangem.domain.tokens.model.NetworkGroup
import com.tangem.domain.tokens.model.TokenList import com.tangem.domain.tokens.model.TokenList
import com.tangem.domain.tokens.repository.PromoRepository
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
import com.tangem.domain.wallets.usecase.IsNeedToBackupUseCase import com.tangem.domain.wallets.usecase.IsNeedToBackupUseCase
import com.tangem.feature.wallet.presentation.wallet.state.components.WalletNotification import com.tangem.feature.wallet.presentation.wallet.state.components.WalletNotification
@ -26,12 +29,15 @@ import kotlinx.coroutines.flow.flowOf
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@Suppress("LongParameterList")
@ViewModelScoped @ViewModelScoped
internal class GetMultiWalletWarningsFactory @Inject constructor( internal class GetMultiWalletWarningsFactory @Inject constructor(
private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase, private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase,
private val getTokenListUseCase: GetTokenListUseCase, private val getTokenListUseCase: GetTokenListUseCase,
private val isDemoCardUseCase: IsDemoCardUseCase, private val isDemoCardUseCase: IsDemoCardUseCase,
private val isReadyToShowRateAppUseCase: IsReadyToShowRateAppUseCase, private val isReadyToShowRateAppUseCase: IsReadyToShowRateAppUseCase,
private val shouldShowSwapPromoWalletUseCase: ShouldShowSwapPromoWalletUseCase,
private val promoRepository: PromoRepository,
private val isNeedToBackupUseCase: IsNeedToBackupUseCase, private val isNeedToBackupUseCase: IsNeedToBackupUseCase,
) { ) {
@ -52,9 +58,15 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
flow = getTokenListUseCase(userWallet.walletId).conflate(), flow = getTokenListUseCase(userWallet.walletId).conflate(),
flow2 = isReadyToShowRateAppUseCase().conflate(), flow2 = isReadyToShowRateAppUseCase().conflate(),
flow3 = isNeedToBackupUseCase(userWallet.walletId).conflate(), flow3 = isNeedToBackupUseCase(userWallet.walletId).conflate(),
) { maybeTokenList, isReadyToShowRating, isNeedToBackup -> flow4 = shouldShowSwapPromoWalletUseCase().conflate(),
) { maybeTokenList, isReadyToShowRating, isNeedToBackup, shouldShowPromo ->
val promoBanner = promoRepository.getChangellyPromoBanner()
readyForRateAppNotification = true readyForRateAppNotification = true
buildList { buildList {
addSwapPromoNotification(shouldShowPromo, promoBanner, clickIntents)
addCriticalNotifications(cardTypesResolver) addCriticalNotifications(cardTypesResolver)
addInformationalNotifications(cardTypesResolver, maybeTokenList, clickIntents) addInformationalNotifications(cardTypesResolver, maybeTokenList, clickIntents)
@ -66,6 +78,23 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
} }
} }
private fun MutableList<WalletNotification>.addSwapPromoNotification(
shouldShowPromo: Boolean,
promoBanner: PromoBanner?,
clickIntents: WalletClickIntentsV2,
) {
promoBanner ?: return
val promoNotification = WalletNotification.SwapPromo(
startDateTime = promoBanner.bannerState.timeline.start,
endDateTime = promoBanner.bannerState.timeline.end,
onCloseClick = clickIntents::onCloseSwapPromoClick,
)
addIf(
element = promoNotification,
condition = shouldShowPromo && promoBanner.isActive,
)
}
private fun MutableList<WalletNotification>.addCriticalNotifications(cardTypesResolver: CardTypesResolver) { private fun MutableList<WalletNotification>.addCriticalNotifications(cardTypesResolver: CardTypesResolver) {
addIf( addIf(
element = WalletNotification.Critical.DevCard, element = WalletNotification.Critical.DevCard,

View file

@ -7,6 +7,7 @@ import com.tangem.core.ui.extensions.pluralReference
import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.wrappedList import com.tangem.core.ui.extensions.wrappedList
import com.tangem.feature.wallet.impl.R import com.tangem.feature.wallet.impl.R
import org.joda.time.DateTime
/** /**
* Wallet notification component state * Wallet notification component state
@ -168,13 +169,21 @@ sealed class WalletNotification(val config: NotificationConfig) {
) )
data class SwapPromo( data class SwapPromo(
val startDateTime: DateTime,
val endDateTime: DateTime,
val onCloseClick: () -> Unit, val onCloseClick: () -> Unit,
) : WalletNotification( ) : WalletNotification(
config = NotificationConfig( config = NotificationConfig(
title = resourceReference(id = R.string.main_swap_promotion_title), title = resourceReference(
subtitle = resourceReference(id = R.string.main_swap_promotion_message), id = R.string.main_swap_changelly_promotion_title,
formatArgs = wrappedList("0%"),
),
subtitle = resourceReference(
id = R.string.main_swap_changelly_promotion_message,
formatArgs = wrappedList("0%", startDateTime.dayOfMonth, endDateTime.dayOfMonth),
),
iconResId = R.drawable.img_swap_promo, iconResId = R.drawable.img_swap_promo,
backgroundResId = R.drawable.img_swap_promo_banner_background, backgroundResId = R.drawable.img_swap_promo_green_banner_background,
onCloseClick = onCloseClick, onCloseClick = onCloseClick,
), ),
) )

View file

@ -72,6 +72,4 @@ internal interface WalletClickIntents {
fun onExploreClick() fun onExploreClick()
fun onTransactionClick(txHash: String) fun onTransactionClick(txHash: String)
fun onCloseSwapPromoNotificationClick()
} }

View file

@ -5,7 +5,6 @@ import com.tangem.domain.common.CardTypesResolver
import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.demo.IsDemoCardUseCase import com.tangem.domain.demo.IsDemoCardUseCase
import com.tangem.domain.settings.IsReadyToShowRateAppUseCase import com.tangem.domain.settings.IsReadyToShowRateAppUseCase
import com.tangem.domain.settings.ShouldShowSwapPromoWalletUseCase
import com.tangem.domain.tokens.GetMissedAddressesCryptoCurrenciesUseCase import com.tangem.domain.tokens.GetMissedAddressesCryptoCurrenciesUseCase
import com.tangem.domain.tokens.error.GetCurrenciesError import com.tangem.domain.tokens.error.GetCurrenciesError
import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrency
@ -41,7 +40,6 @@ internal class WalletNotificationsListFactory(
private val isNeedToBackupUseCase: IsNeedToBackupUseCase, private val isNeedToBackupUseCase: IsNeedToBackupUseCase,
private val getMissedAddressCryptoCurrenciesUseCase: GetMissedAddressesCryptoCurrenciesUseCase, private val getMissedAddressCryptoCurrenciesUseCase: GetMissedAddressesCryptoCurrenciesUseCase,
private val hasSingleWalletSignedHashesUseCase: HasSingleWalletSignedHashesUseCase, private val hasSingleWalletSignedHashesUseCase: HasSingleWalletSignedHashesUseCase,
private val shouldShowSwapPromoWalletUseCase: ShouldShowSwapPromoWalletUseCase,
private val clickIntents: WalletClickIntents, private val clickIntents: WalletClickIntents,
) { ) {
@ -57,12 +55,9 @@ internal class WalletNotificationsListFactory(
flow2 = isReadyToShowRateAppUseCase().conflate(), flow2 = isReadyToShowRateAppUseCase().conflate(),
flow3 = isNeedToBackupUseCase(selectedWallet.walletId).conflate(), flow3 = isNeedToBackupUseCase(selectedWallet.walletId).conflate(),
flow4 = getMissedAddressCryptoCurrenciesUseCase(selectedWallet.walletId).conflate(), flow4 = getMissedAddressCryptoCurrenciesUseCase(selectedWallet.walletId).conflate(),
flow5 = shouldShowSwapPromoWalletUseCase().conflate(), ) { hasSignedHashes, isReadyToShowRating, isNeedToBackup, maybeMissedAddressCurrencies ->
) { hasSignedHashes, isReadyToShowRating, isNeedToBackup, maybeMissedAddressCurrencies, isShowSwapPromo ->
readyForRateAppNotification = true readyForRateAppNotification = true
buildList { buildList {
addSwapPromoNotification(isShowSwapPromo, cardTypesResolver)
addCriticalNotifications(cardTypesResolver) addCriticalNotifications(cardTypesResolver)
addInformationalNotifications(cardTypesResolver, maybeMissedAddressCurrencies) addInformationalNotifications(cardTypesResolver, maybeMissedAddressCurrencies)
@ -86,18 +81,6 @@ internal class WalletNotificationsListFactory(
} }
} }
private fun MutableList<WalletNotification>.addSwapPromoNotification(
showSwapPromo: Boolean,
cardTypesResolver: CardTypesResolver,
) {
addIf(
element = WalletNotification.SwapPromo(
clickIntents::onCloseSwapPromoNotificationClick,
),
condition = showSwapPromo && cardTypesResolver.isMultiwalletAllowed(),
)
}
private fun MutableList<WalletNotification>.addCriticalNotifications(cardTypesResolver: CardTypesResolver) { private fun MutableList<WalletNotification>.addCriticalNotifications(cardTypesResolver: CardTypesResolver) {
addIf( addIf(
element = WalletNotification.Critical.DevCard, element = WalletNotification.Critical.DevCard,

View file

@ -129,7 +129,6 @@ internal class WalletViewModel @Inject constructor(
private val getExplorerTransactionUrlUseCase: GetExplorerTransactionUrlUseCase, private val getExplorerTransactionUrlUseCase: GetExplorerTransactionUrlUseCase,
private val isDemoCardUseCase: IsDemoCardUseCase, private val isDemoCardUseCase: IsDemoCardUseCase,
private val scanCardToUnlockWalletUseCase: ScanCardToUnlockWalletClickHandler, private val scanCardToUnlockWalletUseCase: ScanCardToUnlockWalletClickHandler,
private val shouldShowSwapPromoWalletUseCase: ShouldShowSwapPromoWalletUseCase,
isReadyToShowRateAppUseCase: IsReadyToShowRateAppUseCase, isReadyToShowRateAppUseCase: IsReadyToShowRateAppUseCase,
isNeedToBackupUseCase: IsNeedToBackupUseCase, isNeedToBackupUseCase: IsNeedToBackupUseCase,
getMissedAddressesCryptoCurrenciesUseCase: GetMissedAddressesCryptoCurrenciesUseCase, getMissedAddressesCryptoCurrenciesUseCase: GetMissedAddressesCryptoCurrenciesUseCase,
@ -148,7 +147,6 @@ internal class WalletViewModel @Inject constructor(
isNeedToBackupUseCase = isNeedToBackupUseCase, isNeedToBackupUseCase = isNeedToBackupUseCase,
getMissedAddressCryptoCurrenciesUseCase = getMissedAddressesCryptoCurrenciesUseCase, getMissedAddressCryptoCurrenciesUseCase = getMissedAddressesCryptoCurrenciesUseCase,
hasSingleWalletSignedHashesUseCase = hasSingleWalletSignedHashesUseCase, hasSingleWalletSignedHashesUseCase = hasSingleWalletSignedHashesUseCase,
shouldShowSwapPromoWalletUseCase = shouldShowSwapPromoWalletUseCase,
clickIntents = this, clickIntents = this,
) )
@ -795,12 +793,6 @@ internal class WalletViewModel @Inject constructor(
refreshSingleCurrencyContent(selectedWalletIndex) refreshSingleCurrencyContent(selectedWalletIndex)
} }
override fun onCloseSwapPromoNotificationClick() {
viewModelScope.launch(dispatchers.main) {
shouldShowSwapPromoWalletUseCase.neverToShow()
}
}
// FIXME: refreshSingleCurrencyContent mustn't update the TxHistory and Buttons. It only must fetch primary // FIXME: refreshSingleCurrencyContent mustn't update the TxHistory and Buttons. It only must fetch primary
// currency. Now it not works because GetPrimaryCurrency's subscriber uses .distinctUntilChanged() // currency. Now it not works because GetPrimaryCurrency's subscriber uses .distinctUntilChanged()
private fun refreshSingleCurrencyContent(walletIndex: Int) { private fun refreshSingleCurrencyContent(walletIndex: Int) {

View file

@ -18,6 +18,7 @@ import com.tangem.domain.redux.LegacyAction
import com.tangem.domain.redux.ReduxStateHolder import com.tangem.domain.redux.ReduxStateHolder
import com.tangem.domain.settings.NeverToSuggestRateAppUseCase import com.tangem.domain.settings.NeverToSuggestRateAppUseCase
import com.tangem.domain.settings.RemindToRateAppLaterUseCase import com.tangem.domain.settings.RemindToRateAppLaterUseCase
import com.tangem.domain.settings.ShouldShowSwapPromoWalletUseCase
import com.tangem.domain.tokens.FetchTokenListUseCase import com.tangem.domain.tokens.FetchTokenListUseCase
import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.wallets.models.UnlockWalletsError import com.tangem.domain.wallets.models.UnlockWalletsError
@ -62,6 +63,8 @@ internal interface WalletWarningsClickIntents {
fun onDislikeAppClick() fun onDislikeAppClick()
fun onCloseRateAppWarningClick() fun onCloseRateAppWarningClick()
fun onCloseSwapPromoClick()
} }
@Suppress("LongParameterList") @Suppress("LongParameterList")
@ -82,6 +85,7 @@ internal class WalletWarningsClickIntentsImplementer @Inject constructor(
private val reduxStateHolder: ReduxStateHolder, private val reduxStateHolder: ReduxStateHolder,
private val testerFeatureToggles: TesterFeatureToggles, private val testerFeatureToggles: TesterFeatureToggles,
private val dispatchers: CoroutineDispatcherProvider, private val dispatchers: CoroutineDispatcherProvider,
private val shouldShowSwapPromoWalletUseCase: ShouldShowSwapPromoWalletUseCase,
) : BaseWalletClickIntents(), WalletWarningsClickIntents { ) : BaseWalletClickIntents(), WalletWarningsClickIntents {
override fun onAddBackupCardClick() { override fun onAddBackupCardClick() {
@ -302,4 +306,10 @@ internal class WalletWarningsClickIntentsImplementer @Inject constructor(
remindToRateAppLaterUseCase() remindToRateAppLaterUseCase()
} }
} }
override fun onCloseSwapPromoClick() {
viewModelScope.launch(dispatchers.main) {
shouldShowSwapPromoWalletUseCase.neverToShow()
}
}
} }

View file

@ -152,4 +152,5 @@ include(":data:wallets")
include(":data:analytics") include(":data:analytics")
include(":data:transaction") include(":data:transaction")
include(":data:visa") include(":data:visa")
include(":data:promo")
// endregion Data modules // endregion Data modules