Updated on 2026-08-14
|
|
@ -18,7 +18,7 @@ class ForegroundActivityObserver : ActivityResultCaller {
|
|||
|
||||
val foregroundActivity: AppCompatActivity?
|
||||
get() = activities.entries
|
||||
.firstOrNull { it.value?.isDestroyed ?: false }
|
||||
.firstOrNull { it.value?.isDestroyed == false }
|
||||
?.value
|
||||
|
||||
internal val callbacks: ActivityLifecycleCallbacks
|
||||
|
|
|
|||
|
|
@ -219,4 +219,22 @@ internal object OnrampDomainModule {
|
|||
): GetOnrampRedirectUrlUseCase {
|
||||
return GetOnrampRedirectUrlUseCase(onrampRepository, transactionRepository, onrampErrorResolver)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideFetchOnrampCurrenciesUseCase(
|
||||
onrampRepository: OnrampRepository,
|
||||
onrampErrorResolver: OnrampErrorResolver,
|
||||
): FetchOnrampCurrenciesUseCase {
|
||||
return FetchOnrampCurrenciesUseCase(onrampRepository, onrampErrorResolver)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideFetchOnrampCountriesUseCase(
|
||||
onrampRepository: OnrampRepository,
|
||||
onrampErrorResolver: OnrampErrorResolver,
|
||||
): FetchOnrampCountriesUseCase {
|
||||
return FetchOnrampCountriesUseCase(onrampRepository, onrampErrorResolver)
|
||||
}
|
||||
}
|
||||
|
|
@ -239,6 +239,7 @@ object OnboardingHelper {
|
|||
|
||||
scope.launch {
|
||||
val homeFeatureToggles = store.inject(DaggerGraphState::homeFeatureToggles)
|
||||
val onrampFeatureToggles = store.inject(DaggerGraphState::onrampFeatureToggles)
|
||||
|
||||
val isRussia = if (homeFeatureToggles.isMigrateUserCountryCodeEnabled) {
|
||||
val getUserCountryCodeUseCase = store.inject(DaggerGraphState::getUserCountryUseCase)
|
||||
|
|
@ -248,7 +249,7 @@ object OnboardingHelper {
|
|||
globalState.userCountryCode == RUSSIA_COUNTRY_CODE
|
||||
}
|
||||
|
||||
if (isRussia) {
|
||||
if (isRussia && !onrampFeatureToggles.isFeatureEnabled) {
|
||||
val dialogData = AppDialog.RussianCardholdersWarningDialog.Data(topUpUrl)
|
||||
store.dispatchDialogShow(AppDialog.RussianCardholdersWarningDialog(dialogData))
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -95,7 +95,7 @@ object TradeCryptoMiddleware {
|
|||
|
||||
scope.launch {
|
||||
val homeFeatureToggles = store.inject(DaggerGraphState::homeFeatureToggles)
|
||||
|
||||
val onrampFeatureToggles = store.inject(DaggerGraphState::onrampFeatureToggles)
|
||||
val isRussia = if (homeFeatureToggles.isMigrateUserCountryCodeEnabled) {
|
||||
val getUserCountryCodeUseCase = store.inject(DaggerGraphState::getUserCountryUseCase)
|
||||
|
||||
|
|
@ -104,7 +104,7 @@ object TradeCryptoMiddleware {
|
|||
state()?.globalState?.userCountryCode == RUSSIA_COUNTRY_CODE
|
||||
}
|
||||
|
||||
if (action.checkUserLocation && isRussia) {
|
||||
if (action.checkUserLocation && isRussia && !onrampFeatureToggles.isFeatureEnabled) {
|
||||
val dialogData = topUrl?.let {
|
||||
AppDialog.RussianCardholdersWarningDialog.Data(topUpUrl = it)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -227,6 +227,15 @@ sealed class NotificationUM(val config: NotificationConfig) {
|
|||
wrappedList(cryptoAmount, fiatAmount),
|
||||
),
|
||||
)
|
||||
|
||||
data class OnrampErrorNotification(val onRefresh: () -> Unit) : Warning(
|
||||
title = resourceReference(R.string.common_error),
|
||||
subtitle = resourceReference(R.string.common_unknown_error),
|
||||
buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig(
|
||||
text = resourceReference(R.string.warning_button_refresh),
|
||||
onClick = onRefresh,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
open class Info(
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
package com.tangem.datasource.di
|
||||
|
||||
import com.tangem.datasource.local.datastore.RuntimeDataStore
|
||||
import com.tangem.datasource.local.onramp.countries.DefaultOnrampCountriesStore
|
||||
import com.tangem.datasource.local.onramp.countries.OnrampCountriesStore
|
||||
import com.tangem.datasource.local.onramp.currencies.DefaultOnrampCurrenciesStore
|
||||
import com.tangem.datasource.local.onramp.currencies.OnrampCurrenciesStore
|
||||
import com.tangem.datasource.local.onramp.pairs.DefaultOnrampPairsStore
|
||||
import com.tangem.datasource.local.onramp.pairs.OnrampPairsStore
|
||||
import com.tangem.datasource.local.onramp.paymentmethods.DefaultOnrampPaymentMethodsStore
|
||||
|
|
@ -34,4 +38,16 @@ internal object OnrampStoreModule {
|
|||
fun provideOnrampQuotesStore(): OnrampQuotesStore {
|
||||
return DefaultOnrampQuotesStore(dataStore = RuntimeDataStore())
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideOnrampCountriesStore(): OnrampCountriesStore {
|
||||
return DefaultOnrampCountriesStore(dataStore = RuntimeDataStore())
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideOnrampCurrencies(): OnrampCurrenciesStore {
|
||||
return DefaultOnrampCurrenciesStore(dataStore = RuntimeDataStore())
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.tangem.datasource.local.onramp.countries
|
||||
|
||||
import com.tangem.datasource.local.datastore.core.StringKeyDataStore
|
||||
import com.tangem.datasource.local.datastore.core.StringKeyDataStoreDecorator
|
||||
import com.tangem.domain.onramp.model.OnrampCountry
|
||||
|
||||
internal class DefaultOnrampCountriesStore(
|
||||
dataStore: StringKeyDataStore<List<OnrampCountry>>,
|
||||
) : OnrampCountriesStore, StringKeyDataStoreDecorator<String, List<OnrampCountry>>(dataStore) {
|
||||
override fun provideStringKey(key: String): String = key
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.tangem.datasource.local.onramp.countries
|
||||
|
||||
import com.tangem.domain.onramp.model.OnrampCountry
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface OnrampCountriesStore {
|
||||
suspend fun getSyncOrNull(key: String): List<OnrampCountry>?
|
||||
fun get(key: String): Flow<List<OnrampCountry>>
|
||||
suspend fun store(key: String, value: List<OnrampCountry>)
|
||||
suspend fun clear()
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.tangem.datasource.local.onramp.currencies
|
||||
|
||||
import com.tangem.datasource.local.datastore.core.StringKeyDataStore
|
||||
import com.tangem.datasource.local.datastore.core.StringKeyDataStoreDecorator
|
||||
import com.tangem.domain.onramp.model.OnrampCurrency
|
||||
|
||||
internal class DefaultOnrampCurrenciesStore(
|
||||
dataStore: StringKeyDataStore<List<OnrampCurrency>>,
|
||||
) : OnrampCurrenciesStore, StringKeyDataStoreDecorator<String, List<OnrampCurrency>>(dataStore) {
|
||||
override fun provideStringKey(key: String): String = key
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.tangem.datasource.local.onramp.currencies
|
||||
|
||||
import com.tangem.domain.onramp.model.OnrampCurrency
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface OnrampCurrenciesStore {
|
||||
suspend fun getSyncOrNull(key: String): List<OnrampCurrency>?
|
||||
fun get(key: String): Flow<List<OnrampCurrency>>
|
||||
suspend fun store(key: String, value: List<OnrampCurrency>)
|
||||
suspend fun clear()
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ package com.tangem.data.onramp
|
|||
import com.tangem.data.onramp.converters.error.OnrampErrorConverter
|
||||
import com.tangem.datasource.api.common.response.ApiResponseError
|
||||
import com.tangem.domain.onramp.model.error.OnrampError
|
||||
import com.tangem.domain.onramp.model.error.OnrampPairsError
|
||||
import com.tangem.domain.onramp.model.error.OnrampRedirectError
|
||||
import com.tangem.domain.onramp.repositories.OnrampErrorResolver
|
||||
|
||||
|
|
@ -17,6 +18,7 @@ internal class DefaultOnrampErrorResolver(
|
|||
}
|
||||
is OnrampRedirectError.WrongRequestId -> OnrampError.RedirectError.WrongRequestId
|
||||
is OnrampRedirectError.VerificationFailed -> OnrampError.RedirectError.VerificationFailed
|
||||
is OnrampPairsError.PairsNotFound -> OnrampError.PairsNotFound
|
||||
else -> {
|
||||
OnrampError.DomainError(throwable.message)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,8 @@ import com.tangem.datasource.api.onramp.models.response.model.OnrampCurrencyDTO
|
|||
import com.tangem.datasource.api.onramp.models.response.model.OnrampPairDTO
|
||||
import com.tangem.datasource.api.onramp.models.response.model.PaymentMethodDTO
|
||||
import com.tangem.datasource.crypto.DataSignatureVerifier
|
||||
import com.tangem.datasource.local.onramp.countries.OnrampCountriesStore
|
||||
import com.tangem.datasource.local.onramp.currencies.OnrampCurrenciesStore
|
||||
import com.tangem.datasource.local.onramp.pairs.OnrampPairsStore
|
||||
import com.tangem.datasource.local.onramp.paymentmethods.OnrampPaymentMethodsStore
|
||||
import com.tangem.datasource.local.onramp.quotes.OnrampQuotesStore
|
||||
|
|
@ -38,6 +40,7 @@ import com.tangem.domain.apptheme.model.AppThemeMode
|
|||
import com.tangem.domain.onramp.model.*
|
||||
import com.tangem.domain.onramp.model.cache.OnrampTransaction
|
||||
import com.tangem.domain.onramp.model.error.OnrampError
|
||||
import com.tangem.domain.onramp.model.error.OnrampPairsError
|
||||
import com.tangem.domain.onramp.model.error.OnrampRedirectError
|
||||
import com.tangem.domain.onramp.repositories.OnrampRepository
|
||||
import com.tangem.domain.tokens.model.Amount
|
||||
|
|
@ -65,6 +68,8 @@ internal class DefaultOnrampRepository(
|
|||
private val paymentMethodsStore: OnrampPaymentMethodsStore,
|
||||
private val pairsStore: OnrampPairsStore,
|
||||
private val quotesStore: OnrampQuotesStore,
|
||||
private val countriesStore: OnrampCountriesStore,
|
||||
private val currenciesStore: OnrampCurrenciesStore,
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
private val dataSignatureVerifier: DataSignatureVerifier,
|
||||
moshi: Moshi,
|
||||
|
|
@ -78,16 +83,36 @@ internal class DefaultOnrampRepository(
|
|||
private val onrampErrorAdapter = moshi.adapter(ExpressErrorResponse::class.java)
|
||||
private val onrampErrorConverter = OnrampErrorConverter(onrampErrorAdapter)
|
||||
|
||||
override suspend fun getCurrencies(): List<OnrampCurrency> = withContext(dispatchers.io) {
|
||||
onrampApi.getCurrencies()
|
||||
.getOrThrow()
|
||||
.map(currencyConverter::convert)
|
||||
override suspend fun getCurrencies(): Flow<List<OnrampCurrency>> = withContext(dispatchers.io) {
|
||||
currenciesStore.get(CURRENCIES_KEY)
|
||||
}
|
||||
|
||||
override suspend fun getCountries(): List<OnrampCountry> = withContext(dispatchers.io) {
|
||||
onrampApi.getCountries()
|
||||
override suspend fun fetchCurrencies() = withContext(dispatchers.io) {
|
||||
if (!currenciesStore.getSyncOrNull(CURRENCIES_KEY).isNullOrEmpty()) return@withContext
|
||||
|
||||
val result = onrampApi.getCurrencies()
|
||||
.getOrThrow()
|
||||
.map(currencyConverter::convert)
|
||||
|
||||
currenciesStore.store(CURRENCIES_KEY, result)
|
||||
}
|
||||
|
||||
override suspend fun getCountries(): Flow<List<OnrampCountry>> = withContext(dispatchers.io) {
|
||||
countriesStore.get(COUNTRIES_KEY)
|
||||
}
|
||||
|
||||
override suspend fun getCountriesSync(): List<OnrampCountry>? {
|
||||
return countriesStore.getSyncOrNull(COUNTRIES_KEY)
|
||||
}
|
||||
|
||||
override suspend fun fetchCountries() = withContext(dispatchers.io) {
|
||||
if (!countriesStore.getSyncOrNull(COUNTRIES_KEY).isNullOrEmpty()) return@withContext
|
||||
|
||||
val result = onrampApi.getCountries()
|
||||
.getOrThrow()
|
||||
.map(countryConverter::convert)
|
||||
|
||||
countriesStore.store(COUNTRIES_KEY, result)
|
||||
}
|
||||
|
||||
override suspend fun getCountryByIp(): OnrampCountry = withContext(dispatchers.io) {
|
||||
|
|
@ -338,9 +363,12 @@ internal class DefaultOnrampRepository(
|
|||
paymentMethodsStore.clear()
|
||||
pairsStore.clear()
|
||||
quotesStore.clear()
|
||||
countriesStore.clear()
|
||||
currenciesStore.clear()
|
||||
}
|
||||
|
||||
private suspend fun storeOnrampPairs(pairs: List<OnrampPairDTO>, providers: List<ExchangeProvider>) {
|
||||
if (pairs.isEmpty() || providers.isEmpty()) throw OnrampPairsError.PairsNotFound
|
||||
val onrampPaymentMethods = getPaymentMethods()
|
||||
val onrampPairs = pairs.map { pair ->
|
||||
val onrampProviders = pair.providers.mapNotNull { onrampProviderDTO ->
|
||||
|
|
@ -453,6 +481,8 @@ internal class DefaultOnrampRepository(
|
|||
const val SELECTED_PAYMENT_METHOD_KEY = "onramp_selected_payment_method"
|
||||
const val PAIRS_KEY = "onramp_pairs"
|
||||
const val QUOTES_KEY = "onramp_quotes"
|
||||
const val COUNTRIES_KEY = "onramp_countries"
|
||||
const val CURRENCIES_KEY = "onramp_currencies"
|
||||
const val PROVIDER_THEME_DARK = "dark"
|
||||
const val PROVIDER_THEME_LIGHT = "light"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ import com.tangem.datasource.api.express.models.response.ExpressErrorResponse
|
|||
import com.tangem.datasource.api.onramp.OnrampApi
|
||||
import com.tangem.datasource.crypto.DataSignatureVerifier
|
||||
import com.tangem.datasource.di.NetworkMoshi
|
||||
import com.tangem.datasource.local.onramp.countries.OnrampCountriesStore
|
||||
import com.tangem.datasource.local.onramp.currencies.OnrampCurrenciesStore
|
||||
import com.tangem.datasource.local.onramp.pairs.OnrampPairsStore
|
||||
import com.tangem.datasource.local.onramp.paymentmethods.OnrampPaymentMethodsStore
|
||||
import com.tangem.datasource.local.onramp.quotes.OnrampQuotesStore
|
||||
|
|
@ -39,6 +41,8 @@ internal object OnrampDataModule {
|
|||
paymentMethodsStore: OnrampPaymentMethodsStore,
|
||||
pairsStore: OnrampPairsStore,
|
||||
quotesStore: OnrampQuotesStore,
|
||||
countriesStore: OnrampCountriesStore,
|
||||
currenciesStore: OnrampCurrenciesStore,
|
||||
walletManagersFacade: WalletManagersFacade,
|
||||
dataSignatureVerifier: DataSignatureVerifier,
|
||||
@NetworkMoshi moshi: Moshi,
|
||||
|
|
@ -51,6 +55,8 @@ internal object OnrampDataModule {
|
|||
paymentMethodsStore = paymentMethodsStore,
|
||||
pairsStore = pairsStore,
|
||||
quotesStore = quotesStore,
|
||||
currenciesStore = currenciesStore,
|
||||
countriesStore = countriesStore,
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
dataSignatureVerifier = dataSignatureVerifier,
|
||||
moshi = moshi,
|
||||
|
|
|
|||
|
|
@ -29,4 +29,6 @@ sealed class OnrampError {
|
|||
data class DomainError(
|
||||
val description: String?,
|
||||
) : OnrampError()
|
||||
|
||||
data object PairsNotFound : OnrampError()
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package com.tangem.domain.onramp.model.error
|
||||
|
||||
sealed class OnrampPairsError : Throwable() {
|
||||
data object PairsNotFound : OnrampPairsError()
|
||||
}
|
||||
|
|
@ -26,7 +26,7 @@ class CheckOnrampAvailabilityUseCase(
|
|||
}
|
||||
|
||||
private suspend fun proceedWithSavedCountry(savedCountry: OnrampCountry): OnrampAvailability {
|
||||
val countries = repository.getCountries()
|
||||
val countries = repository.getCountriesSync().orEmpty()
|
||||
val onrampAvailable = countries.find { it == savedCountry }?.onrampAvailable ?: false
|
||||
return if (onrampAvailable) {
|
||||
val currency = repository.getDefaultCurrencySync() ?: run {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
package com.tangem.domain.onramp
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.domain.onramp.model.error.OnrampError
|
||||
import com.tangem.domain.onramp.repositories.OnrampErrorResolver
|
||||
import com.tangem.domain.onramp.repositories.OnrampRepository
|
||||
|
||||
class FetchOnrampCountriesUseCase(
|
||||
private val repository: OnrampRepository,
|
||||
private val errorResolver: OnrampErrorResolver,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(): Either<OnrampError, Unit> {
|
||||
return Either.catch { repository.fetchCountries() }.mapLeft(errorResolver::resolve)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package com.tangem.domain.onramp
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.domain.onramp.model.error.OnrampError
|
||||
import com.tangem.domain.onramp.repositories.OnrampErrorResolver
|
||||
import com.tangem.domain.onramp.repositories.OnrampRepository
|
||||
|
||||
class FetchOnrampCurrenciesUseCase(
|
||||
private val repository: OnrampRepository,
|
||||
private val errorResolver: OnrampErrorResolver,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(): Either<OnrampError, Unit> {
|
||||
return Either.catch { repository.fetchCurrencies() }.mapLeft(errorResolver::resolve)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,17 +1,21 @@
|
|||
package com.tangem.domain.onramp
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.domain.core.utils.EitherFlow
|
||||
import com.tangem.domain.onramp.model.OnrampCountry
|
||||
import com.tangem.domain.onramp.model.error.OnrampError
|
||||
import com.tangem.domain.onramp.repositories.OnrampErrorResolver
|
||||
import com.tangem.domain.onramp.repositories.OnrampRepository
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
class GetOnrampCountriesUseCase(
|
||||
private val onrampRepository: OnrampRepository,
|
||||
private val errorResolver: OnrampErrorResolver,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(): Either<OnrampError, List<OnrampCountry>> {
|
||||
return Either.catch { onrampRepository.getCountries() }.mapLeft(errorResolver::resolve)
|
||||
suspend operator fun invoke(): EitherFlow<OnrampError, List<OnrampCountry>> {
|
||||
return onrampRepository.getCountries().map {
|
||||
Either.catch { it }.mapLeft(errorResolver::resolve)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,23 +1,26 @@
|
|||
package com.tangem.domain.onramp
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.domain.core.utils.EitherFlow
|
||||
import com.tangem.domain.onramp.model.OnrampCurrencies
|
||||
import com.tangem.domain.onramp.model.error.OnrampError
|
||||
import com.tangem.domain.onramp.repositories.OnrampErrorResolver
|
||||
import com.tangem.domain.onramp.repositories.OnrampRepository
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
class GetOnrampCurrenciesUseCase(
|
||||
private val onrampRepository: OnrampRepository,
|
||||
private val errorResolver: OnrampErrorResolver,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(): Either<OnrampError, OnrampCurrencies> {
|
||||
return Either.catch {
|
||||
val currenciesList = onrampRepository.getCurrencies()
|
||||
val (populars, others) = currenciesList.toSet()
|
||||
.partition { popularFiatCodes.contains(it.code.uppercase()) }
|
||||
OnrampCurrencies(populars = populars, others = others)
|
||||
}.mapLeft(errorResolver::resolve)
|
||||
suspend operator fun invoke(): EitherFlow<OnrampError, OnrampCurrencies> {
|
||||
return onrampRepository.getCurrencies().map { currenciesList ->
|
||||
Either.catch {
|
||||
val (populars, others) = currenciesList.toSet()
|
||||
.partition { popularFiatCodes.contains(it.code.uppercase()) }
|
||||
OnrampCurrencies(populars = populars, others = others)
|
||||
}.mapLeft(errorResolver::resolve)
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
|
|
|
|||
|
|
@ -10,10 +10,13 @@ import kotlinx.coroutines.flow.Flow
|
|||
@Suppress("TooManyFunctions")
|
||||
interface OnrampRepository {
|
||||
// api
|
||||
suspend fun getCurrencies(): List<OnrampCurrency>
|
||||
suspend fun getCountries(): List<OnrampCountry>
|
||||
suspend fun getCurrencies(): Flow<List<OnrampCurrency>>
|
||||
suspend fun getCountries(): Flow<List<OnrampCountry>>
|
||||
suspend fun getCountriesSync(): List<OnrampCountry>?
|
||||
suspend fun getCountryByIp(): OnrampCountry
|
||||
suspend fun getStatus(txId: String): OnrampStatus
|
||||
suspend fun fetchCurrencies()
|
||||
suspend fun fetchCountries()
|
||||
suspend fun fetchPaymentMethodsIfAbsent()
|
||||
suspend fun fetchPairs(currency: OnrampCurrency, country: OnrampCountry, cryptoCurrency: CryptoCurrency)
|
||||
suspend fun fetchQuotes(cryptoCurrency: CryptoCurrency, amount: Amount)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
package com.tangem.features.managetokens.utils.list
|
||||
|
||||
import androidx.compose.ui.util.fastForEachIndexed
|
||||
import androidx.compose.ui.util.fastMap
|
||||
import arrow.core.getOrElse
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.decompose.di.ComponentScoped
|
||||
|
|
@ -165,7 +167,7 @@ internal class ManageTokensListManager @Inject constructor(
|
|||
}
|
||||
|
||||
state.update { state ->
|
||||
val newBatches = batchListState.data
|
||||
val newBatches = distinctCurrencies(batchListState.data)
|
||||
val currentBatches = state.currencyBatches
|
||||
|
||||
// Distinct until changed
|
||||
|
|
@ -186,6 +188,28 @@ internal class ManageTokensListManager @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
// FIXME: Add interception functionality to BatchFlow state and do this on domain
|
||||
// [REDACTED_JIRA]
|
||||
private fun distinctCurrencies(
|
||||
batches: List<Batch<Int, List<ManagedCryptoCurrency>>>,
|
||||
): List<Batch<Int, List<ManagedCryptoCurrency>>> {
|
||||
val allCurrenciesIds = mutableListOf<ManagedCryptoCurrency.ID>()
|
||||
|
||||
return batches.fastMap { batch ->
|
||||
val batchCurrencies = batch.data.toMutableList()
|
||||
|
||||
batch.data.fastForEachIndexed { index, currency ->
|
||||
if (currency.id in allCurrenciesIds) {
|
||||
batchCurrencies.removeAt(index)
|
||||
} else {
|
||||
allCurrenciesIds.add(currency.id)
|
||||
}
|
||||
}
|
||||
|
||||
batch.copy(data = batchCurrencies)
|
||||
}
|
||||
}
|
||||
|
||||
override fun addCurrency(batchKey: Int, currency: ManagedCryptoCurrency.Token, network: Network) {
|
||||
changedCurrenciesManager.addCurrency(currency, network)
|
||||
|
||||
|
|
|
|||
|
|
@ -8,4 +8,5 @@ interface OnrampIntents {
|
|||
fun openCurrenciesList()
|
||||
fun onBuyClick(quote: OnrampProviderWithQuote.Data)
|
||||
fun openProviders()
|
||||
fun onRefresh()
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.features.onramp.main.entity
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.tangem.common.ui.notifications.NotificationUM
|
||||
import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.combinedReference
|
||||
|
|
@ -13,11 +14,13 @@ internal sealed interface OnrampMainComponentUM {
|
|||
|
||||
val topBarConfig: OnrampMainTopBarUM
|
||||
val buyButtonConfig: BuyButtonConfig
|
||||
val errorNotification: NotificationUM?
|
||||
|
||||
data class InitialLoading(
|
||||
val currency: String,
|
||||
val onClose: () -> Unit,
|
||||
val openSettings: () -> Unit,
|
||||
override val errorNotification: NotificationUM? = null,
|
||||
) : OnrampMainComponentUM {
|
||||
override val topBarConfig: OnrampMainTopBarUM = OnrampMainTopBarUM(
|
||||
title = combinedReference(resourceReference(R.string.common_buy), stringReference(" $currency")),
|
||||
|
|
@ -43,6 +46,7 @@ internal sealed interface OnrampMainComponentUM {
|
|||
data class Content(
|
||||
override val topBarConfig: OnrampMainTopBarUM,
|
||||
override val buyButtonConfig: BuyButtonConfig,
|
||||
override val errorNotification: NotificationUM?,
|
||||
val amountBlockState: OnrampAmountBlockUM,
|
||||
val providerBlockState: OnrampProviderBlockUM,
|
||||
) : OnrampMainComponentUM
|
||||
|
|
|
|||
|
|
@ -5,12 +5,16 @@ import androidx.compose.foundation.text.KeyboardOptions
|
|||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import com.tangem.common.ui.amountScreen.models.AmountFieldModel
|
||||
import com.tangem.common.ui.notifications.NotificationUM
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.domain.onramp.model.OnrampCurrency
|
||||
import com.tangem.domain.onramp.model.error.OnrampError
|
||||
import com.tangem.domain.tokens.model.Amount
|
||||
import com.tangem.domain.tokens.model.AmountType
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.convertToAmount
|
||||
import com.tangem.features.onramp.impl.R
|
||||
import com.tangem.features.onramp.main.entity.*
|
||||
import com.tangem.utils.Provider
|
||||
import java.math.BigDecimal
|
||||
|
|
@ -38,9 +42,59 @@ internal class OnrampStateFactory(
|
|||
buyButtonConfig = state.buyButtonConfig,
|
||||
amountBlockState = getInitialAmountBlockState(currency),
|
||||
providerBlockState = OnrampProviderBlockUM.Empty,
|
||||
errorNotification = null,
|
||||
)
|
||||
}
|
||||
|
||||
fun getOnrampErrorState(onrampError: OnrampError): OnrampMainComponentUM {
|
||||
return when (onrampError) {
|
||||
OnrampError.PairsNotFound -> getNoPairsErrorState()
|
||||
is OnrampError.DataError,
|
||||
is OnrampError.DomainError,
|
||||
-> getErrorState()
|
||||
is OnrampError.AmountError.TooBigError,
|
||||
is OnrampError.AmountError.TooSmallError,
|
||||
OnrampError.RedirectError.VerificationFailed,
|
||||
OnrampError.RedirectError.WrongRequestId,
|
||||
-> currentStateProvider() // ignore error state
|
||||
}
|
||||
}
|
||||
|
||||
private fun getNoPairsErrorState(): OnrampMainComponentUM {
|
||||
val state = currentStateProvider()
|
||||
val contentState = state as? OnrampMainComponentUM.Content ?: return state
|
||||
|
||||
return contentState.copy(
|
||||
buyButtonConfig = contentState.buyButtonConfig.copy(enabled = false),
|
||||
amountBlockState = contentState.amountBlockState.copy(
|
||||
amountFieldModel = contentState.amountBlockState.amountFieldModel.copy(isError = true),
|
||||
secondaryFieldModel = OnrampAmountSecondaryFieldUM.Error(
|
||||
error = resourceReference(R.string.onramp_no_available_providers),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun getErrorState(): OnrampMainComponentUM {
|
||||
val state = currentStateProvider()
|
||||
val endButton = state.topBarConfig.endButtonUM.copy(enabled = true)
|
||||
|
||||
return when (state) {
|
||||
is OnrampMainComponentUM.Content -> state.copy(
|
||||
topBarConfig = state.topBarConfig.copy(endButtonUM = endButton),
|
||||
buyButtonConfig = state.buyButtonConfig.copy(enabled = false),
|
||||
amountBlockState = state.amountBlockState.copy(
|
||||
amountFieldModel = state.amountBlockState.amountFieldModel.copy(isError = true),
|
||||
),
|
||||
providerBlockState = OnrampProviderBlockUM.Empty,
|
||||
errorNotification = NotificationUM.Warning.OnrampErrorNotification(onrampIntents::onRefresh),
|
||||
)
|
||||
is OnrampMainComponentUM.InitialLoading -> state.copy(
|
||||
errorNotification = NotificationUM.Warning.OnrampErrorNotification(onrampIntents::onRefresh),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getInitialAmountBlockState(currency: OnrampCurrency): OnrampAmountBlockUM {
|
||||
return OnrampAmountBlockUM(
|
||||
currencyUM = OnrampCurrencyUM(
|
||||
|
|
|
|||
|
|
@ -116,10 +116,7 @@ internal class OnrampMainComponentModel @Inject constructor(
|
|||
modelScope.launch {
|
||||
checkOnrampAvailabilityUseCase()
|
||||
.onRight(::handleOnrampAvailability)
|
||||
.onLeft { error ->
|
||||
Timber.e(error.toString())
|
||||
sendOnrampErrorAnalytic(error)
|
||||
}
|
||||
.onLeft(::handleOnrampError)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -136,7 +133,7 @@ internal class OnrampMainComponentModel @Inject constructor(
|
|||
getOnrampCurrencyUseCase.invoke()
|
||||
.onEach { maybeCurrency ->
|
||||
maybeCurrency.fold(
|
||||
ifLeft = ::sendOnrampErrorAnalytic,
|
||||
ifLeft = ::handleOnrampError,
|
||||
ifRight = { currency ->
|
||||
if (currency == null) return@onEach
|
||||
_state.update { amountStateFactory.getUpdatedCurrencyState(currency) }
|
||||
|
|
@ -157,10 +154,16 @@ internal class OnrampMainComponentModel @Inject constructor(
|
|||
}
|
||||
|
||||
private suspend fun updatePairsAndQuotes() {
|
||||
fetchPairsUseCase.invoke(params.cryptoCurrency).onLeft(::sendOnrampErrorAnalytic)
|
||||
fetchPairsUseCase.invoke(params.cryptoCurrency).onLeft(::handleOnrampError)
|
||||
startLoadingQuotes()
|
||||
}
|
||||
|
||||
private fun handleOnrampError(onrampError: OnrampError) {
|
||||
Timber.e(onrampError.toString())
|
||||
sendOnrampErrorAnalytic(onrampError)
|
||||
_state.update { stateFactory.getOnrampErrorState(onrampError) }
|
||||
}
|
||||
|
||||
private fun startLoadingQuotes() {
|
||||
quotesTaskScheduler.cancelTask()
|
||||
quotesTaskScheduler.scheduleTask(scope = modelScope, task = loadQuotesTask())
|
||||
|
|
@ -176,7 +179,7 @@ internal class OnrampMainComponentModel @Inject constructor(
|
|||
fetchQuotesUseCase.invoke(
|
||||
amount = content.amountBlockState.amountFieldModel.fiatAmount,
|
||||
cryptoCurrency = params.cryptoCurrency,
|
||||
).onLeft(::sendOnrampErrorAnalytic)
|
||||
).onLeft(::handleOnrampError)
|
||||
}
|
||||
},
|
||||
onSuccess = {},
|
||||
|
|
@ -188,7 +191,7 @@ internal class OnrampMainComponentModel @Inject constructor(
|
|||
getOnrampQuotesUseCase.invoke()
|
||||
.onEach { maybeQuotes ->
|
||||
maybeQuotes.fold(
|
||||
ifLeft = ::sendOnrampErrorAnalytic,
|
||||
ifLeft = ::handleOnrampError,
|
||||
ifRight = { quotes ->
|
||||
quotes.filterIsInstance<OnrampQuote.Error>().forEach { errorState ->
|
||||
sendOnrampErrorAnalytic(errorState.error)
|
||||
|
|
@ -253,6 +256,20 @@ internal class OnrampMainComponentModel @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
override fun onRefresh() {
|
||||
_state.update {
|
||||
stateFactory.getInitialState(
|
||||
currency = params.cryptoCurrency.name,
|
||||
onClose = router::pop,
|
||||
)
|
||||
}
|
||||
quotesTaskScheduler.cancelTask()
|
||||
modelScope.launch {
|
||||
clearOnrampCacheUseCase.invoke()
|
||||
checkResidenceCountry()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
modelScope.launch { clearOnrampCacheUseCase.invoke() }
|
||||
quotesTaskScheduler.cancelTask()
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import androidx.compose.ui.focus.focusRequester
|
|||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextDirection
|
||||
import androidx.compose.ui.unit.dp
|
||||
import coil.compose.AsyncImage
|
||||
import com.tangem.common.ui.amountScreen.models.AmountFieldModel
|
||||
import com.tangem.core.ui.components.TextShimmer
|
||||
|
|
@ -30,7 +31,6 @@ import com.tangem.features.onramp.impl.R
|
|||
import com.tangem.features.onramp.main.entity.OnrampAmountBlockUM
|
||||
import com.tangem.features.onramp.main.entity.OnrampAmountSecondaryFieldUM
|
||||
import com.tangem.features.onramp.main.entity.OnrampCurrencyUM
|
||||
import kotlinx.coroutines.delay
|
||||
|
||||
@Composable
|
||||
internal fun OnrampAmountContent(state: OnrampAmountBlockUM, modifier: Modifier = Modifier) {
|
||||
|
|
@ -41,10 +41,7 @@ internal fun OnrampAmountContent(state: OnrampAmountBlockUM, modifier: Modifier
|
|||
.padding(vertical = TangemTheme.dimens.spacing28),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
OnrampCurrencyIcon(
|
||||
modifier = Modifier.padding(start = TangemTheme.dimens.spacing24),
|
||||
currencyUM = state.currencyUM,
|
||||
)
|
||||
OnrampCurrencyIcon(currencyUM = state.currencyUM)
|
||||
OnrampAmountField(amountField = state.amountFieldModel)
|
||||
OnrampAmountSecondary(state = state.secondaryFieldModel)
|
||||
}
|
||||
|
|
@ -85,7 +82,6 @@ private fun OnrampAmountField(amountField: AmountFieldModel) {
|
|||
)
|
||||
|
||||
LaunchedEffect(key1 = Unit) {
|
||||
delay(timeMillis = 200)
|
||||
requester.requestFocus()
|
||||
}
|
||||
}
|
||||
|
|
@ -126,7 +122,10 @@ private fun OnrampAmountSecondary(state: OnrampAmountSecondaryFieldUM) {
|
|||
@Composable
|
||||
private fun OnrampCurrencyIcon(currencyUM: OnrampCurrencyUM, modifier: Modifier = Modifier) {
|
||||
Row(
|
||||
modifier = modifier.clickable(onClick = currencyUM.onClick),
|
||||
modifier = modifier
|
||||
.clip(RoundedCornerShape(8.dp))
|
||||
.clickable(onClick = currencyUM.onClick)
|
||||
.padding(start = TangemTheme.dimens.spacing24),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8),
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import com.tangem.core.ui.components.CircleShimmer
|
|||
import com.tangem.core.ui.components.PrimaryButton
|
||||
import com.tangem.core.ui.components.RectangleShimmer
|
||||
import com.tangem.core.ui.components.appbar.TangemTopAppBar
|
||||
import com.tangem.core.ui.components.notifications.Notification
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.utils.WindowInsetsZero
|
||||
|
|
@ -43,16 +44,17 @@ internal fun OnrampMainComponentContent(state: OnrampMainComponentUM, modifier:
|
|||
.fillMaxWidth()
|
||||
.wrapContentHeight()
|
||||
when (state) {
|
||||
is OnrampMainComponentUM.InitialLoading -> InitialLoading(modifier = contentModifier)
|
||||
is OnrampMainComponentUM.InitialLoading -> InitialLoading(modifier = contentModifier, state = state)
|
||||
is OnrampMainComponentUM.Content -> Content(modifier = contentModifier, state = state)
|
||||
}
|
||||
},
|
||||
floatingActionButton = {
|
||||
BuyButton(
|
||||
PrimaryButton(
|
||||
modifier = Modifier
|
||||
.navigationBarsPadding()
|
||||
.padding(horizontal = TangemTheme.dimens.spacing16)
|
||||
.fillMaxWidth(),
|
||||
text = stringResource(id = R.string.common_buy),
|
||||
onClick = state.buyButtonConfig.onClick,
|
||||
enabled = state.buyButtonConfig.enabled,
|
||||
)
|
||||
|
|
@ -62,9 +64,21 @@ internal fun OnrampMainComponentContent(state: OnrampMainComponentUM, modifier:
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun InitialLoading(modifier: Modifier = Modifier) {
|
||||
private fun InitialLoading(state: OnrampMainComponentUM.InitialLoading, modifier: Modifier = Modifier) {
|
||||
Column(
|
||||
modifier = modifier,
|
||||
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
|
||||
) {
|
||||
OnrampAmountContentLoading()
|
||||
if (state.errorNotification != null) Notification(config = state.errorNotification.config)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun OnrampAmountContentLoading(modifier: Modifier = Modifier) {
|
||||
Column(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.clip(shape = RoundedCornerShape(size = TangemTheme.dimens.radius16))
|
||||
.background(TangemTheme.colors.background.action)
|
||||
.padding(vertical = TangemTheme.dimens.spacing28),
|
||||
|
|
@ -97,15 +111,6 @@ private fun Content(state: OnrampMainComponentUM.Content, modifier: Modifier = M
|
|||
) {
|
||||
OnrampAmountContent(state = state.amountBlockState)
|
||||
OnrampProviderContent(state = state.providerBlockState, modifier = Modifier.fillMaxWidth())
|
||||
if (state.errorNotification != null) Notification(config = state.errorNotification.config)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun BuyButton(onClick: () -> Unit, enabled: Boolean, modifier: Modifier = Modifier) {
|
||||
PrimaryButton(
|
||||
modifier = modifier,
|
||||
text = stringResource(id = R.string.common_buy),
|
||||
onClick = onClick,
|
||||
enabled = enabled,
|
||||
)
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ import com.tangem.core.decompose.model.Model
|
|||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.core.ui.components.fields.entity.SearchBarUM
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.domain.onramp.FetchOnrampCountriesUseCase
|
||||
import com.tangem.domain.onramp.GetOnrampCountriesUseCase
|
||||
import com.tangem.domain.onramp.GetOnrampCountryUseCase
|
||||
import com.tangem.domain.onramp.OnrampSaveDefaultCountryUseCase
|
||||
|
|
@ -39,6 +40,7 @@ internal class OnrampSelectCountryModel @Inject constructor(
|
|||
private val getOnrampCountriesUseCase: GetOnrampCountriesUseCase,
|
||||
private val saveDefaultCountryUseCase: OnrampSaveDefaultCountryUseCase,
|
||||
private val getOnrampCountryUseCase: GetOnrampCountryUseCase,
|
||||
private val fetchOnrampCountriesUseCase: FetchOnrampCountriesUseCase,
|
||||
paramsContainer: ParamsContainer,
|
||||
) : Model() {
|
||||
|
||||
|
|
@ -52,9 +54,16 @@ internal class OnrampSelectCountryModel @Inject constructor(
|
|||
|
||||
init {
|
||||
analyticsEventHandler.send(OnrampAnalyticsEvent.SelectResidenceOpened)
|
||||
updateCountriesList()
|
||||
modelScope.launch { subscribeOnUpdateState() }
|
||||
}
|
||||
|
||||
private fun updateCountriesList() {
|
||||
modelScope.launch {
|
||||
fetchOnrampCountriesUseCase()
|
||||
}
|
||||
}
|
||||
|
||||
fun dismiss() {
|
||||
params.onDismiss()
|
||||
}
|
||||
|
|
@ -62,7 +71,7 @@ internal class OnrampSelectCountryModel @Inject constructor(
|
|||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
private suspend fun subscribeOnUpdateState() {
|
||||
combine(
|
||||
flow = refreshTrigger.onStart { emit(Unit) }.flatMapLatest { flowOf(getOnrampCountriesUseCase()) },
|
||||
flow = refreshTrigger.onStart { emit(Unit) }.flatMapLatest { getOnrampCountriesUseCase() },
|
||||
flow2 = getOnrampCountryUseCase(),
|
||||
flow3 = searchManager.query,
|
||||
) { maybeCountries, maybeCountry, query ->
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import com.tangem.core.decompose.model.ParamsContainer
|
|||
import com.tangem.core.ui.components.fields.entity.SearchBarUM
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.domain.onramp.FetchOnrampCurrenciesUseCase
|
||||
import com.tangem.domain.onramp.GetOnrampCurrenciesUseCase
|
||||
import com.tangem.domain.onramp.OnrampSaveDefaultCurrencyUseCase
|
||||
import com.tangem.domain.onramp.analytics.OnrampAnalyticsEvent
|
||||
|
|
@ -31,6 +32,7 @@ import kotlinx.coroutines.flow.*
|
|||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@ComponentScoped
|
||||
internal class OnrampSelectCurrencyModel @Inject constructor(
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
|
|
@ -38,6 +40,7 @@ internal class OnrampSelectCurrencyModel @Inject constructor(
|
|||
private val searchManager: InputManager,
|
||||
private val getOnrampCurrenciesUseCase: GetOnrampCurrenciesUseCase,
|
||||
private val saveDefaultCurrencyUseCase: OnrampSaveDefaultCurrencyUseCase,
|
||||
private val fetchOnrampCurrenciesUseCase: FetchOnrampCurrenciesUseCase,
|
||||
paramsContainer: ParamsContainer,
|
||||
) : Model() {
|
||||
|
||||
|
|
@ -51,13 +54,20 @@ internal class OnrampSelectCurrencyModel @Inject constructor(
|
|||
private val refreshTrigger = MutableSharedFlow<Unit>()
|
||||
|
||||
init {
|
||||
updateCurrenciesList()
|
||||
subscribeOnUpdateState()
|
||||
}
|
||||
|
||||
private fun updateCurrenciesList() {
|
||||
modelScope.launch {
|
||||
fetchOnrampCurrenciesUseCase()
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
private fun subscribeOnUpdateState() {
|
||||
combine(
|
||||
flow = refreshTrigger.onStart { emit(Unit) }.flatMapLatest { flowOf(getOnrampCurrenciesUseCase.invoke()) },
|
||||
flow = refreshTrigger.onStart { emit(Unit) }.flatMapLatest { getOnrampCurrenciesUseCase() },
|
||||
flow2 = searchManager.query,
|
||||
) { maybeCurrencies, query ->
|
||||
maybeCurrencies.onLeft {
|
||||
|
|
|
|||
|
|
@ -28,5 +28,6 @@ internal fun AnalyticsEventHandler.sendOnrampErrorEvent(
|
|||
is OnrampError.AmountError.TooSmallError,
|
||||
OnrampError.RedirectError.VerificationFailed,
|
||||
OnrampError.RedirectError.WrongRequestId,
|
||||
OnrampError.PairsNotFound,
|
||||
-> { /* no-op */ }
|
||||
}
|
||||
|
|
@ -49,6 +49,12 @@ internal enum class Wallet2CobrandImage(
|
|||
batchIds = setOf("AF33"),
|
||||
),
|
||||
|
||||
BTC365(
|
||||
cards2ResId = R.drawable.ill_btc365_card2_120_106,
|
||||
cards3ResId = R.drawable.ill_btc365_card3_120_106,
|
||||
batchIds = setOf("AF97"),
|
||||
),
|
||||
|
||||
CoinMetrica(
|
||||
cards2ResId = R.drawable.ill_coin_metrica_card2_120_106,
|
||||
cards3ResId = R.drawable.ill_coin_metrica_card3_120_106,
|
||||
|
|
@ -115,12 +121,36 @@ internal enum class Wallet2CobrandImage(
|
|||
batchIds = setOf("AF73"),
|
||||
),
|
||||
|
||||
Kasper(
|
||||
cards2ResId = R.drawable.ill_kasper_card2_120_106,
|
||||
cards3ResId = R.drawable.ill_kasper_card3_120_106,
|
||||
batchIds = setOf("AF96"),
|
||||
),
|
||||
|
||||
Kaspy(
|
||||
cards2ResId = R.drawable.ill_kaspy_card2_120_106,
|
||||
cards3ResId = R.drawable.ill_kaspy_card3_120_106,
|
||||
batchIds = setOf("AF95"),
|
||||
),
|
||||
|
||||
KishuInu(
|
||||
cards2ResId = R.drawable.ill_kishu_inu_card2_120_106,
|
||||
cards3ResId = R.drawable.ill_kishu_inu_card3_120_106,
|
||||
batchIds = setOf("AF52"),
|
||||
),
|
||||
|
||||
Konan(
|
||||
cards2ResId = R.drawable.ill_konan_card2_120_106,
|
||||
cards3ResId = R.drawable.ill_konan_card3_120_106,
|
||||
batchIds = setOf("AF93"),
|
||||
),
|
||||
|
||||
Neiro(
|
||||
cards2ResId = R.drawable.ill_neiro_card2_120_106,
|
||||
cards3ResId = R.drawable.ill_neiro_card3_120_106,
|
||||
batchIds = setOf("AF98"),
|
||||
),
|
||||
|
||||
NewWorldElite(
|
||||
cards2ResId = R.drawable.ill_nwe_card2_120_106,
|
||||
cards3ResId = R.drawable.ill_nwe_card3_120_106,
|
||||
|
|
|
|||
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 45 KiB |
|
After Width: | Height: | Size: 57 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 37 KiB |