diff --git a/app/src/main/java/com/tangem/tap/ForegroundActivityObserver.kt b/app/src/main/java/com/tangem/tap/ForegroundActivityObserver.kt index fd93beee79..5e2d1c0731 100644 --- a/app/src/main/java/com/tangem/tap/ForegroundActivityObserver.kt +++ b/app/src/main/java/com/tangem/tap/ForegroundActivityObserver.kt @@ -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 diff --git a/app/src/main/java/com/tangem/tap/di/domain/OnrampDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/OnrampDomainModule.kt index 2c7ebb8702..c2b9cb9ac8 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/OnrampDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/OnrampDomainModule.kt @@ -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) + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt index 7ab791d0b4..e6166576ce 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt @@ -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 { diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt index 7c99d07e6e..9edfafa699 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt @@ -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) } diff --git a/common/ui/src/main/java/com/tangem/common/ui/notifications/NotificationUM.kt b/common/ui/src/main/java/com/tangem/common/ui/notifications/NotificationUM.kt index 4db6780142..e7354ee9b1 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/notifications/NotificationUM.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/notifications/NotificationUM.kt @@ -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( diff --git a/core/datasource/src/main/java/com/tangem/datasource/di/OnrampStoreModule.kt b/core/datasource/src/main/java/com/tangem/datasource/di/OnrampStoreModule.kt index def89f2cb1..e786b22f21 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/di/OnrampStoreModule.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/di/OnrampStoreModule.kt @@ -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()) + } } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/onramp/countries/DefaultOnrampCountriesStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/onramp/countries/DefaultOnrampCountriesStore.kt new file mode 100644 index 0000000000..8a532d8284 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/local/onramp/countries/DefaultOnrampCountriesStore.kt @@ -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>, +) : OnrampCountriesStore, StringKeyDataStoreDecorator>(dataStore) { + override fun provideStringKey(key: String): String = key +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/onramp/countries/OnrampCountriesStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/onramp/countries/OnrampCountriesStore.kt new file mode 100644 index 0000000000..cd6fad266e --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/local/onramp/countries/OnrampCountriesStore.kt @@ -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? + fun get(key: String): Flow> + suspend fun store(key: String, value: List) + suspend fun clear() +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/onramp/currencies/DefaultOnrampCurrenciesStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/onramp/currencies/DefaultOnrampCurrenciesStore.kt new file mode 100644 index 0000000000..d24195590d --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/local/onramp/currencies/DefaultOnrampCurrenciesStore.kt @@ -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>, +) : OnrampCurrenciesStore, StringKeyDataStoreDecorator>(dataStore) { + override fun provideStringKey(key: String): String = key +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/onramp/currencies/OnrampCurrenciesStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/onramp/currencies/OnrampCurrenciesStore.kt new file mode 100644 index 0000000000..38bea3c404 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/local/onramp/currencies/OnrampCurrenciesStore.kt @@ -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? + fun get(key: String): Flow> + suspend fun store(key: String, value: List) + suspend fun clear() +} \ No newline at end of file diff --git a/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampErrorResolver.kt b/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampErrorResolver.kt index 2f60ba2878..09bc96ca6f 100644 --- a/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampErrorResolver.kt +++ b/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampErrorResolver.kt @@ -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) } diff --git a/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampRepository.kt b/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampRepository.kt index 05d14b1d18..8b427f58f6 100644 --- a/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampRepository.kt +++ b/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampRepository.kt @@ -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 = withContext(dispatchers.io) { - onrampApi.getCurrencies() - .getOrThrow() - .map(currencyConverter::convert) + override suspend fun getCurrencies(): Flow> = withContext(dispatchers.io) { + currenciesStore.get(CURRENCIES_KEY) } - override suspend fun getCountries(): List = 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> = withContext(dispatchers.io) { + countriesStore.get(COUNTRIES_KEY) + } + + override suspend fun getCountriesSync(): List? { + 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, providers: List) { + 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" } diff --git a/data/onramp/src/main/java/com/tangem/data/onramp/di/OnrampDataModule.kt b/data/onramp/src/main/java/com/tangem/data/onramp/di/OnrampDataModule.kt index a61d427078..febd5c21c9 100644 --- a/data/onramp/src/main/java/com/tangem/data/onramp/di/OnrampDataModule.kt +++ b/data/onramp/src/main/java/com/tangem/data/onramp/di/OnrampDataModule.kt @@ -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, diff --git a/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/error/OnrampError.kt b/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/error/OnrampError.kt index c7b4a01e27..cf1f656e93 100644 --- a/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/error/OnrampError.kt +++ b/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/error/OnrampError.kt @@ -29,4 +29,6 @@ sealed class OnrampError { data class DomainError( val description: String?, ) : OnrampError() + + data object PairsNotFound : OnrampError() } \ No newline at end of file diff --git a/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/error/OnrampPairsError.kt b/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/error/OnrampPairsError.kt new file mode 100644 index 0000000000..3e390f40b3 --- /dev/null +++ b/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/error/OnrampPairsError.kt @@ -0,0 +1,5 @@ +package com.tangem.domain.onramp.model.error + +sealed class OnrampPairsError : Throwable() { + data object PairsNotFound : OnrampPairsError() +} \ No newline at end of file diff --git a/domain/onramp/src/main/java/com/tangem/domain/onramp/CheckOnrampAvailabilityUseCase.kt b/domain/onramp/src/main/java/com/tangem/domain/onramp/CheckOnrampAvailabilityUseCase.kt index 561f2cf5fd..52971ea345 100644 --- a/domain/onramp/src/main/java/com/tangem/domain/onramp/CheckOnrampAvailabilityUseCase.kt +++ b/domain/onramp/src/main/java/com/tangem/domain/onramp/CheckOnrampAvailabilityUseCase.kt @@ -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 { diff --git a/domain/onramp/src/main/java/com/tangem/domain/onramp/FetchOnrampCountriesUseCase.kt b/domain/onramp/src/main/java/com/tangem/domain/onramp/FetchOnrampCountriesUseCase.kt new file mode 100644 index 0000000000..8f320a1247 --- /dev/null +++ b/domain/onramp/src/main/java/com/tangem/domain/onramp/FetchOnrampCountriesUseCase.kt @@ -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 { + return Either.catch { repository.fetchCountries() }.mapLeft(errorResolver::resolve) + } +} \ No newline at end of file diff --git a/domain/onramp/src/main/java/com/tangem/domain/onramp/FetchOnrampCurrenciesUseCase.kt b/domain/onramp/src/main/java/com/tangem/domain/onramp/FetchOnrampCurrenciesUseCase.kt new file mode 100644 index 0000000000..d1a15ca673 --- /dev/null +++ b/domain/onramp/src/main/java/com/tangem/domain/onramp/FetchOnrampCurrenciesUseCase.kt @@ -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 { + return Either.catch { repository.fetchCurrencies() }.mapLeft(errorResolver::resolve) + } +} \ No newline at end of file diff --git a/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampCountriesUseCase.kt b/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampCountriesUseCase.kt index 928db29e59..1ede7cb75b 100644 --- a/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampCountriesUseCase.kt +++ b/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampCountriesUseCase.kt @@ -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> { - return Either.catch { onrampRepository.getCountries() }.mapLeft(errorResolver::resolve) + suspend operator fun invoke(): EitherFlow> { + return onrampRepository.getCountries().map { + Either.catch { it }.mapLeft(errorResolver::resolve) + } } } \ No newline at end of file diff --git a/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampCurrenciesUseCase.kt b/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampCurrenciesUseCase.kt index 29b1aee914..33f5ac2f22 100644 --- a/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampCurrenciesUseCase.kt +++ b/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampCurrenciesUseCase.kt @@ -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 { - 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 { + 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 { diff --git a/domain/onramp/src/main/java/com/tangem/domain/onramp/repositories/OnrampRepository.kt b/domain/onramp/src/main/java/com/tangem/domain/onramp/repositories/OnrampRepository.kt index 7ead04a301..c5d09a5da7 100644 --- a/domain/onramp/src/main/java/com/tangem/domain/onramp/repositories/OnrampRepository.kt +++ b/domain/onramp/src/main/java/com/tangem/domain/onramp/repositories/OnrampRepository.kt @@ -10,10 +10,13 @@ import kotlinx.coroutines.flow.Flow @Suppress("TooManyFunctions") interface OnrampRepository { // api - suspend fun getCurrencies(): List - suspend fun getCountries(): List + suspend fun getCurrencies(): Flow> + suspend fun getCountries(): Flow> + suspend fun getCountriesSync(): List? 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) diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/list/ManageTokensListManager.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/list/ManageTokensListManager.kt index 414fd1161a..d3a52095a7 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/list/ManageTokensListManager.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/list/ManageTokensListManager.kt @@ -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>>, + ): List>> { + val allCurrenciesIds = mutableListOf() + + 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) diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/OnrampIntents.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/OnrampIntents.kt index ef55baf857..96e54c7555 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/OnrampIntents.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/OnrampIntents.kt @@ -8,4 +8,5 @@ interface OnrampIntents { fun openCurrenciesList() fun onBuyClick(quote: OnrampProviderWithQuote.Data) fun openProviders() + fun onRefresh() } \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/OnrampMainComponentUM.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/OnrampMainComponentUM.kt index afd1b4e88e..4015d007a8 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/OnrampMainComponentUM.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/OnrampMainComponentUM.kt @@ -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 diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/factory/OnrampStateFactory.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/factory/OnrampStateFactory.kt index 7616976a84..6736beb593 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/factory/OnrampStateFactory.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/factory/OnrampStateFactory.kt @@ -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( diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/model/OnrampMainComponentModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/model/OnrampMainComponentModel.kt index 006544cb2a..6026f919fe 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/model/OnrampMainComponentModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/model/OnrampMainComponentModel.kt @@ -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().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() diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/ui/OnrampAmountContent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/ui/OnrampAmountContent.kt index 855297b410..608587c822 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/ui/OnrampAmountContent.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/ui/OnrampAmountContent.kt @@ -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), ) { diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/ui/OnrampMainComponentContent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/ui/OnrampMainComponentContent.kt index 15551bbb8d..ee9194f0a3 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/ui/OnrampMainComponentContent.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/ui/OnrampMainComponentContent.kt @@ -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, - ) } \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/model/OnrampSelectCountryModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/model/OnrampSelectCountryModel.kt index 3b401da0a1..60c6819e3b 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/model/OnrampSelectCountryModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcountry/model/OnrampSelectCountryModel.kt @@ -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 -> diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/model/OnrampSelectCurrencyModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/model/OnrampSelectCurrencyModel.kt index 46a3aea5de..3ff2d6fc80 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/model/OnrampSelectCurrencyModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/model/OnrampSelectCurrencyModel.kt @@ -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() 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 { diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/utils/OnrampErrorAnalyticsSender.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/utils/OnrampErrorAnalyticsSender.kt index 275cd02a25..5ca1b735b6 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/utils/OnrampErrorAnalyticsSender.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/utils/OnrampErrorAnalyticsSender.kt @@ -28,5 +28,6 @@ internal fun AnalyticsEventHandler.sendOnrampErrorEvent( is OnrampError.AmountError.TooSmallError, OnrampError.RedirectError.VerificationFailed, OnrampError.RedirectError.WrongRequestId, + OnrampError.PairsNotFound, -> { /* no-op */ } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/Wallet2CobrandImage.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/Wallet2CobrandImage.kt index 21890462f2..033f180725 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/Wallet2CobrandImage.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/Wallet2CobrandImage.kt @@ -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, diff --git a/features/wallet/impl/src/main/res/drawable/ill_btc365_card2_120_106.webp b/features/wallet/impl/src/main/res/drawable/ill_btc365_card2_120_106.webp new file mode 100644 index 0000000000..4557ff9cf2 Binary files /dev/null and b/features/wallet/impl/src/main/res/drawable/ill_btc365_card2_120_106.webp differ diff --git a/features/wallet/impl/src/main/res/drawable/ill_btc365_card3_120_106.webp b/features/wallet/impl/src/main/res/drawable/ill_btc365_card3_120_106.webp new file mode 100644 index 0000000000..309bf72239 Binary files /dev/null and b/features/wallet/impl/src/main/res/drawable/ill_btc365_card3_120_106.webp differ diff --git a/features/wallet/impl/src/main/res/drawable/ill_kasper_card2_120_106.webp b/features/wallet/impl/src/main/res/drawable/ill_kasper_card2_120_106.webp new file mode 100644 index 0000000000..a5239f05a5 Binary files /dev/null and b/features/wallet/impl/src/main/res/drawable/ill_kasper_card2_120_106.webp differ diff --git a/features/wallet/impl/src/main/res/drawable/ill_kasper_card3_120_106.webp b/features/wallet/impl/src/main/res/drawable/ill_kasper_card3_120_106.webp new file mode 100644 index 0000000000..8fa87a3990 Binary files /dev/null and b/features/wallet/impl/src/main/res/drawable/ill_kasper_card3_120_106.webp differ diff --git a/features/wallet/impl/src/main/res/drawable/ill_kaspy_card2_120_106.webp b/features/wallet/impl/src/main/res/drawable/ill_kaspy_card2_120_106.webp new file mode 100644 index 0000000000..e84b88d640 Binary files /dev/null and b/features/wallet/impl/src/main/res/drawable/ill_kaspy_card2_120_106.webp differ diff --git a/features/wallet/impl/src/main/res/drawable/ill_kaspy_card3_120_106.webp b/features/wallet/impl/src/main/res/drawable/ill_kaspy_card3_120_106.webp new file mode 100644 index 0000000000..d38d20e9a9 Binary files /dev/null and b/features/wallet/impl/src/main/res/drawable/ill_kaspy_card3_120_106.webp differ diff --git a/features/wallet/impl/src/main/res/drawable/ill_konan_card2_120_106.webp b/features/wallet/impl/src/main/res/drawable/ill_konan_card2_120_106.webp new file mode 100644 index 0000000000..129f2ede84 Binary files /dev/null and b/features/wallet/impl/src/main/res/drawable/ill_konan_card2_120_106.webp differ diff --git a/features/wallet/impl/src/main/res/drawable/ill_konan_card3_120_106.webp b/features/wallet/impl/src/main/res/drawable/ill_konan_card3_120_106.webp new file mode 100644 index 0000000000..884f56afb2 Binary files /dev/null and b/features/wallet/impl/src/main/res/drawable/ill_konan_card3_120_106.webp differ diff --git a/features/wallet/impl/src/main/res/drawable/ill_neiro_card2_120_106.webp b/features/wallet/impl/src/main/res/drawable/ill_neiro_card2_120_106.webp new file mode 100644 index 0000000000..f325549dff Binary files /dev/null and b/features/wallet/impl/src/main/res/drawable/ill_neiro_card2_120_106.webp differ diff --git a/features/wallet/impl/src/main/res/drawable/ill_neiro_card3_120_106.webp b/features/wallet/impl/src/main/res/drawable/ill_neiro_card3_120_106.webp new file mode 100644 index 0000000000..20b6dc59be Binary files /dev/null and b/features/wallet/impl/src/main/res/drawable/ill_neiro_card3_120_106.webp differ