Updated on 2026-08-14
This commit is contained in:
parent
17d7fb3c02
commit
2934b42af5
29 changed files with 360 additions and 49 deletions
8
.gitignore
vendored
8
.gitignore
vendored
|
|
@ -3,6 +3,14 @@
|
|||
# Built application files
|
||||
/build
|
||||
/buildSrc
|
||||
**/build/.transforms/**
|
||||
**/build/classes/**
|
||||
**/build/generated/**
|
||||
**/build/intermediates/**
|
||||
**/build/kotlin/**
|
||||
**/build/libs/**
|
||||
**/build/outputs/**
|
||||
**/build/tmp/**
|
||||
|
||||
# Local configuration file (sdk path, etc)
|
||||
local.properties
|
||||
|
|
|
|||
|
|
@ -120,6 +120,12 @@ internal object OnrampDomainModule {
|
|||
return OnrampSaveTransactionUseCase(onrampTransactionRepository, onrampErrorResolver)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideOnrampSepaAvailableUseCase(onrampRepository: OnrampRepository): OnrampSepaAvailableUseCase {
|
||||
return OnrampSepaAvailableUseCase(onrampRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideOnrampUpdateTransactionStatusUseCase(
|
||||
|
|
|
|||
|
|
@ -207,6 +207,7 @@ internal class ChildFactory @Inject constructor(
|
|||
userWalletId = route.userWalletId,
|
||||
cryptoCurrency = route.currency,
|
||||
source = route.source,
|
||||
launchSepa = route.launchSepa,
|
||||
),
|
||||
componentFactory = onrampComponentFactory,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -238,6 +238,7 @@ sealed class AppRoute(val path: String) : Route {
|
|||
val source: OnrampSource,
|
||||
val userWalletId: UserWalletId,
|
||||
val currency: CryptoCurrency,
|
||||
val launchSepa: Boolean = false,
|
||||
) : AppRoute(path = "/onramp/${userWalletId.stringValue}/${currency.symbol}"), RouteBundleParams {
|
||||
override fun getBundle(): Bundle = bundle(serializer())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -878,7 +878,7 @@
|
|||
<string name="send_extras_hint_memo">Memo</string>
|
||||
<string name="send_fee_unreachable_error_text">Compruebe su conexión de red</string>
|
||||
<string name="send_fee_unreachable_error_title">Información de tarifa de red no accesible</string>
|
||||
<string name="send_from_title">Tú envías</string>
|
||||
<string name="send_from_title">Envía</string>
|
||||
<string name="send_from_wallet_android">Desde</string>
|
||||
<string name="send_from_wallet_name">De %s</string>
|
||||
<string name="send_gas_limit">Límite de gas</string>
|
||||
|
|
@ -951,10 +951,13 @@
|
|||
<string name="send_with_swap_confirm_title">Intercambiar y enviar</string>
|
||||
<string name="send_with_swap_convert_token_alert_message">¿Continuar con la conversión? Esto borrará sus datos anteriores.</string>
|
||||
<string name="send_with_swap_convert_token_alert_title">Confirmar Conversión</string>
|
||||
<string name="send_with_swap_correct_recipient_network_notification_message">El envío de cualquier otra moneda supondrá su pérdida irreversible.</string>
|
||||
<string name="send_with_swap_correct_recipient_network_notification_title">Seleccione la red de destino correcta</string>
|
||||
<string name="send_with_swap_notification_text">Envía cualquier token y lo convertiremos en el camino. Su destinatario obtiene exactamente lo que necesita, sin problemas.</string>
|
||||
<string name="send_with_swap_recipient_amount_success_title">El destinatario recibirá</string>
|
||||
<string name="send_with_swap_recipient_amount_text">Al destinatario</string>
|
||||
<string name="send_with_swap_recipient_amount_title">Cantidad a recibir</string>
|
||||
<string name="send_with_swap_recipient_get_amount">El destinatario recibe %s</string>
|
||||
<string name="send_with_swap_remove_convert_alert_message">¿Seguro que desea cancelar la conversión? Se borrarán sus datos anteriores.</string>
|
||||
<string name="send_with_swap_title">Enviar con swap</string>
|
||||
<string name="sent_transaction_sent_title">Transacción enviada</string>
|
||||
|
|
|
|||
|
|
@ -1043,13 +1043,13 @@
|
|||
<string name="send_with_swap_change_token_alert_message">受信トークンを変更してもよろしいですか? 変更すると、以前入力したデータがリセットされます。</string>
|
||||
<string name="send_with_swap_change_token_alert_title">トークンの変更</string>
|
||||
<string name="send_with_swap_confirm_title">スワップして送信</string>
|
||||
<string name="send_with_swap_convert_token_alert_message">変換を続行しますか? これにより以前のデータは消去されます。</string>
|
||||
<string name="send_with_swap_convert_token_alert_message">スワップを実行しますか?これにより以前のデータは消去されます。</string>
|
||||
<string name="send_with_swap_convert_token_alert_title">変換を確定</string>
|
||||
<string name="send_with_swap_correct_recipient_network_notification_message">その他の通貨を送信すると、取り返しのつかない損失が発生します。</string>
|
||||
<string name="send_with_swap_correct_recipient_network_notification_title">正しい受信者ネットワークを選択してください</string>
|
||||
<string name="send_with_swap_notification_text">受け取るトークンを自由に選んでください。受信者は、あなたが選択したトークンをシームレスに受け取ります。</string>
|
||||
<string name="send_with_swap_recipient_amount_success_title">受信者は受け取ります</string>
|
||||
<string name="send_with_swap_recipient_amount_text">受取人へ</string>
|
||||
<string name="send_with_swap_notification_text">受け取るトークンを選んでください。受信者は、あなたが選択したトークンをシームレスに受け取ります。</string>
|
||||
<string name="send_with_swap_recipient_amount_success_title">受信者が受け取ります</string>
|
||||
<string name="send_with_swap_recipient_amount_text">受信者へ</string>
|
||||
<string name="send_with_swap_recipient_amount_title">受取金額</string>
|
||||
<string name="send_with_swap_recipient_get_amount">受信者は%sを取得します</string>
|
||||
<string name="send_with_swap_remove_convert_alert_message">変換をキャンセルしてもよろしいですか?以前のデータは消去されます。</string>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,11 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<resources>
|
||||
<string name="account_details_archive_action">Архив</string>
|
||||
<string name="account_details_archive_description">Вы архивируете свой аккаунт, но в любое время можете вернуть его обратно</string>
|
||||
<string name="account_form_placeholder_edit_account">Аккаунт</string>
|
||||
<string name="account_form_placeholder_new_account">Новый аккаунт</string>
|
||||
<string name="account_unsaved_dialog_action_first">Продолжить редактирование</string>
|
||||
<string name="account_unsaved_dialog_title">Несохраненные изменения</string>
|
||||
<string name="action_buttons_buy_empty_search_message">Не нашли свой токен? Перейдите в раздел «Рынок» на главной странице и добавьте его в свой портфель для покупки.</string>
|
||||
<string name="action_buttons_sell_empty_search_message">Не нашли свой токен? Перейдите в раздел «Рынок» на главной странице и добавьте его в свой портфель для продажи.</string>
|
||||
<string name="action_buttons_sell_navigation_bar_title">Продать</string>
|
||||
|
|
@ -309,6 +315,7 @@
|
|||
<string name="domain_receive_assets_legacy_address">Legacy адрес</string>
|
||||
<string name="domain_receive_assets_navigation_title">Получить активы</string>
|
||||
<string name="domain_receive_assets_onboarding_description">Отправка средств в другой сети может повлечь потерю средств.</string>
|
||||
<string name="domain_receive_assets_onboarding_network_name">%s сети</string>
|
||||
<string name="domain_receive_assets_onboarding_title">Отправляйте средства, используя только</string>
|
||||
<string name="email_preface_wc_error">Привет, команда поддержки, у меня возникла ошибка с кодом: %s</string>
|
||||
<string name="email_subject_wc_error">Ошибка WalletConnect</string>
|
||||
|
|
@ -410,7 +417,16 @@
|
|||
<string name="home_button_scan">Сканировать</string>
|
||||
<string name="hot_crypto_add_token_subtitle">в %s</string>
|
||||
<string name="hot_crypto_token_network">В сети %s</string>
|
||||
<string name="hw_access_code_create_alert_title">Вы уверены, что хотите прервать процесс создания кода доступа?</string>
|
||||
<string name="hw_backup_hardware_title">Аппаратный кошелёк</string>
|
||||
<string name="hw_create_seed_description">Будьте в курсе новых функций и новостей</string>
|
||||
<string name="hw_upgrade_error_card_already_has_wallet">Это устройство не может быть использовано для апгрейда, оно уже содержит другой кошелек.</string>
|
||||
<string name="hw_upgrade_error_wallet2_card_required">Во время операции произошла ошибка.</string>
|
||||
<string name="hw_upgrade_key_migration_description">Приватные ключи будут перемещены из приложения в вашу Tangem карту или кольцо</string>
|
||||
<string name="hw_upgrade_key_migration_title">Миграция ключей</string>
|
||||
<string name="hw_upgrade_start_action">Начать апгрейд</string>
|
||||
<string name="hw_upgrade_start_title">Tangem кошелек</string>
|
||||
<string name="hw_upgrade_to_cold_banner_title">Сделать апгрейд кошелька до аппаратной версии.</string>
|
||||
<string name="information_generated_with_ai">Эта информация была сгенерирована ИИ.\nНажмите здесь, если обнаружили ошибку.</string>
|
||||
<string name="initial_message_change_access_code_body">Чтобы изменить код доступа, приложите карту или кольцо как показано выше и не убирайте до окончания операции</string>
|
||||
<string name="initial_message_change_passcode_body">Чтобы изменить пароль, приложите карту как показано выше и не убирайте до окончания операции</string>
|
||||
|
|
@ -928,7 +944,7 @@
|
|||
<string name="send_with_swap_change_token_alert_message">Вы уверены, что хотите изменить токен для получения? Это действие сбросит ранее введённые данные.</string>
|
||||
<string name="send_with_swap_change_token_alert_title">Изменение токена</string>
|
||||
<string name="send_with_swap_confirm_title">Обмен и отправка</string>
|
||||
<string name="send_with_swap_convert_token_alert_message">Продолжить с конвертацией? Это действие удалит предыдущие данные</string>
|
||||
<string name="send_with_swap_convert_token_alert_message">Продолжить с обменом? Это действие удалит предыдущие данные</string>
|
||||
<string name="send_with_swap_convert_token_alert_title">Подтвердить конвертацию</string>
|
||||
<string name="send_with_swap_notification_text">Выберите любой токен к получению. Ваш адресат получит ровно то, что вы выбрали — без лишних сложностей.</string>
|
||||
<string name="send_with_swap_recipient_amount_success_title">Будет получено</string>
|
||||
|
|
@ -1100,6 +1116,7 @@
|
|||
<string name="swapping_fee_estimation_error_text">Ошибка расчета комиссии. Пожалуйста, отправьте информацию в поддержку.</string>
|
||||
<string name="swapping_from_title">Вы отправляете</string>
|
||||
<string name="swapping_high_price_impact_description">Обмен этой суммы выбранных токенов может вызвать значительные колебания цены и уменьшить получаемую сумму.</string>
|
||||
<string name="swapping_high_price_impact_title">Высокое влияние на цену</string>
|
||||
<string name="swapping_insufficient_funds">Недостаточно средств</string>
|
||||
<string name="swapping_permission_header">Дать разрешение</string>
|
||||
<string name="swapping_swap_action">Обменять</string>
|
||||
|
|
@ -1228,9 +1245,11 @@
|
|||
<string name="wallet_promo_banner_button_title">Получить с 10% скидкой</string>
|
||||
<string name="wallet_promo_banner_description">Получите доступ к более чем 13 000 криптовалют. Покупайте, продавайте, обменивайте и стейкайте в один клик. Свяжите до трех карт для резервного копирования. </string>
|
||||
<string name="wallet_promo_banner_title">Откройте Tangem Wallet</string>
|
||||
<string name="wallet_settings_change_access_code_title">Изменить Код Доступа</string>
|
||||
<string name="wallet_settings_push_notifications_description">Получайте уведомления о входящих транзакциях в кошельке и обновлениях Tangem.</string>
|
||||
<string name="wallet_settings_push_notifications_huawei_warning">В настоящий момент push-уведомления могут не работать на устройствах Huawei. Мы уже работаем над решением этой проблемы и планируем исправление в ближайших обновлениях. Спасибо за понимание!</string>
|
||||
<string name="wallet_settings_push_notifications_title">Уведомления о транзакциях</string>
|
||||
<string name="wallet_settings_set_access_code_title">Установить Код Доступа</string>
|
||||
<string name="wallet_settings_title">Настройки кошелька</string>
|
||||
<string name="wallet_title">Tangem</string>
|
||||
<string name="warning_access_denied_message">Используйте %s или отсканируйте карту/кольцо, чтобы разблокировать доступ к вашему кошельку</string>
|
||||
|
|
|
|||
|
|
@ -1367,6 +1367,7 @@
|
|||
<string name="wc_errors_proposal_expired_subtitle">Будь ласка, згенеруйте новий URI та спробуйте ще раз</string>
|
||||
<string name="wc_errors_proposal_expired_title">Термін для з’єднання минув</string>
|
||||
<string name="wc_estimated_wallet_changes">Прогнозовані зміни</string>
|
||||
<string name="wc_fee_suggested">Пропоновано %s</string>
|
||||
<string name="wc_insufficient_warning_subtitle">Поповніть баланс, щоб покрити комісію мережі</string>
|
||||
<string name="wc_insufficient_warning_title">Недостатньо %1$s</string>
|
||||
<string name="wc_missing_required_network_description">Додайте %s мережі до вашого портфелю для цього гаманця</string>
|
||||
|
|
|
|||
|
|
@ -1060,7 +1060,7 @@
|
|||
<string name="send_with_swap_change_token_alert_message">Are you sure you want to change the receiving token? This will reset your previously entered data.</string>
|
||||
<string name="send_with_swap_change_token_alert_title">Changing token</string>
|
||||
<string name="send_with_swap_confirm_title">Swap and send</string>
|
||||
<string name="send_with_swap_convert_token_alert_message">Proceed with conversion? This will clear your previous data.</string>
|
||||
<string name="send_with_swap_convert_token_alert_message">Proceed with swap? This will clear your previous data.</string>
|
||||
<string name="send_with_swap_convert_token_alert_title">Confirm Conversion</string>
|
||||
<string name="send_with_swap_correct_recipient_network_notification_message">Sending any other currency will result in its irreversible loss.</string>
|
||||
<string name="send_with_swap_correct_recipient_network_notification_title">Select the correct recipient network</string>
|
||||
|
|
|
|||
BIN
core/ui/src/main/res/drawable/img_notification_sepa.webp
Normal file
BIN
core/ui/src/main/res/drawable/img_notification_sepa.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
|
|
@ -1,6 +1,8 @@
|
|||
package com.tangem.utils.coroutines
|
||||
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlin.coroutines.EmptyCoroutineContext
|
||||
|
||||
|
|
@ -54,4 +56,25 @@ fun CoroutineScope.launchOnCancellation(block: suspend () -> Unit) {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("LongParameterList", "MagicNumber")
|
||||
inline fun <T1, T2, T3, T4, T5, T6, R> combine6(
|
||||
flow1: Flow<T1>,
|
||||
flow2: Flow<T2>,
|
||||
flow3: Flow<T3>,
|
||||
flow4: Flow<T4>,
|
||||
flow5: Flow<T5>,
|
||||
flow6: Flow<T6>,
|
||||
crossinline transform: suspend (T1, T2, T3, T4, T5, T6) -> R,
|
||||
): Flow<R> = combine(flow1, flow2, flow3, flow4, flow5, flow6) { arr ->
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
transform(
|
||||
arr[0] as T1,
|
||||
arr[1] as T2,
|
||||
arr[2] as T3,
|
||||
arr[3] as T4,
|
||||
arr[4] as T5,
|
||||
arr[5] as T6,
|
||||
)
|
||||
}
|
||||
|
|
@ -261,6 +261,48 @@ internal class DefaultOnrampRepository(
|
|||
storeOnrampPairs(pairs = onrampPairs.await(), providers = providers.await())
|
||||
}
|
||||
|
||||
override suspend fun hasMercuryoSepaMethod(
|
||||
userWallet: UserWallet,
|
||||
currency: OnrampCurrency,
|
||||
country: OnrampCountry,
|
||||
cryptoCurrency: CryptoCurrency,
|
||||
): Boolean {
|
||||
return withContext(dispatchers.io) {
|
||||
val onrampPairs =
|
||||
safeApiCall(
|
||||
call = {
|
||||
onrampApi.getPairs(
|
||||
userWalletId = userWallet.walletId.stringValue,
|
||||
refCode = ExpressUtils.getRefCode(
|
||||
userWallet = userWallet,
|
||||
appPreferencesStore = appPreferencesStore,
|
||||
),
|
||||
body = OnrampPairsRequest(
|
||||
fromCurrencyCode = currency.code,
|
||||
countryCode = country.code,
|
||||
to = listOf(
|
||||
OnrampDestinationDTO(
|
||||
contractAddress = cryptoCurrency.getContractAddress(),
|
||||
network = cryptoCurrency.network.backendId,
|
||||
),
|
||||
),
|
||||
),
|
||||
).bind()
|
||||
},
|
||||
onError = {
|
||||
Timber.w(it, "Unable to fetch onramp pairs")
|
||||
throw it
|
||||
},
|
||||
)
|
||||
|
||||
val mercuryoProvider = onrampPairs.map { it.providers }.flatten()
|
||||
.find { it.providerId == MERCURYO_PROVIDER_ID }
|
||||
val hasSepaMethod = mercuryoProvider?.paymentMethods?.any { it == SEPA_METHOD_ID } ?: false
|
||||
|
||||
hasSepaMethod
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun fetchQuotes(userWallet: UserWallet, cryptoCurrency: CryptoCurrency, amount: Amount) =
|
||||
withContext(dispatchers.io) {
|
||||
val pairs = requireNotNull(pairsStore.getSyncOrNull(PAIRS_KEY)) {
|
||||
|
|
@ -554,5 +596,8 @@ internal class DefaultOnrampRepository(
|
|||
const val PROVIDER_THEME_LIGHT = "light"
|
||||
|
||||
const val REDIRECT_URL = "https://tangem.com/onramp"
|
||||
|
||||
const val SEPA_METHOD_ID = "sepa"
|
||||
const val MERCURYO_PROVIDER_ID = "mercuryo"
|
||||
}
|
||||
}
|
||||
|
|
@ -38,24 +38,19 @@ internal class DefaultPromoRepository(
|
|||
key = PreferencesKeys.getShouldShowPromoKey(promoId = promoId.name),
|
||||
default = true,
|
||||
).map { shouldShow ->
|
||||
if (promoId == PromoId.Referral) {
|
||||
runCatching {
|
||||
when (promoId) {
|
||||
PromoId.Referral -> runCatching {
|
||||
!referralRepository.isReferralParticipant(userWalletId) && shouldShow
|
||||
}.getOrDefault(false)
|
||||
} else {
|
||||
shouldShow
|
||||
PromoId.Sepa -> shouldShow
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun isReadyToShowTokenPromo(promoId: PromoId): Flow<Boolean> {
|
||||
return if (promoId == PromoId.Referral) {
|
||||
flowOf(false)
|
||||
} else {
|
||||
appPreferencesStore.get(
|
||||
PreferencesKeys.getShouldShowPromoKey(promoId = promoId.name),
|
||||
default = false,
|
||||
)
|
||||
return when (promoId) {
|
||||
PromoId.Referral -> flowOf(false)
|
||||
PromoId.Sepa -> flowOf(false)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,4 +6,5 @@ enum class OnrampSource(val analyticsName: String) {
|
|||
TOKEN_LONG_TAP("Long Tap"),
|
||||
TOKEN_DETAILS("Token"),
|
||||
MARKETS("Markets"),
|
||||
SEPA_BANNER("SEPA Banner"),
|
||||
}
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
package com.tangem.domain.onramp
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.getOrElse
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.onramp.repositories.OnrampRepository
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.onramp.model.OnrampCountry
|
||||
import com.tangem.domain.onramp.model.OnrampCurrency
|
||||
|
||||
class OnrampSepaAvailableUseCase(
|
||||
private val repository: OnrampRepository,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(
|
||||
userWallet: UserWallet,
|
||||
currency: OnrampCurrency,
|
||||
country: OnrampCountry,
|
||||
cryptoCurrency: CryptoCurrency,
|
||||
): Boolean {
|
||||
if (country.code !in SEPA_AVAILABLE_COUNTRY_CODES) {
|
||||
return false
|
||||
}
|
||||
|
||||
return Either.catch {
|
||||
repository.hasMercuryoSepaMethod(
|
||||
userWallet = userWallet,
|
||||
currency = currency,
|
||||
country = country,
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
)
|
||||
}.getOrElse { false }
|
||||
}
|
||||
|
||||
companion object {
|
||||
val SEPA_AVAILABLE_COUNTRY_CODES = listOf(
|
||||
"AL", // Albania
|
||||
"AD", // Andorra
|
||||
"AT", // Austria
|
||||
"BE", // Belgium
|
||||
"BG", // Bulgaria
|
||||
"HR", // Croatia
|
||||
"CY", // Cyprus
|
||||
"CZ", // Czech Republic
|
||||
"DK", // Denmark
|
||||
"EE", // Estonia
|
||||
"FI", // Finland
|
||||
"FR", // France
|
||||
"DE", // Germany
|
||||
"GR", // Greece
|
||||
"HU", // Hungary
|
||||
"IS", // Iceland
|
||||
"IE", // Ireland
|
||||
"IT", // Italy
|
||||
"LV", // Latvia
|
||||
"LI", // Liechtenstein
|
||||
"LT", // Lithuania
|
||||
"LU", // Luxembourg
|
||||
"MT", // Malta
|
||||
"MD", // Moldova
|
||||
"MC", // Monaco
|
||||
"ME", // Montenegro
|
||||
"NL", // Netherlands
|
||||
"MK", // North Macedonia
|
||||
"NO", // Norway
|
||||
"PL", // Poland
|
||||
"PT", // Portugal
|
||||
"RO", // Romania
|
||||
"SM", // San Marino
|
||||
"RS", // Serbia
|
||||
"SK", // Slovakia
|
||||
"SI", // Slovenia
|
||||
"ES", // Spain
|
||||
"SE", // Sweden
|
||||
"CH", // Switzerland
|
||||
"GB", // United Kingdom
|
||||
"VA", // Vatican City
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -15,6 +15,12 @@ interface OnrampRepository {
|
|||
suspend fun getCountriesSync(): List<OnrampCountry>?
|
||||
suspend fun getCountryByIp(userWallet: UserWallet): OnrampCountry
|
||||
suspend fun getStatus(userWallet: UserWallet, txId: String): OnrampStatus
|
||||
suspend fun hasMercuryoSepaMethod(
|
||||
userWallet: UserWallet,
|
||||
currency: OnrampCurrency,
|
||||
country: OnrampCountry,
|
||||
cryptoCurrency: CryptoCurrency,
|
||||
): Boolean
|
||||
suspend fun fetchCurrencies(userWallet: UserWallet)
|
||||
suspend fun fetchCountries(userWallet: UserWallet): List<OnrampCountry>
|
||||
suspend fun fetchPaymentMethodsIfAbsent(userWallet: UserWallet)
|
||||
|
|
|
|||
|
|
@ -27,4 +27,5 @@ data class PromoBanner(
|
|||
|
||||
enum class PromoId {
|
||||
Referral,
|
||||
Sepa,
|
||||
}
|
||||
|
|
@ -9,24 +9,24 @@ sealed class TokenSwapPromoAnalyticsEvent(
|
|||
) : AnalyticsEvent(category = "Promotion", event = event, params = params) {
|
||||
class NoticePromotionBanner(
|
||||
source: AnalyticsParam.ScreensSources,
|
||||
programName: ProgramName,
|
||||
program: Program,
|
||||
) : TokenSwapPromoAnalyticsEvent(
|
||||
event = "Notice - Promotion Banner",
|
||||
params = mapOf(
|
||||
AnalyticsParam.SOURCE to source.value,
|
||||
"Program Name" to programName.name,
|
||||
"Program Name" to program.programName,
|
||||
),
|
||||
)
|
||||
|
||||
class PromotionBannerClicked(
|
||||
source: AnalyticsParam.ScreensSources,
|
||||
programName: ProgramName,
|
||||
program: Program,
|
||||
action: BannerAction,
|
||||
) : TokenSwapPromoAnalyticsEvent(
|
||||
event = "Promo Banner Clicked",
|
||||
params = mapOf(
|
||||
AnalyticsParam.SOURCE to source.value,
|
||||
"Program Name" to programName.name,
|
||||
"Program Name" to program.programName,
|
||||
"Action" to action.action,
|
||||
),
|
||||
) {
|
||||
|
|
@ -37,7 +37,8 @@ sealed class TokenSwapPromoAnalyticsEvent(
|
|||
}
|
||||
|
||||
// Use it on new promo action
|
||||
enum class ProgramName {
|
||||
Empty,
|
||||
enum class Program(val programName: String) {
|
||||
Empty("Empty"),
|
||||
Sepa("Sepa"),
|
||||
}
|
||||
}
|
||||
|
|
@ -8,7 +8,12 @@ import com.tangem.domain.models.wallet.UserWalletId
|
|||
|
||||
interface OnrampComponent : ComposableContentComponent {
|
||||
|
||||
data class Params(val userWalletId: UserWalletId, val cryptoCurrency: CryptoCurrency, val source: OnrampSource)
|
||||
data class Params(
|
||||
val userWalletId: UserWalletId,
|
||||
val cryptoCurrency: CryptoCurrency,
|
||||
val source: OnrampSource,
|
||||
val launchSepa: Boolean = false,
|
||||
)
|
||||
|
||||
interface Factory : ComponentFactory<Params, OnrampComponent>
|
||||
}
|
||||
|
|
@ -15,6 +15,7 @@ internal interface OnrampMainComponent : ComposableContentComponent {
|
|||
val source: OnrampSource,
|
||||
val openSettings: () -> Unit,
|
||||
val openRedirectPage: (quote: OnrampProviderWithQuote.Data) -> Unit,
|
||||
val launchSepa: Boolean,
|
||||
)
|
||||
|
||||
interface Factory : ComponentFactory<Params, OnrampMainComponent>
|
||||
|
|
|
|||
|
|
@ -148,4 +148,8 @@ internal class OnrampStateFactory(
|
|||
decimals = currency.precision,
|
||||
type = AmountType.FiatType(currency.code),
|
||||
)
|
||||
|
||||
companion object {
|
||||
const val PREDEFINED_SEPA_AMOUNT = "100"
|
||||
}
|
||||
}
|
||||
|
|
@ -28,6 +28,7 @@ import com.tangem.domain.wallets.usecase.GetWalletsUseCase
|
|||
import com.tangem.features.onramp.main.OnrampMainComponent
|
||||
import com.tangem.features.onramp.main.entity.*
|
||||
import com.tangem.features.onramp.main.entity.factory.OnrampStateFactory
|
||||
import com.tangem.features.onramp.main.entity.factory.OnrampStateFactory.Companion.PREDEFINED_SEPA_AMOUNT
|
||||
import com.tangem.features.onramp.main.entity.factory.amount.OnrampAmountStateFactory
|
||||
import com.tangem.features.onramp.providers.entity.SelectProviderResult
|
||||
import com.tangem.features.onramp.utils.sendOnrampErrorEvent
|
||||
|
|
@ -64,6 +65,8 @@ internal class OnrampMainComponentModel @Inject constructor(
|
|||
|
||||
private val params: OnrampMainComponent.Params = paramsContainer.require()
|
||||
|
||||
private var isSepaLaunched = false
|
||||
|
||||
val userWallet = getWalletsUseCase.invokeSync().first { it.walletId == params.userWalletId }
|
||||
|
||||
private val stateFactory = OnrampStateFactory(
|
||||
|
|
@ -148,11 +151,16 @@ internal class OnrampMainComponentModel @Inject constructor(
|
|||
if (country == null) return@onEach
|
||||
_state.update {
|
||||
if (it is OnrampMainComponentUM.InitialLoading) {
|
||||
stateFactory.getReadyState(country.defaultCurrency)
|
||||
val state = stateFactory.getReadyState(country.defaultCurrency)
|
||||
if (params.launchSepa) {
|
||||
onAmountValueChanged(PREDEFINED_SEPA_AMOUNT)
|
||||
}
|
||||
state
|
||||
} else {
|
||||
amountStateFactory.getUpdatedCurrencyState(country.defaultCurrency)
|
||||
}
|
||||
}
|
||||
|
||||
updatePairsAndQuotes()
|
||||
},
|
||||
)
|
||||
|
|
@ -317,8 +325,18 @@ internal class OnrampMainComponentModel @Inject constructor(
|
|||
private fun selectOrUpdateQuote(quotes: List<OnrampQuote>): OnrampQuote? {
|
||||
val quoteToCheck = quotes.firstOrNull { it !is OnrampQuote.Error }
|
||||
|
||||
val sepaQuote = if (params.launchSepa && !isSepaLaunched) {
|
||||
isSepaLaunched = true
|
||||
|
||||
quotes.filterIsInstance<OnrampQuote.Data>().firstOrNull {
|
||||
it.provider.id == MERCURYO_PROVIDER_ID && it.paymentMethod.id == SEPA_METHOD_ID
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
// Check if amount, country or currency has changed
|
||||
val newQuote = if (checkLastInputState(quoteToCheck)) {
|
||||
val newQuote = sepaQuote ?: if (checkLastInputState(quoteToCheck)) {
|
||||
quoteToCheck
|
||||
} else {
|
||||
val state = state.value as? OnrampMainComponentUM.Content
|
||||
|
|
@ -420,5 +438,8 @@ internal class OnrampMainComponentModel @Inject constructor(
|
|||
|
||||
private companion object {
|
||||
const val UPDATE_DELAY = 10_000L
|
||||
|
||||
const val MERCURYO_PROVIDER_ID = "mercuryo"
|
||||
const val SEPA_METHOD_ID = "sepa"
|
||||
}
|
||||
}
|
||||
|
|
@ -81,6 +81,7 @@ internal class DefaultOnrampComponent @AssistedInject constructor(
|
|||
),
|
||||
)
|
||||
},
|
||||
launchSepa = params.launchSepa,
|
||||
),
|
||||
)
|
||||
is OnrampChild.RedirectPage -> onrampRedirectComponentFactory.create(
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ internal class TokenDetailsNotificationsAnalyticsSender(
|
|||
currency = cryptoCurrency,
|
||||
)
|
||||
is TokenDetailsNotification.SwapPromo -> TokenSwapPromoAnalyticsEvent.NoticePromotionBanner(
|
||||
programName = TokenSwapPromoAnalyticsEvent.ProgramName.Empty, // Use it on new promo action
|
||||
program = TokenSwapPromoAnalyticsEvent.Program.Empty, // Use it on new promo action
|
||||
source = AnalyticsParam.ScreensSources.Token,
|
||||
)
|
||||
is TokenDetailsNotification.KaspaIncompleteTransactionWarning -> TokenDetailsAnalyticsEvent.Notice.Reveal(
|
||||
|
|
|
|||
|
|
@ -850,7 +850,7 @@ internal class TokenDetailsModel @Inject constructor(
|
|||
analyticsEventsHandler.send(
|
||||
TokenSwapPromoAnalyticsEvent.PromotionBannerClicked(
|
||||
source = AnalyticsParam.ScreensSources.Token,
|
||||
programName = TokenSwapPromoAnalyticsEvent.ProgramName.Empty, // Use it on new promo action
|
||||
program = TokenSwapPromoAnalyticsEvent.Program.Empty, // Use it on new promo action
|
||||
action = TokenSwapPromoAnalyticsEvent.PromotionBannerClicked.BannerAction.Closed,
|
||||
),
|
||||
)
|
||||
|
|
@ -863,7 +863,7 @@ internal class TokenDetailsModel @Inject constructor(
|
|||
analyticsEventsHandler.send(
|
||||
TokenSwapPromoAnalyticsEvent.PromotionBannerClicked(
|
||||
source = AnalyticsParam.ScreensSources.Token,
|
||||
programName = TokenSwapPromoAnalyticsEvent.ProgramName.Empty, // Use it on new promo action
|
||||
program = TokenSwapPromoAnalyticsEvent.Program.Empty, // Use it on new promo action
|
||||
action = TokenSwapPromoAnalyticsEvent.PromotionBannerClicked.BannerAction.Clicked,
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import com.tangem.domain.models.wallet.UserWallet
|
|||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.models.wallet.requireColdWallet
|
||||
import com.tangem.domain.networks.multi.MultiNetworkStatusFetcher
|
||||
import com.tangem.domain.onramp.model.OnrampSource
|
||||
import com.tangem.domain.promo.ShouldShowPromoWalletUseCase
|
||||
import com.tangem.domain.promo.models.PromoId
|
||||
import com.tangem.domain.quotes.multi.MultiQuoteStatusFetcher
|
||||
|
|
@ -75,7 +76,7 @@ internal interface WalletWarningsClickIntents {
|
|||
|
||||
fun onClosePromoClick(promoId: PromoId)
|
||||
|
||||
fun onPromoClick(promoId: PromoId)
|
||||
fun onPromoClick(promoId: PromoId, cryptoCurrency: CryptoCurrency? = null)
|
||||
|
||||
fun onSupportClick()
|
||||
|
||||
|
|
@ -92,7 +93,7 @@ internal interface WalletWarningsClickIntents {
|
|||
fun onFinishWalletActivationClick()
|
||||
}
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@Suppress("LargeClass", "LongParameterList")
|
||||
@ModelScoped
|
||||
internal class WalletWarningsClickIntentsImplementor @Inject constructor(
|
||||
private val stateHolder: WalletStateController,
|
||||
|
|
@ -260,26 +261,46 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor(
|
|||
|
||||
override fun onClosePromoClick(promoId: PromoId) {
|
||||
analyticsEventHandler.send(
|
||||
if (promoId == PromoId.Referral) {
|
||||
MainScreen.ReferralPromoButtonDismiss
|
||||
} else {
|
||||
TokenSwapPromoAnalyticsEvent.PromotionBannerClicked(
|
||||
when (promoId) {
|
||||
PromoId.Referral -> MainScreen.ReferralPromoButtonDismiss
|
||||
PromoId.Sepa -> TokenSwapPromoAnalyticsEvent.PromotionBannerClicked(
|
||||
source = AnalyticsParam.ScreensSources.Main,
|
||||
programName = TokenSwapPromoAnalyticsEvent.ProgramName.Empty, // Use it on new promo action
|
||||
program = TokenSwapPromoAnalyticsEvent.Program.Sepa,
|
||||
action = TokenSwapPromoAnalyticsEvent.PromotionBannerClicked.BannerAction.Closed,
|
||||
)
|
||||
},
|
||||
|
||||
)
|
||||
modelScope.launch(dispatchers.main) {
|
||||
shouldShowPromoWalletUseCase.neverToShow(promoId)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPromoClick(promoId: PromoId) {
|
||||
if (promoId == PromoId.Referral) {
|
||||
val userWallet = getSelectedUserWallet() ?: return
|
||||
analyticsEventHandler.send(MainScreen.ReferralPromoButtonParticipate)
|
||||
appRouter.push(AppRoute.ReferralProgram(userWalletId = userWallet.walletId))
|
||||
override fun onPromoClick(promoId: PromoId, cryptoCurrency: CryptoCurrency?) {
|
||||
val userWallet = getSelectedUserWallet() ?: return
|
||||
when (promoId) {
|
||||
PromoId.Referral -> {
|
||||
analyticsEventHandler.send(MainScreen.ReferralPromoButtonParticipate)
|
||||
appRouter.push(AppRoute.ReferralProgram(userWalletId = userWallet.walletId))
|
||||
}
|
||||
PromoId.Sepa -> {
|
||||
analyticsEventHandler.send(
|
||||
TokenSwapPromoAnalyticsEvent.PromotionBannerClicked(
|
||||
source = AnalyticsParam.ScreensSources.Main,
|
||||
program = TokenSwapPromoAnalyticsEvent.Program.Sepa,
|
||||
action = TokenSwapPromoAnalyticsEvent.PromotionBannerClicked.BannerAction.Clicked,
|
||||
),
|
||||
)
|
||||
cryptoCurrency ?: return
|
||||
appRouter.push(
|
||||
AppRoute.Onramp(
|
||||
userWalletId = userWallet.walletId,
|
||||
currency = cryptoCurrency,
|
||||
source = OnrampSource.SEPA_BANNER,
|
||||
launchSepa = true,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import com.tangem.core.analytics.models.AnalyticsEvent
|
|||
import com.tangem.core.analytics.models.AnalyticsParam
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.domain.tokens.model.analytics.TokenSwapPromoAnalyticsEvent
|
||||
import com.tangem.domain.tokens.model.analytics.TokenSwapPromoAnalyticsEvent.ProgramName
|
||||
import com.tangem.domain.tokens.model.analytics.TokenSwapPromoAnalyticsEvent.Program
|
||||
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
|
||||
|
|
@ -51,7 +51,11 @@ internal class WalletWarningsAnalyticsSender @Inject constructor(
|
|||
is WalletNotification.NoteMigration -> MainScreen.NotePromo
|
||||
is WalletNotification.SwapPromo -> TokenSwapPromoAnalyticsEvent.NoticePromotionBanner(
|
||||
source = AnalyticsParam.ScreensSources.Main,
|
||||
programName = ProgramName.Empty, // Use it on new promo action
|
||||
program = Program.Empty, // Use it on new promo action
|
||||
)
|
||||
is WalletNotification.Sepa -> TokenSwapPromoAnalyticsEvent.NoticePromotionBanner(
|
||||
source = AnalyticsParam.ScreensSources.Main,
|
||||
program = Program.Sepa,
|
||||
)
|
||||
is WalletNotification.ReferralPromo -> MainScreen.ReferralPromo
|
||||
is WalletNotification.UnlockWallets -> null // See [SelectedWalletAnalyticsSender]
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
@file:Suppress("MaximumLineLength")
|
||||
|
||||
package com.tangem.feature.wallet.presentation.wallet.domain
|
||||
|
||||
import arrow.core.getOrElse
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.ui.components.notifications.NotificationConfig.ButtonsState
|
||||
import com.tangem.core.ui.components.notifications.NotificationConfig.IconTint
|
||||
|
|
@ -14,9 +17,12 @@ import com.tangem.domain.models.currency.CryptoCurrency
|
|||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.tokenlist.TokenList
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.onramp.GetOnrampCountryUseCase
|
||||
import com.tangem.domain.onramp.OnrampSepaAvailableUseCase
|
||||
import com.tangem.domain.promo.ShouldShowPromoWalletUseCase
|
||||
import com.tangem.domain.promo.models.PromoId
|
||||
import com.tangem.domain.settings.IsReadyToShowRateAppUseCase
|
||||
import com.tangem.domain.tokens.GetCryptoCurrenciesUseCase
|
||||
import com.tangem.domain.tokens.error.TokenListError
|
||||
import com.tangem.domain.wallets.models.SeedPhraseNotificationsStatus
|
||||
import com.tangem.domain.wallets.usecase.IsNeedToBackupUseCase
|
||||
|
|
@ -24,11 +30,13 @@ import com.tangem.domain.wallets.usecase.SeedPhraseNotificationUseCase
|
|||
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
|
||||
import com.tangem.feature.wallet.impl.R
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNotification
|
||||
import com.tangem.lib.crypto.BlockchainUtils.isBitcoin
|
||||
import com.tangem.utils.coroutines.combine6
|
||||
import com.tangem.utils.extensions.isPositive
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
|
|
@ -41,19 +49,22 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
|
|||
private val backupValidator: BackupValidator,
|
||||
private val seedPhraseNotificationUseCase: SeedPhraseNotificationUseCase,
|
||||
private val shouldShowPromoWalletUseCase: ShouldShowPromoWalletUseCase,
|
||||
private val getCryptoCurrenciesUseCase: GetCryptoCurrenciesUseCase,
|
||||
private val onrampSepaAvailableUseCase: OnrampSepaAvailableUseCase,
|
||||
private val getOnrampCountryUseCase: GetOnrampCountryUseCase,
|
||||
) {
|
||||
|
||||
@Suppress("MagicNumber", "MaximumLineLength")
|
||||
fun create(userWallet: UserWallet, clickIntents: WalletClickIntents): Flow<ImmutableList<WalletNotification>> {
|
||||
val cardTypesResolver = (userWallet as? UserWallet.Cold)?.scanResponse?.cardTypesResolver
|
||||
|
||||
return combine(
|
||||
flow = tokenListStore.getOrThrow(userWallet.walletId),
|
||||
return combine6(
|
||||
flow1 = tokenListStore.getOrThrow(userWallet.walletId),
|
||||
flow2 = isReadyToShowRateAppUseCase(),
|
||||
flow3 = isNeedToBackupUseCase(userWallet.walletId),
|
||||
flow4 = seedPhraseNotificationUseCase(userWalletId = userWallet.walletId),
|
||||
flow5 = shouldShowPromoWalletUseCase(userWalletId = userWallet.walletId, promoId = PromoId.Referral),
|
||||
) { maybeTokenList, isReadyToShowRating, isNeedToBackup, seedPhraseIssueStatus, shouldShowReferralPromo ->
|
||||
flow6 = shouldShowPromoWalletUseCase(userWalletId = userWallet.walletId, promoId = PromoId.Sepa),
|
||||
) { maybeTokenList, isReadyToShowRating, isNeedToBackup, seedPhraseIssueStatus, shouldShowReferralPromo, shouldShowSepaBanner ->
|
||||
buildList {
|
||||
addUsedOutdatedDataNotification(maybeTokenList)
|
||||
|
||||
|
|
@ -63,6 +74,8 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
|
|||
|
||||
addReferralPromoNotification(cardTypesResolver, clickIntents, shouldShowReferralPromo)
|
||||
|
||||
addSepaPromoNotification(userWallet, clickIntents, shouldShowSepaBanner)
|
||||
|
||||
addInformationalNotifications(cardTypesResolver, maybeTokenList, clickIntents)
|
||||
|
||||
addWarningNotifications(cardTypesResolver, maybeTokenList, isNeedToBackup, clickIntents)
|
||||
|
|
@ -217,6 +230,39 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
private suspend fun MutableList<WalletNotification>.addSepaPromoNotification(
|
||||
userWallet: UserWallet,
|
||||
clickIntents: WalletClickIntents,
|
||||
shouldShowSepaPromo: Boolean,
|
||||
) {
|
||||
val currencies = getCryptoCurrenciesUseCase(userWalletId = userWallet.walletId).getOrElse {
|
||||
Timber.e("Error on getting crypto currency list")
|
||||
return
|
||||
}
|
||||
|
||||
val bitcoinCurrency = currencies.find { isBitcoin(it.network.rawId) } ?: return
|
||||
|
||||
val country = getOnrampCountryUseCase.invokeSync(userWallet).getOrElse {
|
||||
Timber.e("Error on getting onramp country")
|
||||
return
|
||||
}
|
||||
|
||||
val isSepaAvailable = onrampSepaAvailableUseCase(
|
||||
userWallet = userWallet,
|
||||
country = country,
|
||||
currency = country.defaultCurrency,
|
||||
cryptoCurrency = bitcoinCurrency,
|
||||
)
|
||||
|
||||
addIf(
|
||||
element = WalletNotification.Sepa(
|
||||
onCloseClick = { clickIntents.onClosePromoClick(promoId = PromoId.Sepa) },
|
||||
onClick = { clickIntents.onPromoClick(promoId = PromoId.Sepa, bitcoinCurrency) },
|
||||
),
|
||||
condition = shouldShowSepaPromo && isSepaAvailable,
|
||||
)
|
||||
}
|
||||
|
||||
private fun MutableList<WalletNotification>.addWarningNotifications(
|
||||
cardTypesResolver: CardTypesResolver?,
|
||||
tokenList: Lce<TokenListError, TokenList>,
|
||||
|
|
|
|||
|
|
@ -287,4 +287,21 @@ sealed class WalletNotification(val config: NotificationConfig) {
|
|||
iconSize = 54.dp,
|
||||
),
|
||||
)
|
||||
|
||||
data class Sepa(
|
||||
val onCloseClick: () -> Unit,
|
||||
val onClick: () -> Unit,
|
||||
) : WalletNotification(
|
||||
config = NotificationConfig(
|
||||
title = resourceReference(R.string.notification_sepa_title),
|
||||
subtitle = resourceReference(R.string.notification_sepa_text),
|
||||
iconResId = R.drawable.img_notification_sepa,
|
||||
onCloseClick = onCloseClick,
|
||||
buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig(
|
||||
text = resourceReference(R.string.notification_sepa_button),
|
||||
onClick = onClick,
|
||||
),
|
||||
iconSize = 54.dp,
|
||||
),
|
||||
)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue