diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/onramp/models/response/model/OnrampCurrencyDTO.kt b/core/datasource/src/main/java/com/tangem/datasource/api/onramp/models/response/model/OnrampCurrencyDTO.kt index 7dbb49bbdf..a2a62175ec 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/onramp/models/response/model/OnrampCurrencyDTO.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/onramp/models/response/model/OnrampCurrencyDTO.kt @@ -12,7 +12,7 @@ data class OnrampCurrencyDTO( val code: String, @Json(name = "image") - val image: String, + val image: String?, @Json(name = "precision") val precision: Int, 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 1ada8bde19..f3e0ead995 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 @@ -465,7 +465,7 @@ internal class DefaultOnrampRepository( ) = if (error is ApiResponseError.HttpException) { val onrampError = onrampErrorConverter.convert(value = error.errorBody.orEmpty()) if (onrampError is OnrampError.AmountError) { - OnrampQuote.Error( + OnrampQuote.AmountError( paymentMethod = paymentMethod, provider = provider, fromAmount = fromOnrampAmount, @@ -473,7 +473,10 @@ internal class DefaultOnrampRepository( ) } else { Timber.w(error, "Unable to fetch onramp quotes for ${provider.id}. $error") - null + OnrampQuote.Error( + paymentMethod = paymentMethod, + provider = provider, + ) } } else { Timber.w(error, "Unable to fetch onramp quotes for ${provider.id}. $error") diff --git a/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampCurrency.kt b/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampCurrency.kt index 20f225f582..8b5bdbf34b 100644 --- a/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampCurrency.kt +++ b/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampCurrency.kt @@ -6,7 +6,7 @@ import kotlinx.serialization.Serializable data class OnrampCurrency( val name: String, val code: String, - val image: String, + val image: String?, val precision: Int, val unit: String, ) \ No newline at end of file diff --git a/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampProviderWithQuote.kt b/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampProviderWithQuote.kt index f0d51802d6..2891d6072d 100644 --- a/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampProviderWithQuote.kt +++ b/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampProviderWithQuote.kt @@ -23,7 +23,7 @@ sealed interface OnrampProviderWithQuote { data class Error( override val provider: OnrampProvider, - val quoteError: OnrampQuote.Error, + val quoteError: OnrampQuote.AmountError, ) : Unavailable } } \ No newline at end of file diff --git a/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampQuote.kt b/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampQuote.kt index cc6a4586c1..033d3f3f87 100644 --- a/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampQuote.kt +++ b/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampQuote.kt @@ -16,10 +16,15 @@ sealed class OnrampQuote { val maxFromAmount: OnrampAmount, ) : OnrampQuote() - data class Error( + data class AmountError( override val paymentMethod: OnrampPaymentMethod, override val provider: OnrampProvider, val fromAmount: OnrampAmount, val error: OnrampError.AmountError, ) : OnrampQuote() + + data class Error( + override val paymentMethod: OnrampPaymentMethod, + override val provider: OnrampProvider, + ) : OnrampQuote() } \ No newline at end of file diff --git a/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampProviderWithQuoteUseCase.kt b/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampProviderWithQuoteUseCase.kt index 22238ab748..1d6c6700d6 100644 --- a/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampProviderWithQuoteUseCase.kt +++ b/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampProviderWithQuoteUseCase.kt @@ -31,8 +31,8 @@ class GetOnrampProviderWithQuoteUseCase( private fun List.quoteWithProvider( provider: OnrampProvider, selectedPaymentMethod: OnrampPaymentMethod, - ): OnrampProviderWithQuote { - val matchedQuote = firstOrNull { it.paymentMethod == selectedPaymentMethod } + ): OnrampProviderWithQuote? { + val matchedQuote = firstOrNull { it.paymentMethod.id == selectedPaymentMethod.id } return when (matchedQuote) { is OnrampQuote.Data -> OnrampProviderWithQuote.Data( provider = matchedQuote.provider, @@ -40,14 +40,23 @@ class GetOnrampProviderWithQuoteUseCase( toAmount = matchedQuote.toAmount, fromAmount = matchedQuote.fromAmount, ) - is OnrampQuote.Error -> Unavailable.Error( + is OnrampQuote.AmountError -> Unavailable.Error( provider = matchedQuote.provider, quoteError = matchedQuote, ) - null -> Unavailable.NotSupportedPaymentMethod( - provider = provider, - availablePaymentMethods = provider.paymentMethods, - ) + is OnrampQuote.Error -> null + null -> { + val availablePaymentMethods = getAvailablePaymentMethods(provider) + Unavailable.NotSupportedPaymentMethod( + provider = provider, + availablePaymentMethods = availablePaymentMethods, + ).takeIf { availablePaymentMethods.isNotEmpty() } + } } } + + private fun List.getAvailablePaymentMethods(provider: OnrampProvider) = provider.paymentMethods + .filter { pm -> + filter { it !is OnrampQuote.Error }.any { it.paymentMethod.id == pm.id } + } } \ No newline at end of file diff --git a/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampQuotesUseCase.kt b/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampQuotesUseCase.kt index 1da7243f67..24b27d4860 100644 --- a/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampQuotesUseCase.kt +++ b/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampQuotesUseCase.kt @@ -47,9 +47,9 @@ class GetOnrampQuotesUseCase( grouped.value.sortedByDescending { when (it) { is OnrampQuote.Data -> it.toAmount.value - + is OnrampQuote.Error -> null // negative difference to sort both when data and unavailable is present - is OnrampQuote.Error -> { + is OnrampQuote.AmountError -> { when (val error = it.error) { is OnrampError.AmountError.TooSmallError -> it.fromAmount.value - error.requiredAmount is OnrampError.AmountError.TooBigError -> error.requiredAmount - it.fromAmount.value diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/AmountBlockState.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/AmountBlockState.kt index d0cfbe8515..c56ecedb67 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/AmountBlockState.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/AmountBlockState.kt @@ -10,7 +10,12 @@ internal data class OnrampAmountBlockUM( val secondaryFieldModel: OnrampAmountSecondaryFieldUM, ) -internal data class OnrampCurrencyUM(val code: String, val iconUrl: String, val precision: Int, val onClick: () -> Unit) +internal data class OnrampCurrencyUM( + val code: String, + val iconUrl: String?, + val precision: Int, + val onClick: () -> Unit, +) @Immutable internal sealed interface OnrampAmountSecondaryFieldUM { diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/factory/amount/OnrampAmountStateFactory.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/factory/amount/OnrampAmountStateFactory.kt index 5d85cb0561..70d7f41345 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/factory/amount/OnrampAmountStateFactory.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/factory/amount/OnrampAmountStateFactory.kt @@ -75,7 +75,7 @@ internal class OnrampAmountStateFactory( return currentState.copy( amountBlockState = amountState.copy( - secondaryFieldModel = quote.toSecondaryFieldUiModel(amountState), + secondaryFieldModel = quote.toSecondaryFieldUiModel(amountState) ?: amountState.secondaryFieldModel, ), providerBlockState = quote.toProviderBlockState(isBestRate), buyButtonConfig = currentState.buyButtonConfig.copy( @@ -175,19 +175,20 @@ internal class OnrampAmountStateFactory( ) } - private fun OnrampQuote.toSecondaryFieldUiModel(amountState: OnrampAmountBlockUM): OnrampAmountSecondaryFieldUM { + private fun OnrampQuote.toSecondaryFieldUiModel(amountState: OnrampAmountBlockUM): OnrampAmountSecondaryFieldUM? { return when (this) { + is OnrampQuote.Error -> null is OnrampQuote.Data -> { val amount = toAmount.value.format { crypto(symbol = toAmount.symbol, decimals = toAmount.decimals) } OnrampAmountSecondaryFieldUM.Content(stringReference(amount)) } - is OnrampQuote.Error -> this.toSecondaryFieldUiModel(amountState) + is OnrampQuote.AmountError -> this.toSecondaryFieldUiModel(amountState) } } - private fun OnrampQuote.Error.toSecondaryFieldUiModel( + private fun OnrampQuote.AmountError.toSecondaryFieldUiModel( amountState: OnrampAmountBlockUM, ): OnrampAmountSecondaryFieldUM.Error { val amount = error.requiredAmount.format { 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 366e4ba313..a7955b8c84 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 @@ -196,6 +196,8 @@ internal class OnrampMainComponentModel @Inject constructor( private fun subscribeToQuotesUpdate() { getOnrampQuotesUseCase.invoke() + .distinctUntilChanged() + .conflate() .onEach { maybeQuotes -> maybeQuotes.fold( ifLeft = ::handleOnrampError, @@ -269,7 +271,7 @@ internal class OnrampMainComponentModel @Inject constructor( } private fun handleQuoteResult(quotes: List) { - quotes.filterIsInstance().forEach { errorState -> + quotes.filterIsInstance().forEach { errorState -> sendOnrampErrorAnalytic(errorState.error) } @@ -299,12 +301,12 @@ internal class OnrampMainComponentModel @Inject constructor( paymentMethod = quote.paymentMethod.name, ), ) - } - _state.update { - amountStateFactory.getAmountSecondaryUpdatedState( - quote = quote, - isBestRate = isBestProvider, - ) + _state.update { + amountStateFactory.getAmountSecondaryUpdatedState( + quote = quote, + isBestRate = isBestProvider, + ) + } } } diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/entity/SelectProviderResult.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/entity/SelectProviderResult.kt index 434138e8ab..3f1364105d 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/entity/SelectProviderResult.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/entity/SelectProviderResult.kt @@ -20,6 +20,6 @@ sealed class SelectProviderResult { data class ProviderWithError( override val paymentMethod: OnrampPaymentMethod, override val provider: OnrampProvider, - val quoteError: OnrampQuote.Error, + val quoteError: OnrampQuote.AmountError, ) : SelectProviderResult() } \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/entity/OnrampSuccessComponentUM.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/entity/OnrampSuccessComponentUM.kt index 71f80e2f05..cb5a0cd776 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/entity/OnrampSuccessComponentUM.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/entity/OnrampSuccessComponentUM.kt @@ -11,7 +11,7 @@ sealed class OnrampSuccessComponentUM { data class Content( val txId: String, val timestamp: Long, - val currencyImageUrl: String, + val currencyImageUrl: String?, val fromAmount: TextReference, val toAmount: TextReference, val statusBlock: ExpressStatusUM,