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 0f50feba84..0c20fd0993 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
@@ -179,20 +179,6 @@ internal object OnrampDomainModule {
)
}
- @Provides
- @Singleton
- fun provideGetOnrampV2QuotesUseCase(
- settingsRepository: SettingsRepository,
- onrampRepository: OnrampRepository,
- onrampErrorResolver: OnrampErrorResolver,
- ): GetOnrampV2QuotesUseCase {
- return GetOnrampV2QuotesUseCase(
- settingsRepository = settingsRepository,
- repository = onrampRepository,
- errorResolver = onrampErrorResolver,
- )
- }
-
@Provides
@Singleton
fun provideGetOnrampProviderWithQuoteUseCase(
diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml
index b9b098fef2..ea88b88529 100644
--- a/core/res/src/main/res/values/strings.xml
+++ b/core/res/src/main/res/values/strings.xml
@@ -918,7 +918,7 @@
- up to %d days
%s min
- Available from
+ Available from
You get
Service is provided by an external provider. \nTangem is not responsible.
You can close this screen and check the transaction status on the token details screen.
diff --git a/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampOffer.kt b/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampOffer.kt
index 56700b01f4..ff2085d060 100644
--- a/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampOffer.kt
+++ b/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampOffer.kt
@@ -15,7 +15,7 @@ data class OnrampOffer(
)
enum class OnrampOfferAdvantages {
- Default, BestRate, Fastest,
+ Default, BestRate, Fastest, GreatRate,
}
enum class OnrampOfferCategory {
diff --git a/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampPaymentMethod.kt b/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampPaymentMethod.kt
index 17c2e41ae0..7af75375b7 100644
--- a/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampPaymentMethod.kt
+++ b/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampPaymentMethod.kt
@@ -18,31 +18,73 @@ enum class PaymentMethodType(val id: String?) {
OTHER(id = null),
;
+ // TODO will be removed in next request [REDACTED_TASK_KEY]
@Suppress("MagicNumber")
fun getPriority(isGooglePayEnabled: Boolean): Int = if (isGooglePayEnabled) {
when (this) {
GOOGLE_PAY -> 0
CARD -> 1
- SEPA -> 2
- REVOLUT_PAY -> 3
+ REVOLUT_PAY -> 2
+ SEPA -> 3
OTHER -> 4
}
} else {
when (this) {
- CARD -> 0
- GOOGLE_PAY -> 1
+ CARD -> 2
+ REVOLUT_PAY -> 1
SEPA -> 2
- REVOLUT_PAY -> 3
+ OTHER -> 3
+ GOOGLE_PAY -> 4
+ }
+ }
+
+ /**
+ * Get priority regardless of real speed. By business logic.
+ */
+ @Suppress("MagicNumber")
+ fun getPriorityForMethod(isGooglePayEnabled: Boolean): Int = if (isGooglePayEnabled) {
+ when (this) {
+ GOOGLE_PAY -> 0
+ CARD -> 1
+ REVOLUT_PAY -> 2
+ SEPA -> 3
OTHER -> 4
}
+ } else {
+ when (this) {
+ CARD -> 2
+ REVOLUT_PAY -> 1
+ SEPA -> 2
+ OTHER -> 3
+ GOOGLE_PAY -> 4
+ }
+ }
+
+ @Suppress("MagicNumber")
+ fun getPriorityBySpeed(isGooglePayEnabled: Boolean): Int = if (isGooglePayEnabled) {
+ when (this) {
+ GOOGLE_PAY -> 0
+ REVOLUT_PAY -> 1
+ CARD -> 2
+ SEPA -> 3
+ OTHER -> 4
+ }
+ } else {
+ when (this) {
+ REVOLUT_PAY -> 0
+ CARD -> 1
+ SEPA -> 2
+ OTHER -> 3
+ GOOGLE_PAY -> 4
+ }
}
/**
* BE AWARE. HARDCODED. Returns the speed of transaction for payment method type.
*/
fun getProcessingSpeed(): PaymentSpeed = when (this) {
- REVOLUT_PAY,
GOOGLE_PAY,
+ REVOLUT_PAY,
-> PaymentSpeed.Instant
CARD -> PaymentSpeed.FewMin
SEPA -> PaymentSpeed.FewDays
diff --git a/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampOffersUseCase.kt b/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampOffersUseCase.kt
index 81ee5e82fd..d6eae3a74f 100644
--- a/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampOffersUseCase.kt
+++ b/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampOffersUseCase.kt
@@ -47,7 +47,12 @@ class GetOnrampOffersUseCase(
if (validQuotes.isEmpty()) return emptyList()
val isGooglePayAvailable = settingsRepository.isGooglePayAvailability()
- val bestRateQuote = validQuotes.maxWithOrNull(compareOffersByRateSpeedAndPriority(isGooglePayAvailable))
+ val bestRateQuote = validQuotes.maxWithOrNull(
+ compareOffersByRateSpeedAndPriority(
+ isGooglePayAvailable = isGooglePayAvailable,
+ isSepaPrioritized = true,
+ ),
+ )
val bestRate = bestRateQuote?.toAmount?.value
val offers = validQuotes.map { quote ->
@@ -71,8 +76,9 @@ class GetOnrampOffersUseCase(
val lastTransaction = transactions.maxByOrNull { it.timestamp } ?: return null
return offers.find { offer ->
- offer.quote.provider.id == lastTransaction.providerType &&
- offer.quote.paymentMethod.id == lastTransaction.paymentMethod
+ offer.quote.provider.info.name == lastTransaction.providerName &&
+ offer.quote.paymentMethod.name == lastTransaction.paymentMethod &&
+ lastTransaction.status.isTerminal
}
}
@@ -83,14 +89,14 @@ class GetOnrampOffersUseCase(
private fun findFastestOffer(offers: List, isGooglePayAvailable: Boolean): OnrampOffer? {
val instantOffers = offers.filter { it.quote.paymentMethod.type.isInstant() }
return if (instantOffers.isNotEmpty()) {
- instantOffers.maxWithOrNull(offerComparator(isGooglePayAvailable))
+ instantOffers.maxWithOrNull(fastestOfferComparator(isGooglePayAvailable))
} else {
val offersBySpeed = offers.groupBy { offer ->
offer.quote.paymentMethod.type.getProcessingSpeed().speed
}
val fastestSpeed = offersBySpeed.keys.minOrNull() ?: return null
val fastestOffers = offersBySpeed[fastestSpeed] ?: return null
- fastestOffers.maxWithOrNull(offerComparator(isGooglePayAvailable))
+ fastestOffers.maxWithOrNull(fastestOfferComparator(isGooglePayAvailable))
}
}
@@ -99,7 +105,10 @@ class GetOnrampOffersUseCase(
is OnrampQuote.Data -> {
when (val quote2 = offer2.quote) {
is OnrampQuote.Data -> {
- compareOffersByRateSpeedAndPriority(isGooglePayAvailable).compare(quote1, quote2)
+ compareOffersByRateSpeedAndPriority(
+ isGooglePayAvailable = isGooglePayAvailable,
+ isSepaPrioritized = true,
+ ).compare(quote1, quote2)
}
else -> 1
}
@@ -108,6 +117,30 @@ class GetOnrampOffersUseCase(
}
}
+ private fun fastestOfferComparator(isGooglePayAvailable: Boolean): Comparator =
+ Comparator { offer1, offer2 ->
+ when (val quote1 = offer1.quote) {
+ is OnrampQuote.Data -> {
+ when (val quote2 = offer2.quote) {
+ is OnrampQuote.Data -> {
+ // For fastest offer, first compare by priority of speed
+ val priorityComparison = quote2.paymentMethod.type.getPriorityBySpeed(isGooglePayAvailable)
+ .compareTo(quote1.paymentMethod.type.getPriorityBySpeed(isGooglePayAvailable))
+
+ // If priorities are equal, compare by rate
+ if (priorityComparison != 0) {
+ priorityComparison
+ } else {
+ quote1.toAmount.value.compareTo(quote2.toAmount.value)
+ }
+ }
+ else -> 1
+ }
+ }
+ else -> -1
+ }
+ }
+
private fun buildOffersBlocks(
recentOffer: OnrampOffer?,
bestRateOffer: OnrampOffer?,
@@ -143,7 +176,7 @@ class GetOnrampOffersUseCase(
)
}
- if (recommendedOffers.isNotEmpty() && hasOnlyOneMethodAndProvider(allOffers).not()) {
+ if (recommendedOffers.isNotEmpty()) {
add(
OnrampOffersBlock(
category = OnrampOfferCategory.Recommended,
@@ -161,10 +194,10 @@ class GetOnrampOffersUseCase(
fastestOffer: OnrampOffer?,
): OnrampOfferAdvantages {
if (isSameOffer(recentOffer, bestRateOffer) && isSameOffer(recentOffer, fastestOffer)) {
- return OnrampOfferAdvantages.BestRate
+ return OnrampOfferAdvantages.GreatRate
}
if (isSameOffer(recentOffer, bestRateOffer)) {
- return OnrampOfferAdvantages.BestRate
+ return OnrampOfferAdvantages.GreatRate
}
if (isSameOffer(recentOffer, fastestOffer)) {
return OnrampOfferAdvantages.Fastest
@@ -182,7 +215,7 @@ class GetOnrampOffersUseCase(
bestRateOffer?.let { offer ->
add(
offer.copy(
- advantages = OnrampOfferAdvantages.BestRate,
+ advantages = OnrampOfferAdvantages.GreatRate,
rateDif = null,
),
)
@@ -191,7 +224,7 @@ class GetOnrampOffersUseCase(
if (bestRateOffer != null && !isSameOffer(bestRateOffer, recentOffer)) {
add(
bestRateOffer.copy(
- advantages = OnrampOfferAdvantages.BestRate,
+ advantages = OnrampOfferAdvantages.GreatRate,
rateDif = null,
),
)
@@ -211,12 +244,6 @@ class GetOnrampOffersUseCase(
}
}
- private fun hasOnlyOneMethodAndProvider(offers: List): Boolean {
- val uniquePaymentMethods = offers.map { it.quote.paymentMethod.id }.distinct()
- val uniqueProviders = offers.map { it.quote.provider.id }.distinct()
- return uniquePaymentMethods.size == 1 && uniqueProviders.size == 1
- }
-
private fun isSameOffer(offer1: OnrampOffer?, offer2: OnrampOffer?): Boolean {
if (offer1 == null || offer2 == null) return false
return offer1.quote.provider.id == offer2.quote.provider.id &&
diff --git a/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampPaymentMethodsUseCase.kt b/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampPaymentMethodsUseCase.kt
index f4528c9e4b..6a8c990b1f 100644
--- a/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampPaymentMethodsUseCase.kt
+++ b/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampPaymentMethodsUseCase.kt
@@ -19,7 +19,7 @@ class GetOnrampPaymentMethodsUseCase(
repository.getAvailablePaymentMethods()
.toList()
- .sortedBy { it.type.getPriority(isGooglePayAvailable) }
+ .sortedBy { it.type.getPriorityBySpeed(isGooglePayAvailable) }
.toSet()
}.mapLeft(errorResolver::resolve)
}
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 24b27d4860..657c529152 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
@@ -26,7 +26,7 @@ class GetOnrampQuotesUseCase(
quotes.groupBy { it.paymentMethod.type }
.asSequence()
- .sortedBy { it.key.getPriority(isGooglePayAvailable) }
+ .sortedBy { it.key.getPriorityBySpeed(isGooglePayAvailable) }
.sortByRate()
.toList()
.flatten()
@@ -53,7 +53,6 @@ class GetOnrampQuotesUseCase(
when (val error = it.error) {
is OnrampError.AmountError.TooSmallError -> it.fromAmount.value - error.requiredAmount
is OnrampError.AmountError.TooBigError -> error.requiredAmount - it.fromAmount.value
- else -> null
}
}
}
diff --git a/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampV2QuotesUseCase.kt b/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampV2QuotesUseCase.kt
deleted file mode 100644
index 5039240734..0000000000
--- a/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampV2QuotesUseCase.kt
+++ /dev/null
@@ -1,73 +0,0 @@
-package com.tangem.domain.onramp
-
-import arrow.core.Either
-import arrow.core.left
-import arrow.core.right
-import com.tangem.domain.onramp.model.OnrampQuote
-import com.tangem.domain.onramp.model.error.OnrampError
-import com.tangem.domain.onramp.repositories.OnrampErrorResolver
-import com.tangem.domain.onramp.repositories.OnrampRepository
-import com.tangem.domain.settings.repositories.SettingsRepository
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.catch
-import kotlinx.coroutines.flow.map
-import java.math.BigDecimal
-import java.util.Comparator
-
-class GetOnrampV2QuotesUseCase(
- private val settingsRepository: SettingsRepository,
- private val repository: OnrampRepository,
- private val errorResolver: OnrampErrorResolver,
-) {
-
- operator fun invoke(): Flow>> {
- return repository.getQuotes()
- .map, Either>> { quotes ->
- val isGooglePayAvailable = settingsRepository.isGooglePayAvailability()
- quotes.sortedWith(
- Comparator.comparing { quote ->
- getQuoteSortPriority(quote)
- }
- .thenComparingInt { quote ->
- quote.paymentMethod.type.getPriority(isGooglePayAvailable)
- },
- ).right()
- }
- .catch {
- emit(errorResolver.resolve(it).left())
- }
- }
-
- private class SortableBigDecimalWrapper(
- val value: BigDecimal?,
- val negateForSort: Boolean = false,
- ) : Comparable {
- override fun compareTo(other: SortableBigDecimalWrapper): Int {
- return when {
- value == null && other.value == null -> 0
- value == null -> 1
- other.value == null -> -1
- else -> {
- val thisValue = if (negateForSort) value.negate() else value
- val otherValue = if (other.negateForSort) other.value.negate() else other.value
- thisValue.compareTo(otherValue)
- }
- }
- }
- }
-
- private fun getQuoteSortPriority(quote: OnrampQuote): SortableBigDecimalWrapper {
- return when (quote) {
- is OnrampQuote.Data -> SortableBigDecimalWrapper(quote.toAmount.value, negateForSort = true)
- is OnrampQuote.Error -> SortableBigDecimalWrapper(null)
- is OnrampQuote.AmountError -> {
- when (val error = quote.error) {
- is OnrampError.AmountError.TooSmallError ->
- SortableBigDecimalWrapper((quote.fromAmount.value - error.requiredAmount).abs())
- is OnrampError.AmountError.TooBigError ->
- SortableBigDecimalWrapper((error.requiredAmount - quote.fromAmount.value).abs())
- }
- }
- }
- }
-}
\ No newline at end of file
diff --git a/domain/onramp/src/main/java/com/tangem/domain/onramp/utils/OnrampOfferUtils.kt b/domain/onramp/src/main/java/com/tangem/domain/onramp/utils/OnrampOfferUtils.kt
index 7329a88a84..8d8bb89a05 100644
--- a/domain/onramp/src/main/java/com/tangem/domain/onramp/utils/OnrampOfferUtils.kt
+++ b/domain/onramp/src/main/java/com/tangem/domain/onramp/utils/OnrampOfferUtils.kt
@@ -1,34 +1,42 @@
package com.tangem.domain.onramp.utils
import com.tangem.domain.onramp.model.OnrampQuote
+import com.tangem.domain.onramp.model.PaymentMethodType
import java.math.BigDecimal
internal fun calculateRateDif(currentTokenRate: BigDecimal, bestRate: BigDecimal?): BigDecimal? {
if (bestRate == null) return null
- return BigDecimal.ONE - currentTokenRate / bestRate
+ if (currentTokenRate > bestRate) return null
+
+ val rateDif = BigDecimal.ONE - currentTokenRate / bestRate
+ return if (rateDif >= BigDecimal("0.01")) rateDif else null
}
-internal fun compareOffersByRateSpeedAndPriority(isGooglePayAvailable: Boolean): Comparator {
+/**
+ * @param isSepaPrioritized Sepa provider should be prioritized over all offers no matter what.
+ */
+internal fun compareOffersByRateSpeedAndPriority(
+ isGooglePayAvailable: Boolean,
+ isSepaPrioritized: Boolean = false,
+): Comparator {
return Comparator { quote1, quote2 ->
- val rateComparison = quote1
- .toAmount
- .value
- .compareTo(quote2.toAmount.value)
+ if (isSepaPrioritized) {
+ val isQuote1Sepa = quote1.paymentMethod.type == PaymentMethodType.SEPA
+ val isQuote2Sepa = quote2.paymentMethod.type == PaymentMethodType.SEPA
+
+ if (isQuote1Sepa && !isQuote2Sepa) return@Comparator 1
+ if (!isQuote1Sepa && isQuote2Sepa) return@Comparator -1
+ }
+
+ val rateComparison = quote1.toAmount.value.compareTo(quote2.toAmount.value)
if (rateComparison != 0) return@Comparator rateComparison
val speedComparison =
- quote2
- .paymentMethod
- .type
- .getProcessingSpeed()
- .speed
+ quote2.paymentMethod.type.getProcessingSpeed().speed
.compareTo(quote1.paymentMethod.type.getProcessingSpeed().speed)
if (speedComparison != 0) return@Comparator speedComparison
- quote1
- .paymentMethod
- .type
- .getPriority(isGooglePayAvailable)
- .compareTo(quote2.paymentMethod.type.getPriority(isGooglePayAvailable))
+ quote2.paymentMethod.type.getPriorityBySpeed(isGooglePayAvailable)
+ .compareTo(quote1.paymentMethod.type.getPriorityBySpeed(isGooglePayAvailable))
}
}
\ No newline at end of file
diff --git a/domain/onramp/src/test/kotlin/com/tangem/domain/onramp/GetOnrampOffersUseCaseTest.kt b/domain/onramp/src/test/kotlin/com/tangem/domain/onramp/GetOnrampOffersUseCaseTest.kt
index 87f9fbb1c6..663d05542a 100644
--- a/domain/onramp/src/test/kotlin/com/tangem/domain/onramp/GetOnrampOffersUseCaseTest.kt
+++ b/domain/onramp/src/test/kotlin/com/tangem/domain/onramp/GetOnrampOffersUseCaseTest.kt
@@ -67,8 +67,8 @@ class GetOnrampOffersUseCaseTest {
@Test
fun `invoke should return offers blocks with recent and recommended categories`() = runTest {
- val paymentMethod1 = createMockPaymentMethod("card", "Card", isInstant = true)
- val paymentMethod2 = createMockPaymentMethod("bank", "Bank Transfer", isInstant = false)
+ val paymentMethod1 = createMockPaymentMethod("card", "Card", PaymentMethodType.GOOGLE_PAY)
+ val paymentMethod2 = createMockPaymentMethod("bank", "Bank Transfer", PaymentMethodType.CARD)
val provider1 = createMockProvider("provider1", "Provider 1")
val provider2 = createMockProvider("provider2", "Provider 2")
@@ -78,7 +78,7 @@ class GetOnrampOffersUseCaseTest {
)
val transactions = listOf(
- createMockTransaction("provider1", "card", 1000L),
+ createMockTransaction("Provider 1", "Card", 1000L),
)
coEvery { settingsRepository.isGooglePayAvailability() } returns false
@@ -105,7 +105,7 @@ class GetOnrampOffersUseCaseTest {
Truth.assertThat(recommendedBlock).isNotNull()
Truth.assertThat(recommendedBlock?.offers).hasSize(1)
Truth.assertThat(recommendedBlock?.offers?.first()?.advantages)
- .isEqualTo(OnrampOfferAdvantages.BestRate)
+ .isEqualTo(OnrampOfferAdvantages.GreatRate)
},
)
}
@@ -113,8 +113,8 @@ class GetOnrampOffersUseCaseTest {
@Test
fun `invoke should find best rate offer correctly`() = runTest {
- val paymentMethod1 = createMockPaymentMethod("card", "Card", isInstant = false)
- val paymentMethod2 = createMockPaymentMethod("bank", "Bank Transfer", isInstant = false)
+ val paymentMethod1 = createMockPaymentMethod("card", "Card", PaymentMethodType.CARD)
+ val paymentMethod2 = createMockPaymentMethod("bank", "Bank Transfer", PaymentMethodType.CARD)
val provider1 = createMockProvider("provider1", "Provider 1")
val provider2 = createMockProvider("provider2", "Provider 2")
@@ -145,10 +145,10 @@ class GetOnrampOffersUseCaseTest {
Truth.assertThat(recommendedBlock).isNotNull()
Truth.assertThat(recommendedBlock?.offers).hasSize(1)
- val bestRateOffer = recommendedBlock?.offers?.first()
- Truth.assertThat(bestRateOffer?.advantages).isEqualTo(OnrampOfferAdvantages.BestRate)
+ val grateRateOffer = recommendedBlock?.offers?.first()
+ Truth.assertThat(grateRateOffer?.advantages).isEqualTo(OnrampOfferAdvantages.GreatRate)
- when (val quote = bestRateOffer?.quote) {
+ when (val quote = grateRateOffer?.quote) {
is OnrampQuote.Data -> Truth.assertThat(quote.toAmount.value).isEqualTo(BigDecimal("100.0"))
else -> Truth.assertThat(false).isTrue()
}
@@ -159,8 +159,10 @@ class GetOnrampOffersUseCaseTest {
@Test
fun `invoke should find fastest offer correctly`() = runTest {
- val instantPaymentMethod = createMockPaymentMethod("card", "Card", isInstant = true)
- val slowPaymentMethod = createMockPaymentMethod("bank", "Bank Transfer", isInstant = false)
+ val instantPaymentMethod =
+ createMockPaymentMethod("card", "Card", PaymentMethodType.GOOGLE_PAY)
+ val slowPaymentMethod =
+ createMockPaymentMethod("bank", "Bank Transfer", PaymentMethodType.CARD)
val provider1 = createMockProvider("provider1", "Provider 1")
val provider2 = createMockProvider("provider2", "Provider 2")
@@ -190,17 +192,17 @@ class GetOnrampOffersUseCaseTest {
Truth.assertThat(recommendedBlock).isNotNull()
Truth.assertThat(recommendedBlock?.offers).hasSize(2)
- val bestRateOffer = recommendedBlock
+ val grateRateOffer = recommendedBlock
?.offers
- ?.find { it.advantages == OnrampOfferAdvantages.BestRate }
+ ?.find { it.advantages == OnrampOfferAdvantages.GreatRate }
val fastestOffer = recommendedBlock
?.offers
?.find { it.advantages == OnrampOfferAdvantages.Fastest }
- Truth.assertThat(bestRateOffer).isNotNull()
+ Truth.assertThat(grateRateOffer).isNotNull()
Truth.assertThat(fastestOffer).isNotNull()
- when (val quote = bestRateOffer?.quote) {
+ when (val quote = grateRateOffer?.quote) {
is OnrampQuote.Data -> Truth.assertThat(quote.toAmount.value).isEqualTo(BigDecimal("100.0"))
else -> Truth.assertThat(false).isTrue()
}
@@ -215,8 +217,8 @@ class GetOnrampOffersUseCaseTest {
}
@Test
- fun `invoke should not show recommended block when only one method and provider`() = runTest {
- val paymentMethod = createMockPaymentMethod("card", "Card", isInstant = false)
+ fun `invoke should show recommended block when only one method and provider`() = runTest {
+ val paymentMethod = createMockPaymentMethod("card", "Card", PaymentMethodType.CARD)
val provider = createMockProvider("provider1", "Provider 1")
val quotes = listOf(
@@ -238,22 +240,22 @@ class GetOnrampOffersUseCaseTest {
either.fold(
ifLeft = { error -> Truth.assertThat(error).isNull() },
ifRight = { offers ->
- Truth.assertThat(offers).isEmpty()
+ Truth.assertThat(offers).isNotEmpty()
+ Truth.assertThat(offers).hasSize(1)
},
)
}
}
- private fun createMockPaymentMethod(id: String, name: String, isInstant: Boolean): OnrampPaymentMethod {
+ private fun createMockPaymentMethod(
+ id: String,
+ name: String,
+ type: PaymentMethodType = PaymentMethodType.CARD,
+ ): OnrampPaymentMethod {
return mockk {
every { this@mockk.id } returns id
every { this@mockk.name } returns name
- every { this@mockk.type } returns mockk {
- every { isInstant() } returns isInstant
- every { getProcessingSpeed() } returns mockk {
- every { speed } returns if (isInstant) 1 else 3
- }
- }
+ every { this@mockk.type } returns type
}
}
@@ -278,11 +280,12 @@ class GetOnrampOffersUseCaseTest {
}
}
- private fun createMockTransaction(providerType: String, paymentMethod: String, timestamp: Long): OnrampTransaction {
+ private fun createMockTransaction(providerName: String, paymentMethod: String, timestamp: Long): OnrampTransaction {
return mockk {
- every { this@mockk.providerType } returns providerType
+ every { this@mockk.providerName } returns providerName
every { this@mockk.paymentMethod } returns paymentMethod
every { this@mockk.timestamp } returns timestamp
+ every { this@mockk.status.isTerminal } returns true
}
}
}
\ No newline at end of file
diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/alloffers/entity/AllOffersStateFactory.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/alloffers/entity/AllOffersStateFactory.kt
index 351cd1a5aa..ac5a54cfbd 100644
--- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/alloffers/entity/AllOffersStateFactory.kt
+++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/alloffers/entity/AllOffersStateFactory.kt
@@ -108,6 +108,7 @@ internal class AllOffersStateFactory(
OnrampOfferAdvantages.Default -> OnrampOfferAdvantagesUM.Default
OnrampOfferAdvantages.BestRate -> OnrampOfferAdvantagesUM.BestRate
OnrampOfferAdvantages.Fastest -> OnrampOfferAdvantagesUM.Fastest
+ OnrampOfferAdvantages.GreatRate -> OnrampOfferAdvantagesUM.Default
}
}
@@ -121,7 +122,6 @@ internal class AllOffersStateFactory(
category = OnrampOfferCategoryUM.Recommended,
advantages = mapOfferAdvantagesDTOtoUM(offer.advantages),
paymentMethod = quote.paymentMethod,
- providerId = quote.provider.id,
providerName = quote.provider.info.name,
rate = quote.toAmount.value.format {
crypto(
diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/alloffers/ui/AllOffersContentSheet.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/alloffers/ui/AllOffersContentSheet.kt
index f60660546a..5f051f3341 100644
--- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/alloffers/ui/AllOffersContentSheet.kt
+++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/alloffers/ui/AllOffersContentSheet.kt
@@ -204,7 +204,6 @@ private fun AllOffersContentSheetPaymentPreview() {
imageUrl = "https://s3.eu-central-1.amazonaws.com/tangem.api/express/PaymentMethods/visa-mc.png",
type = PaymentMethodType.CARD,
),
- providerId = "providerId1",
providerName = "Simplex",
rate = "0,0245334 BTC",
diff = null,
@@ -219,7 +218,6 @@ private fun AllOffersContentSheetPaymentPreview() {
imageUrl = "https://s3.eu-central-1.amazonaws.com/tangem.api/express/PaymentMethods/visa-mc.png",
type = PaymentMethodType.CARD,
),
- providerId = "providerId2",
providerName = "Simplex",
rate = "0,00145334 BTC",
diff = stringReference("–0.07%"),
@@ -270,7 +268,6 @@ private fun AllOffersContentSheetOffersPreview() {
"https://s3.eu-central-1.amazonaws.com/tangem.api/express/PaymentMethods/visa-mc.png",
type = PaymentMethodType.CARD,
),
- providerId = "providerId1",
providerName = "Simplex",
rate = "0,0245334 BTC",
diff = null,
@@ -286,7 +283,6 @@ private fun AllOffersContentSheetOffersPreview() {
"https://s3.eu-central-1.amazonaws.com/tangem.api/express/PaymentMethods/visa-mc.png",
type = PaymentMethodType.CARD,
),
- providerId = "providerId2",
providerName = "Simplex",
rate = "0,00145334 BTC",
diff = stringReference("–0.07%"),
diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/alloffers/ui/PaymentMethodsContent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/alloffers/ui/PaymentMethodsContent.kt
index 7c16f78f2e..0547dcbc0e 100644
--- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/alloffers/ui/PaymentMethodsContent.kt
+++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/alloffers/ui/PaymentMethodsContent.kt
@@ -234,7 +234,6 @@ private fun PaymentMethodsContentPreview() {
imageUrl = "https://s3.eu-central-1.amazonaws.com/tangem.api/express/PaymentMethods/visa-mc.png",
type = PaymentMethodType.CARD,
),
- providerId = "providerId1",
providerName = "Simplex",
rate = "0,0245334 BTC",
diff = null,
@@ -249,7 +248,6 @@ private fun PaymentMethodsContentPreview() {
imageUrl = "https://s3.eu-central-1.amazonaws.com/tangem.api/express/PaymentMethods/visa-mc.png",
type = PaymentMethodType.CARD,
),
- providerId = "providerId2",
providerName = "Simplex",
rate = "0,00145334 BTC",
diff = stringReference("–0.07%"),
diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/mainv2/entity/OnrampOfferBlockUM.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/mainv2/entity/OnrampOfferBlockUM.kt
index 54ea1f1451..6603b00623 100644
--- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/mainv2/entity/OnrampOfferBlockUM.kt
+++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/mainv2/entity/OnrampOfferBlockUM.kt
@@ -9,19 +9,11 @@ import kotlinx.collections.immutable.ImmutableList
@Immutable
internal sealed interface OnrampOffersBlockUM {
- val isBlockVisible: Boolean
+ data object Empty : OnrampOffersBlockUM
- data object Empty : OnrampOffersBlockUM {
- override val isBlockVisible: Boolean
- get() = false
- }
-
- data class Loading(
- override val isBlockVisible: Boolean,
- ) : OnrampOffersBlockUM
+ data object Loading : OnrampOffersBlockUM
data class Content(
- override val isBlockVisible: Boolean,
val recentOffer: OnrampOfferUM?,
val recommended: ImmutableList,
val onrampAllOffersButtonConfig: OnrampAllOffersButtonConfig?,
@@ -32,7 +24,6 @@ internal data class OnrampOfferUM(
val category: OnrampOfferCategoryUM,
val advantages: OnrampOfferAdvantagesUM,
val paymentMethod: OnrampPaymentMethod,
- val providerId: String,
val providerName: String,
val rate: String,
val diff: TextReference?,
@@ -44,7 +35,7 @@ internal enum class OnrampOfferCategoryUM {
}
internal enum class OnrampOfferAdvantagesUM {
- Default, BestRate, Fastest;
+ Default, BestRate, GreatRate, Fastest, Unavailable;
fun toAnalyticsEvent(
cryptoCurrencySymbol: String,
@@ -52,7 +43,7 @@ internal enum class OnrampOfferAdvantagesUM {
paymentMethodName: String,
): OnrampAnalyticsEvent? {
return when (this) {
- BestRate -> OnrampAnalyticsEvent.BestRateClicked(
+ GreatRate -> OnrampAnalyticsEvent.BestRateClicked(
tokenSymbol = cryptoCurrencySymbol,
providerName = providerName,
paymentMethod = paymentMethodName,
@@ -62,7 +53,10 @@ internal enum class OnrampOfferAdvantagesUM {
providerName = providerName,
paymentMethod = paymentMethodName,
)
- Default -> null
+ Default,
+ BestRate,
+ Unavailable,
+ -> null
}
}
}
diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/mainv2/entity/OnrampV2AmountBlockUM.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/mainv2/entity/OnrampV2AmountBlockUM.kt
index b151b7818c..9ae8528504 100644
--- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/mainv2/entity/OnrampV2AmountBlockUM.kt
+++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/mainv2/entity/OnrampV2AmountBlockUM.kt
@@ -8,7 +8,7 @@ import kotlinx.collections.immutable.ImmutableList
internal data class OnrampNewAmountBlockUM(
val currencyUM: OnrampNewCurrencyUM,
val amountFieldModel: AmountFieldModel,
- val secondaryFieldModel: OnrampNewAmountSecondaryFieldUM,
+ val secondaryFieldModel: OnrampSecondaryFieldErrorUM,
)
internal data class OnrampNewCurrencyUM(
@@ -20,10 +20,9 @@ internal data class OnrampNewCurrencyUM(
)
@Immutable
-internal sealed interface OnrampNewAmountSecondaryFieldUM {
- data object Loading : OnrampNewAmountSecondaryFieldUM
- data class Content(val amount: TextReference) : OnrampNewAmountSecondaryFieldUM
- data class Error(val error: TextReference) : OnrampNewAmountSecondaryFieldUM
+internal sealed interface OnrampSecondaryFieldErrorUM {
+ data object Empty : OnrampSecondaryFieldErrorUM
+ data class Error(val error: TextReference) : OnrampSecondaryFieldErrorUM
}
internal sealed interface OnrampV2AmountButtonUMState {
diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/mainv2/entity/OnrampV2Intents.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/mainv2/entity/OnrampV2Intents.kt
index a9f65922cd..7ed9434b91 100644
--- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/mainv2/entity/OnrampV2Intents.kt
+++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/mainv2/entity/OnrampV2Intents.kt
@@ -9,5 +9,4 @@ internal interface OnrampV2Intents {
fun onBuyClick(quote: OnrampProviderWithQuote.Data, onrampOfferAdvantagesUM: OnrampOfferAdvantagesUM)
fun openProviders()
fun onRefresh()
- fun onContinueClick()
}
\ No newline at end of file
diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/mainv2/entity/OnrampV2MainComponentUM.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/mainv2/entity/OnrampV2MainComponentUM.kt
index 0b6560e380..256aeadd72 100644
--- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/mainv2/entity/OnrampV2MainComponentUM.kt
+++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/mainv2/entity/OnrampV2MainComponentUM.kt
@@ -9,33 +9,22 @@ import com.tangem.core.ui.extensions.TextReference
internal sealed interface OnrampV2MainComponentUM {
val topBarConfig: OnrampV2MainTopBarUM
- val continueButtonConfig: ContinueButtonUM
val errorNotification: NotificationUM?
data class InitialLoading(
override val topBarConfig: OnrampV2MainTopBarUM,
- override val continueButtonConfig: ContinueButtonUM,
override val errorNotification: NotificationUM?,
) : OnrampV2MainComponentUM
data class Content(
override val topBarConfig: OnrampV2MainTopBarUM,
- override val continueButtonConfig: ContinueButtonUM,
override val errorNotification: NotificationUM?,
val amountBlockState: OnrampNewAmountBlockUM,
val offersBlockState: OnrampOffersBlockUM,
val onrampAmountButtonUMState: OnrampV2AmountButtonUMState,
- val onrampProviderState: OnrampV2ProvidersUM,
) : OnrampV2MainComponentUM
}
-internal data class ContinueButtonUM(
- val text: TextReference,
- val onClick: () -> Unit,
- val enabled: Boolean,
- val showProgress: Boolean = false,
-)
-
internal data class OnrampV2MainTopBarUM(
val title: TextReference,
val startButtonUM: TopAppBarButtonUM,
diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/mainv2/entity/converter/OnrampV2AmountFieldChangeConverter.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/mainv2/entity/converter/OnrampV2AmountFieldChangeConverter.kt
index ec94be047d..34902a7683 100644
--- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/mainv2/entity/converter/OnrampV2AmountFieldChangeConverter.kt
+++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/mainv2/entity/converter/OnrampV2AmountFieldChangeConverter.kt
@@ -3,11 +3,7 @@ package com.tangem.features.onramp.mainv2.entity.converter
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
-import com.tangem.core.ui.extensions.stringReference
-import com.tangem.core.ui.format.bigdecimal.crypto
-import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.core.ui.utils.parseBigDecimalOrNull
-import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.features.onramp.mainv2.entity.*
import com.tangem.features.onramp.mainv2.entity.factory.OnrampAmountButtonUMStateFactory
import com.tangem.utils.Provider
@@ -18,7 +14,6 @@ internal class OnrampV2AmountFieldChangeConverter(
private val currentStateProvider: Provider,
private val onrampAmountButtonUMStateFactory: OnrampAmountButtonUMStateFactory,
private val onrampIntents: OnrampV2Intents,
- private val cryptoCurrency: CryptoCurrency,
) : Converter {
override fun convert(value: String): OnrampV2MainComponentUM {
@@ -39,12 +34,11 @@ internal class OnrampV2AmountFieldChangeConverter(
return state.copy(
amountBlockState = amountState.copy(
amountFieldModel = amountFieldModel,
- secondaryFieldModel = OnrampNewAmountSecondaryFieldUM.Loading,
+ secondaryFieldModel = OnrampSecondaryFieldErrorUM.Empty,
),
- continueButtonConfig = state.continueButtonConfig.copy(enabled = false),
- onrampProviderState = OnrampV2ProvidersUM.Loading,
onrampAmountButtonUMState = OnrampV2AmountButtonUMState.None,
- offersBlockState = OnrampOffersBlockUM.Empty,
+ offersBlockState = OnrampOffersBlockUM.Loading,
+ errorNotification = null,
)
}
@@ -63,22 +57,14 @@ internal class OnrampV2AmountFieldChangeConverter(
return copy(
amountBlockState = amountBlockState.copy(
amountFieldModel = amountFieldModel,
- secondaryFieldModel = OnrampNewAmountSecondaryFieldUM.Content(
- stringReference(
- BigDecimal.ZERO.format {
- crypto(cryptoCurrency = cryptoCurrency, ignoreSymbolPosition = true)
- },
- ),
- ),
+ secondaryFieldModel = OnrampSecondaryFieldErrorUM.Empty,
),
- continueButtonConfig = continueButtonConfig.copy(enabled = false),
offersBlockState = OnrampOffersBlockUM.Empty,
onrampAmountButtonUMState = onrampAmountButtonUMStateFactory.createOnrampAmountActionButton(
currencySymbol = amountBlockState.currencyUM.unit,
currencyCode = amountBlockState.currencyUM.code,
onAmountValueChanged = onrampIntents::onAmountValueChanged,
),
- onrampProviderState = OnrampV2ProvidersUM.Empty,
)
}
}
\ No newline at end of file
diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/mainv2/entity/factory/OnrampOffersStateFactory.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/mainv2/entity/factory/OnrampOffersStateFactory.kt
index bb490cc01f..7617651504 100644
--- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/mainv2/entity/factory/OnrampOffersStateFactory.kt
+++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/mainv2/entity/factory/OnrampOffersStateFactory.kt
@@ -17,15 +17,12 @@ internal class OnrampOffersStateFactory(
private val onrampIntents: OnrampV2Intents,
) {
- fun getOnShowOffersState(offers: List): OnrampV2MainComponentUM {
+ fun getOffersState(offers: List): OnrampV2MainComponentUM {
val currentState = currentStateProvider.invoke()
return when (currentState) {
is OnrampV2MainComponentUM.Content -> {
currentState.copy(
- offersBlockState = mapOnrampOffersBlockToUM(
- offersBlocks = offers,
- currentState = currentState,
- ),
+ offersBlockState = mapOnrampOffersBlockToUM(offersBlocks = offers),
)
}
is OnrampV2MainComponentUM.InitialLoading -> {
@@ -34,10 +31,7 @@ internal class OnrampOffersStateFactory(
}
}
- private fun mapOnrampOffersBlockToUM(
- offersBlocks: List,
- currentState: OnrampV2MainComponentUM.Content,
- ): OnrampOffersBlockUM.Content {
+ private fun mapOnrampOffersBlockToUM(offersBlocks: List): OnrampOffersBlockUM {
val allOffersUM = mutableListOf()
offersBlocks.map { block ->
block.offers.forEach { offer ->
@@ -51,7 +45,6 @@ internal class OnrampOffersStateFactory(
category = mapOfferCategoryDTOtoUM(block.category),
advantages = mapOfferAdvantagesDTOtoUM(offer.advantages),
paymentMethod = currentQuote.paymentMethod,
- providerId = currentQuote.provider.id,
providerName = currentQuote.provider.info.name,
rate = currentQuote.toAmount.value.format {
crypto(
@@ -80,8 +73,9 @@ internal class OnrampOffersStateFactory(
}
}
+ if (allOffersUM.isEmpty()) return OnrampOffersBlockUM.Empty
+
return OnrampOffersBlockUM.Content(
- isBlockVisible = currentState.offersBlockState.isBlockVisible,
recentOffer = allOffersUM.firstOrNull { it.category == OnrampOfferCategoryUM.RecentlyUsed },
recommended = allOffersUM.filter { it.category == OnrampOfferCategoryUM.Recommended }.toPersistentList(),
onrampAllOffersButtonConfig = if (offersBlocks.any { it.hasMoreOffers }) {
@@ -107,6 +101,7 @@ internal class OnrampOffersStateFactory(
OnrampOfferAdvantages.Default -> OnrampOfferAdvantagesUM.Default
OnrampOfferAdvantages.BestRate -> OnrampOfferAdvantagesUM.BestRate
OnrampOfferAdvantages.Fastest -> OnrampOfferAdvantagesUM.Fastest
+ OnrampOfferAdvantages.GreatRate -> OnrampOfferAdvantagesUM.GreatRate
}
}
}
\ No newline at end of file
diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/mainv2/entity/factory/OnrampV2AmountStateFactory.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/mainv2/entity/factory/OnrampV2AmountStateFactory.kt
index 9f08657016..cc17d8bc4e 100644
--- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/mainv2/entity/factory/OnrampV2AmountStateFactory.kt
+++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/mainv2/entity/factory/OnrampV2AmountStateFactory.kt
@@ -1,14 +1,10 @@
package com.tangem.features.onramp.mainv2.entity.factory
import com.tangem.core.analytics.api.AnalyticsEventHandler
-import com.tangem.core.ui.extensions.combinedReference
import com.tangem.core.ui.extensions.resourceReference
-import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.extensions.wrappedList
-import com.tangem.core.ui.format.bigdecimal.crypto
import com.tangem.core.ui.format.bigdecimal.fiat
import com.tangem.core.ui.format.bigdecimal.format
-import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.onramp.analytics.OnrampAnalyticsEvent
import com.tangem.domain.onramp.model.OnrampCurrency
import com.tangem.domain.onramp.model.OnrampQuote
@@ -18,13 +14,11 @@ import com.tangem.features.onramp.impl.R
import com.tangem.features.onramp.mainv2.entity.*
import com.tangem.features.onramp.mainv2.entity.converter.OnrampV2AmountFieldChangeConverter
import com.tangem.utils.Provider
-import java.math.BigDecimal
internal class OnrampV2AmountStateFactory(
private val currentStateProvider: Provider,
private val analyticsEventHandler: AnalyticsEventHandler,
private val onrampIntents: OnrampV2Intents,
- private val cryptoCurrency: CryptoCurrency,
private val onrampAmountButtonUMStateFactory: OnrampAmountButtonUMStateFactory,
) {
@@ -35,7 +29,6 @@ internal class OnrampV2AmountStateFactory(
currentStateProvider = currentStateProvider,
onrampAmountButtonUMStateFactory = onrampAmountButtonUMStateFactory,
onrampIntents = onrampIntents,
- cryptoCurrency = cryptoCurrency,
)
}
@@ -74,127 +67,41 @@ internal class OnrampV2AmountStateFactory(
)
}
- fun getAmountSecondaryLoadingState(): OnrampV2MainComponentUM {
- val currentState = currentStateProvider()
- if (currentState !is OnrampV2MainComponentUM.Content) return currentState
-
- val amountState = currentState.amountBlockState
-
- return currentState.copy(
- amountBlockState = amountState.copy(
- secondaryFieldModel = OnrampNewAmountSecondaryFieldUM.Loading,
- ),
- offersBlockState = OnrampOffersBlockUM.Loading(
- isBlockVisible = currentState.offersBlockState.isBlockVisible,
- ),
- continueButtonConfig = currentState.continueButtonConfig.copy(enabled = false),
- errorNotification = null,
- onrampAmountButtonUMState = OnrampV2AmountButtonUMState.None,
- onrampProviderState = OnrampV2ProvidersUM.Loading,
- )
- }
-
- fun getAmountSecondaryUpdatedState(quote: OnrampQuote): OnrampV2MainComponentUM {
+ fun getAmountSecondaryFieldUpdatedState(quotes: List): OnrampV2MainComponentUM {
val currentState = currentStateProvider()
if (currentState !is OnrampV2MainComponentUM.Content) return currentState
val amountState = currentState.amountBlockState
if (amountState.amountFieldModel.fiatValue.isEmpty()) return currentState
+ val limitedQuote = getLimitFromAmountErrors(quotes)
return currentState.copy(
amountBlockState = amountState.copy(
amountFieldModel = amountState.amountFieldModel.copy(isError = false),
- secondaryFieldModel = quote.toSecondaryFieldUiModel(amountState) ?: amountState.secondaryFieldModel,
- ),
- continueButtonConfig = currentState.continueButtonConfig.copy(
- enabled = quote is OnrampQuote.Data,
- onClick = onrampIntents::onContinueClick,
+ secondaryFieldModel = limitedQuote?.toSecondaryFieldUiModel(amountState)
+ ?: OnrampSecondaryFieldErrorUM.Empty,
),
errorNotification = null,
)
}
- fun getUpdatedProviderState(selectedQuote: OnrampQuote): OnrampV2MainComponentUM {
- val currentState = currentStateProvider()
- if (currentState !is OnrampV2MainComponentUM.Content) return currentState
-
- analyticsEventHandler.send(
- OnrampAnalyticsEvent.ProviderCalculated(
- providerName = selectedQuote.provider.info.name,
- tokenSymbol = cryptoCurrency.symbol,
- paymentMethod = selectedQuote.paymentMethod.name,
- ),
- )
- return currentState.copy(
- onrampProviderState = selectedQuote.toProviderBlockState(),
- )
- }
-
- fun getAmountSecondaryResetState(): OnrampV2MainComponentUM {
+ fun getAmountSecondaryFieldResetState(): OnrampV2MainComponentUM {
val currentState = currentStateProvider()
if (currentState !is OnrampV2MainComponentUM.Content) return currentState
val amountState = currentState.amountBlockState
-
- if (amountState.secondaryFieldModel is OnrampNewAmountSecondaryFieldUM.Content) return currentState
+ if (amountState.secondaryFieldModel is OnrampSecondaryFieldErrorUM.Empty) return currentState
return currentState.copy(
- amountBlockState = amountState.copy(
- secondaryFieldModel = OnrampNewAmountSecondaryFieldUM.Content(
- amount = stringReference(
- BigDecimal.ZERO.format {
- crypto(cryptoCurrency = cryptoCurrency, ignoreSymbolPosition = true)
- },
- ),
- ),
- ),
+ amountBlockState = amountState.copy(secondaryFieldModel = OnrampSecondaryFieldErrorUM.Empty),
onrampAmountButtonUMState = OnrampV2AmountButtonUMState.None,
errorNotification = null,
)
}
- fun getShowProvidersState(): OnrampV2MainComponentUM {
- val currentState = currentStateProvider()
- if (currentState !is OnrampV2MainComponentUM.Content) return currentState
-
- return when (currentState.offersBlockState) {
- is OnrampOffersBlockUM.Content -> {
- currentState.copy(
- offersBlockState = currentState.offersBlockState.copy(isBlockVisible = true),
- )
- }
- OnrampOffersBlockUM.Empty,
- is OnrampOffersBlockUM.Loading,
- -> currentState
- }
- }
-
- private fun OnrampQuote.toProviderBlockState(): OnrampV2ProvidersUM {
- return OnrampV2ProvidersUM.Content(
- paymentMethod = paymentMethod,
- providerId = provider.id,
- )
- }
-
- private fun OnrampQuote.toSecondaryFieldUiModel(
- amountState: OnrampNewAmountBlockUM,
- ): OnrampNewAmountSecondaryFieldUM? {
- return when (this) {
- is OnrampQuote.Error -> null
- is OnrampQuote.Data -> {
- val amount = toAmount.value.format {
- crypto(cryptoCurrency = cryptoCurrency, ignoreSymbolPosition = true)
- }
- val contentAmount = combinedReference(stringReference("\u007E"), stringReference(amount))
- OnrampNewAmountSecondaryFieldUM.Content(contentAmount)
- }
- is OnrampQuote.AmountError -> this.toSecondaryFieldUiModel(amountState)
- }
- }
-
private fun OnrampQuote.AmountError.toSecondaryFieldUiModel(
amountState: OnrampNewAmountBlockUM,
- ): OnrampNewAmountSecondaryFieldUM.Error {
+ ): OnrampSecondaryFieldErrorUM.Error {
val amount = error.requiredAmount.format {
fiat(
fiatCurrencyCode = amountState.amountFieldModel.fiatAmount.currencySymbol,
@@ -213,11 +120,27 @@ internal class OnrampV2AmountStateFactory(
}
}
- return OnrampNewAmountSecondaryFieldUM.Error(
+ return OnrampSecondaryFieldErrorUM.Error(
resourceReference(
errorTextRes,
wrappedList(amount),
),
)
}
+
+ private fun getLimitFromAmountErrors(quotes: List): OnrampQuote.AmountError? {
+ val amountErrorQuotes = quotes.filterIsInstance()
+ if (amountErrorQuotes.isEmpty()) {
+ return null
+ }
+ val tooSmallErrors = amountErrorQuotes.filter { it.error is OnrampError.AmountError.TooSmallError }
+ val tooBigErrors = amountErrorQuotes.filter { it.error is OnrampError.AmountError.TooBigError }
+ if (tooSmallErrors.isNotEmpty()) {
+ return tooSmallErrors.minByOrNull { it.error.requiredAmount }
+ }
+ if (tooBigErrors.isNotEmpty()) {
+ return tooBigErrors.maxByOrNull { it.error.requiredAmount }
+ }
+ return null
+ }
}
\ No newline at end of file
diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/mainv2/entity/factory/OnrampV2StateFactory.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/mainv2/entity/factory/OnrampV2StateFactory.kt
index 631c06be7a..ad9a4c4577 100644
--- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/mainv2/entity/factory/OnrampV2StateFactory.kt
+++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/mainv2/entity/factory/OnrampV2StateFactory.kt
@@ -11,8 +11,6 @@ import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.combinedReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
-import com.tangem.core.ui.format.bigdecimal.crypto
-import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.onramp.model.OnrampCurrency
import com.tangem.domain.onramp.model.error.OnrampError
@@ -50,11 +48,6 @@ internal class OnrampV2StateFactory(
isEnabled = false,
),
),
- continueButtonConfig = ContinueButtonUM(
- text = resourceReference(R.string.common_continue),
- onClick = {},
- enabled = false,
- ),
)
}
@@ -70,11 +63,6 @@ internal class OnrampV2StateFactory(
return OnrampV2MainComponentUM.Content(
topBarConfig = state.topBarConfig.copy(endButtonUM = endButton),
- continueButtonConfig = ContinueButtonUM(
- text = resourceReference(R.string.common_continue),
- onClick = onrampIntents::onContinueClick,
- enabled = false,
- ),
amountBlockState = initialAmountBlockState,
offersBlockState = OnrampOffersBlockUM.Empty,
errorNotification = null,
@@ -83,7 +71,6 @@ internal class OnrampV2StateFactory(
currencySymbol = currency.unit,
onAmountValueChanged = onrampIntents::onAmountValueChanged,
),
- onrampProviderState = OnrampV2ProvidersUM.Empty,
)
}
@@ -103,23 +90,6 @@ internal class OnrampV2StateFactory(
}
}
- private fun getNoPairsErrorState(): OnrampV2MainComponentUM {
- val state = currentStateProvider()
- val contentState = state as? OnrampV2MainComponentUM.Content ?: return state
-
- return contentState.copy(
- continueButtonConfig = contentState.continueButtonConfig.copy(enabled = false),
- amountBlockState = contentState.amountBlockState.copy(
- amountFieldModel = contentState.amountBlockState.amountFieldModel.copy(isError = true),
- secondaryFieldModel = OnrampNewAmountSecondaryFieldUM.Error(
- error = resourceReference(R.string.onramp_no_available_providers),
- ),
- ),
- onrampAmountButtonUMState = OnrampV2AmountButtonUMState.None,
- offersBlockState = OnrampOffersBlockUM.Empty,
- )
- }
-
fun getErrorState(errorCode: String? = null, onRefresh: () -> Unit): OnrampV2MainComponentUM {
val state = currentStateProvider()
val endButton = when (val button = state.topBarConfig.endButtonUM) {
@@ -130,23 +100,15 @@ internal class OnrampV2StateFactory(
return when (state) {
is OnrampV2MainComponentUM.Content -> state.copy(
topBarConfig = state.topBarConfig.copy(endButtonUM = endButton),
- continueButtonConfig = state.continueButtonConfig.copy(enabled = false),
- amountBlockState = state.amountBlockState.copy(
- secondaryFieldModel = OnrampNewAmountSecondaryFieldUM.Content(
- stringReference(
- BigDecimal.ZERO.format {
- crypto(cryptoCurrency = cryptoCurrency, ignoreSymbolPosition = true)
- },
- ),
- ),
- ),
offersBlockState = OnrampOffersBlockUM.Empty,
errorNotification = NotificationUM.Warning.OnrampErrorNotification(
errorCode = errorCode,
onRefresh = onRefresh,
),
onrampAmountButtonUMState = OnrampV2AmountButtonUMState.None,
- onrampProviderState = OnrampV2ProvidersUM.Empty,
+ amountBlockState = state.amountBlockState.copy(
+ secondaryFieldModel = OnrampSecondaryFieldErrorUM.Empty,
+ ),
)
is OnrampV2MainComponentUM.InitialLoading -> state.copy(
errorNotification = NotificationUM.Warning.OnrampErrorNotification(
@@ -157,6 +119,22 @@ internal class OnrampV2StateFactory(
}
}
+ private fun getNoPairsErrorState(): OnrampV2MainComponentUM {
+ val state = currentStateProvider()
+ val contentState = state as? OnrampV2MainComponentUM.Content ?: return state
+
+ return contentState.copy(
+ amountBlockState = contentState.amountBlockState.copy(
+ amountFieldModel = contentState.amountBlockState.amountFieldModel.copy(isError = true),
+ secondaryFieldModel = OnrampSecondaryFieldErrorUM.Error(
+ error = resourceReference(R.string.onramp_no_available_providers),
+ ),
+ ),
+ onrampAmountButtonUMState = OnrampV2AmountButtonUMState.None,
+ offersBlockState = OnrampOffersBlockUM.Empty,
+ )
+ }
+
private fun getInitialAmountBlockState(currency: OnrampCurrency): OnrampNewAmountBlockUM {
return OnrampNewAmountBlockUM(
currencyUM = OnrampNewCurrencyUM(
@@ -185,13 +163,7 @@ internal class OnrampV2StateFactory(
isValuePasted = false,
onValuePastedTriggerDismiss = {},
),
- secondaryFieldModel = OnrampNewAmountSecondaryFieldUM.Content(
- stringReference(
- BigDecimal.ZERO.format {
- crypto(cryptoCurrency = cryptoCurrency, ignoreSymbolPosition = true)
- },
- ),
- ),
+ secondaryFieldModel = OnrampSecondaryFieldErrorUM.Empty,
)
}
diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/mainv2/model/OnrampV2MainComponentModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/mainv2/model/OnrampV2MainComponentModel.kt
index 3407fe9696..d7008695dd 100644
--- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/mainv2/model/OnrampV2MainComponentModel.kt
+++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/mainv2/model/OnrampV2MainComponentModel.kt
@@ -1,6 +1,5 @@
package com.tangem.features.onramp.mainv2.model
-import androidx.compose.runtime.mutableStateOf
import com.arkivanov.decompose.router.slot.SlotNavigation
import com.arkivanov.decompose.router.slot.activate
import com.tangem.core.analytics.api.AnalyticsEventHandler
@@ -15,7 +14,6 @@ import com.tangem.domain.onramp.model.OnrampProviderWithQuote
import com.tangem.domain.onramp.model.OnrampQuote
import com.tangem.domain.onramp.model.error.OnrampError
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
-import com.tangem.features.onramp.main.entity.OnrampLastUpdate
import com.tangem.features.onramp.mainv2.OnrampV2MainComponent
import com.tangem.features.onramp.mainv2.entity.*
import com.tangem.features.onramp.mainv2.entity.factory.OnrampAmountButtonUMStateFactory
@@ -42,7 +40,7 @@ internal class OnrampV2MainComponentModel @Inject constructor(
private val getOnrampCountryUseCase: GetOnrampCountryUseCase,
private val clearOnrampCacheUseCase: ClearOnrampCacheUseCase,
private val fetchQuotesUseCase: OnrampFetchQuotesUseCase,
- private val getOnrampQuotesUseCase: GetOnrampV2QuotesUseCase,
+ private val getOnrampQuotesUseCase: GetOnrampQuotesUseCase,
private val fetchPairsUseCase: OnrampFetchPairsUseCase,
private val amountInputManager: InputManager,
private val getOnrampOffersUseCase: GetOnrampOffersUseCase,
@@ -52,8 +50,6 @@ internal class OnrampV2MainComponentModel @Inject constructor(
val params = paramsContainer.require()
- private val lastUpdateState = mutableStateOf(null)
-
private val onrampAmountButtonUMStateFactory: OnrampAmountButtonUMStateFactory by lazy(LazyThreadSafetyMode.NONE) {
OnrampAmountButtonUMStateFactory()
}
@@ -65,20 +61,23 @@ internal class OnrampV2MainComponentModel @Inject constructor(
)
}
- private val stateFactory = OnrampV2StateFactory(
- currentStateProvider = Provider { _state.value },
- cryptoCurrency = params.cryptoCurrency,
- onrampIntents = this,
- onrampAmountButtonUMStateFactory = onrampAmountButtonUMStateFactory,
- )
+ private val stateFactory: OnrampV2StateFactory by lazy(LazyThreadSafetyMode.NONE) {
+ OnrampV2StateFactory(
+ currentStateProvider = Provider { _state.value },
+ cryptoCurrency = params.cryptoCurrency,
+ onrampIntents = this,
+ onrampAmountButtonUMStateFactory = onrampAmountButtonUMStateFactory,
+ )
+ }
- private val amountStateFactory = OnrampV2AmountStateFactory(
- currentStateProvider = Provider { _state.value },
- analyticsEventHandler = analyticsEventHandler,
- onrampIntents = this,
- cryptoCurrency = params.cryptoCurrency,
- onrampAmountButtonUMStateFactory = onrampAmountButtonUMStateFactory,
- )
+ private val amountStateFactory: OnrampV2AmountStateFactory by lazy(LazyThreadSafetyMode.NONE) {
+ OnrampV2AmountStateFactory(
+ currentStateProvider = Provider { _state.value },
+ analyticsEventHandler = analyticsEventHandler,
+ onrampIntents = this,
+ onrampAmountButtonUMStateFactory = onrampAmountButtonUMStateFactory,
+ )
+ }
private val _state: MutableStateFlow = MutableStateFlow(
value = stateFactory.getInitialState(
@@ -97,7 +96,6 @@ internal class OnrampV2MainComponentModel @Inject constructor(
modelScope.launch {
clearOnrampCacheUseCase()
}
-
sendScreenOpenAnalytics()
checkResidenceCountry()
subscribeToAmountChanges()
@@ -139,7 +137,7 @@ internal class OnrampV2MainComponentModel @Inject constructor(
cryptoCurrencySymbol = params.cryptoCurrency.symbol,
providerName = quote.provider.info.name,
paymentMethodName = quote.paymentMethod.name,
- )?.let { analyticsEventHandler::send }
+ )?.let(analyticsEventHandler::send)
params.openRedirectPage(quote)
}
@@ -160,27 +158,23 @@ internal class OnrampV2MainComponentModel @Inject constructor(
modelScope.launch {
clearOnrampCacheUseCase.invoke()
checkResidenceCountry()
- }
- }
-
- override fun onContinueClick() {
- val currentState = _state.value
- if (currentState is OnrampV2MainComponentUM.Content) {
- _state.update { amountStateFactory.getShowProvidersState() }
+ handleOnrampAvailable()
}
}
fun onStart() {
- quotesTaskScheduler.scheduleTask(
- scope = modelScope,
- task = loadQuotesTask(),
- )
+ startLoadingQuotes()
}
fun onStop() {
quotesTaskScheduler.cancelTask()
}
+ fun handleOnrampAvailable() {
+ subscribeToCountryAndCurrencyUpdates()
+ subscribeToQuotesUpdate()
+ }
+
private fun startLoadingQuotes() {
quotesTaskScheduler.cancelTask()
quotesTaskScheduler.scheduleTask(scope = modelScope, task = loadQuotesTask())
@@ -235,7 +229,11 @@ internal class OnrampV2MainComponentModel @Inject constructor(
maybeOffers.fold(
ifLeft = ::handleOnrampError,
ifRight = { offers ->
- _state.update { onrampOffersStateFactory.getOnShowOffersState(offers) }
+ if (offers.isEmpty()) {
+ _state.update { stateFactory.getErrorState(onRefresh = ::onRetryQuotes) }
+ } else {
+ _state.update { onrampOffersStateFactory.getOffersState(offers) }
+ }
},
)
}
@@ -245,7 +243,6 @@ internal class OnrampV2MainComponentModel @Inject constructor(
amountInputManager.query
.filter(String::isNotEmpty)
.collectLatest { _ ->
- _state.update { amountStateFactory.getAmountSecondaryLoadingState() }
startLoadingQuotes()
}
}
@@ -288,74 +285,29 @@ internal class OnrampV2MainComponentModel @Inject constructor(
private fun handleQuoteResult(quotes: List) {
sendOnrampQuotesErrorAnalytic(quotes)
-
- val quote = selectOrUpdateQuote(quotes)
-
- if (quote == null) {
- _state.update { stateFactory.getErrorState(onRefresh = ::onRetryQuotes) }
- lastUpdateState.value = null
- return
- }
- _state.update { amountStateFactory.getAmountSecondaryUpdatedState(quote = quote) }
- }
-
- private fun selectOrUpdateQuote(quotes: List): OnrampQuote? {
- val quoteToCheck = quotes.firstOrNull { it !is OnrampQuote.Error }
-
- // Check if amount, country or currency has changed
- val newQuote = if (checkLastInputState(quoteToCheck)) {
- quoteToCheck
+ if (quotes.all { it is OnrampQuote.AmountError }) {
+ _state.update { amountStateFactory.getAmountSecondaryFieldUpdatedState(quotes) }
} else {
- val state = state.value as? OnrampV2MainComponentUM.Content
- val providerState = state?.onrampProviderState as? OnrampV2ProvidersUM.Content
-
- // Get current selected quote to update
- val lastSelectedQuote = quotes.firstOrNull {
- it.provider.id == providerState?.providerId &&
- it.paymentMethod.id == providerState.paymentMethod.id
- }
-
- if (lastSelectedQuote is OnrampQuote.Error) {
- quoteToCheck
- } else {
- lastSelectedQuote
- }
+ _state.update { amountStateFactory.getAmountSecondaryFieldResetState() }
}
- newQuote?.let { updateProvider(newQuote) }
-
- return newQuote
}
private fun onRetryQuotes() {
_state.update {
(it as? OnrampV2MainComponentUM.Content)?.copy(
errorNotification = null,
- onrampProviderState = OnrampV2ProvidersUM.Loading,
- offersBlockState = OnrampOffersBlockUM.Loading(isBlockVisible = false),
- amountBlockState = it.amountBlockState.copy(
- secondaryFieldModel = OnrampNewAmountSecondaryFieldUM.Loading,
- ),
+ offersBlockState = OnrampOffersBlockUM.Loading,
+ amountBlockState = it.amountBlockState.copy(secondaryFieldModel = OnrampSecondaryFieldErrorUM.Empty),
) ?: it
}
startLoadingQuotes()
}
private suspend fun updatePairsAndQuotes() {
- val state = state.value as? OnrampV2MainComponentUM.Content
-
- if (!state?.amountBlockState?.amountFieldModel?.fiatValue.isNullOrEmpty()) {
- _state.update { amountStateFactory.getAmountSecondaryLoadingState() }
- }
fetchPairsUseCase.invoke(userWallet, params.cryptoCurrency).fold(
ifLeft = ::handleOnrampError,
ifRight = {
- _state.update {
- if (!state?.amountBlockState?.amountFieldModel?.fiatValue.isNullOrEmpty()) {
- return@fold
- } else {
- amountStateFactory.getAmountSecondaryResetState()
- }
- }
+ _state.update { amountStateFactory.getAmountSecondaryFieldResetState() }
},
)
startLoadingQuotes()
@@ -366,18 +318,6 @@ internal class OnrampV2MainComponentModel @Inject constructor(
_state.update { stateFactory.getOnrampErrorState(onrampError) }
}
- private fun updateProvider(quote: OnrampQuote) {
- lastUpdateState.value = OnrampLastUpdate(
- fromAmount = quote.fromAmount,
- countryCode = quote.countryCode,
- paymentMethod = quote.paymentMethod,
- )
-
- _state.update {
- amountStateFactory.getUpdatedProviderState(selectedQuote = quote)
- }
- }
-
private fun sendOnrampQuotesErrorAnalytic(quotes: List) {
quotes.forEach { errorState ->
when (errorState) {
@@ -398,11 +338,6 @@ internal class OnrampV2MainComponentModel @Inject constructor(
}
}
- private fun checkLastInputState(quote: OnrampQuote?): Boolean {
- return lastUpdateState.value?.fromAmount != quote?.fromAmount ||
- lastUpdateState.value?.countryCode != quote?.countryCode
- }
-
private fun sendScreenOpenAnalytics() {
analyticsEventHandler.send(
OnrampAnalyticsEvent.ScreenOpened(
diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/mainv2/ui/OnrampFooterContent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/mainv2/ui/OnrampFooterContent.kt
index 5668fac482..979234f572 100644
--- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/mainv2/ui/OnrampFooterContent.kt
+++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/mainv2/ui/OnrampFooterContent.kt
@@ -14,16 +14,13 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
-import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.Keyboard
-import com.tangem.core.ui.components.PrimaryButton
import com.tangem.core.ui.components.SpacerH
import com.tangem.core.ui.components.keyboardAsState
-import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
-import com.tangem.features.onramp.impl.R
import com.tangem.features.onramp.mainv2.entity.OnrampAmountButtonUM
+import com.tangem.features.onramp.mainv2.entity.OnrampOffersBlockUM
import com.tangem.features.onramp.mainv2.entity.OnrampV2AmountButtonUMState
import com.tangem.features.onramp.mainv2.entity.OnrampV2MainComponentUM
@@ -33,14 +30,12 @@ internal fun OnrampFooterContent(
boxScope: BoxScope,
modifier: Modifier = Modifier,
) {
- val keyboardController = LocalSoftwareKeyboardController.current
-
boxScope.apply {
AnimatedVisibility(
modifier = Modifier
.imePadding()
.align(Alignment.BottomCenter),
- visible = state.offersBlockState.isBlockVisible.not(),
+ visible = state.offersBlockState is OnrampOffersBlockUM.Empty,
enter = slideInVertically(
initialOffsetY = { it },
animationSpec = tween(durationMillis = 300),
@@ -55,17 +50,6 @@ internal fun OnrampFooterContent(
modifier = modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally,
) {
- PrimaryButton(
- modifier = Modifier
- .fillMaxWidth()
- .padding(horizontal = 16.dp),
- text = stringResourceSafe(id = R.string.common_continue),
- onClick = {
- state.continueButtonConfig.onClick()
- keyboardController?.hide()
- },
- enabled = state.continueButtonConfig.enabled,
- )
SpacerH(16.dp)
OnrampAmountButtons(state = state.onrampAmountButtonUMState)
}
diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/mainv2/ui/OnrampOffersContent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/mainv2/ui/OnrampOffersContent.kt
index c39c79a8da..9a1a053cc5 100644
--- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/mainv2/ui/OnrampOffersContent.kt
+++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/mainv2/ui/OnrampOffersContent.kt
@@ -1,13 +1,10 @@
package com.tangem.features.onramp.mainv2.ui
import android.content.res.Configuration
-import androidx.compose.animation.AnimatedVisibility
-import androidx.compose.animation.core.tween
-import androidx.compose.animation.slideInVertically
-import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
@@ -43,19 +40,8 @@ import kotlinx.collections.immutable.persistentListOf
@Composable
internal fun OnrampOffersContent(state: OnrampOffersBlockUM) {
- AnimatedVisibility(
- visible = state.isBlockVisible,
- enter = slideInVertically(
- initialOffsetY = { it },
- animationSpec = tween(durationMillis = 300),
- ),
- exit = slideOutVertically(
- targetOffsetY = { it },
- animationSpec = tween(durationMillis = 300),
- ),
- label = "Offers block animation",
- ) {
- if (state is OnrampOffersBlockUM.Content) {
+ when (state) {
+ is OnrampOffersBlockUM.Content -> {
Column(modifier = Modifier.fillMaxWidth()) {
state.recentOffer?.let { recentOffer ->
Column {
@@ -100,6 +86,15 @@ internal fun OnrampOffersContent(state: OnrampOffersBlockUM) {
}
}
}
+ OnrampOffersBlockUM.Loading -> {
+ Box(
+ modifier = Modifier.fillMaxWidth(),
+ contentAlignment = Alignment.Center,
+ ) {
+ LoadingBlock()
+ }
+ }
+ OnrampOffersBlockUM.Empty -> Unit
}
}
@@ -120,13 +115,18 @@ internal fun Offer(onrampOfferUM: OnrampOfferUM, modifier: Modifier = Modifier)
) {
Column(verticalArrangement = Arrangement.spacedBy(2.dp)) {
OfferHeader(advantage = onrampOfferUM.advantages)
- RateBlock(rate = onrampOfferUM.rate, diff = onrampOfferUM.diff)
+ RateBlock(
+ rate = onrampOfferUM.rate,
+ diff = onrampOfferUM.diff,
+ isOfferUnavailable = onrampOfferUM.advantages == OnrampOfferAdvantagesUM.Unavailable,
+ )
}
SpacerWMax()
- SecondaryButton(
+ PrimaryButton(
size = TangemButtonSize.RoundedAction,
text = stringResourceSafe(R.string.common_buy),
onClick = onrampOfferUM.onBuyClicked,
+ enabled = onrampOfferUM.advantages != OnrampOfferAdvantagesUM.Unavailable,
)
}
SpacerH(10.dp)
@@ -142,6 +142,14 @@ internal fun Offer(onrampOfferUM: OnrampOfferUM, modifier: Modifier = Modifier)
@Composable
private fun OfferHeader(advantage: OnrampOfferAdvantagesUM) {
when (advantage) {
+ OnrampOfferAdvantagesUM.GreatRate -> {
+ Text(
+ text = stringResourceSafe(R.string.express_provider_great_rate),
+ style = TangemTheme.typography.caption1,
+ color = TangemTheme.colors.icon.accent,
+ modifier = Modifier.testTag(OnrampOffersBlockTestTags.BEST_RATE_TITLE),
+ )
+ }
OnrampOfferAdvantagesUM.Default -> {
Text(
text = stringResourceSafe(R.string.onramp_title_you_get),
@@ -169,27 +177,24 @@ private fun OfferHeader(advantage: OnrampOfferAdvantagesUM) {
}
}
OnrampOfferAdvantagesUM.Fastest -> {
- Row(
- verticalAlignment = Alignment.CenterVertically,
- horizontalArrangement = Arrangement.spacedBy(4.dp),
- ) {
- Icon(
- imageVector = ImageVector.vectorResource(R.drawable.ic_fastest_16),
- tint = TangemTheme.colors.icon.attention,
- contentDescription = null,
- )
- Text(
- text = stringResourceSafe(R.string.onramp_offer_type_fastet),
- style = TangemTheme.typography.caption1,
- color = TangemTheme.colors.icon.attention,
- )
- }
+ Text(
+ text = stringResourceSafe(R.string.onramp_offer_type_fastet),
+ style = TangemTheme.typography.caption1,
+ color = TangemTheme.colors.icon.attention,
+ )
+ }
+ OnrampOfferAdvantagesUM.Unavailable -> {
+ Text(
+ text = stringResourceSafe(R.string.onramp_title_available_from),
+ style = TangemTheme.typography.caption1,
+ color = TangemTheme.colors.text.tertiary,
+ )
}
}
}
@Composable
-private fun RateBlock(rate: String, diff: TextReference?) {
+private fun RateBlock(rate: String, diff: TextReference?, isOfferUnavailable: Boolean) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(4.dp),
@@ -197,7 +202,7 @@ private fun RateBlock(rate: String, diff: TextReference?) {
Text(
text = rate,
style = TangemTheme.typography.subtitle1,
- color = TangemTheme.colors.text.primary1,
+ color = if (isOfferUnavailable) TangemTheme.colors.text.tertiary else TangemTheme.colors.text.primary1,
modifier = Modifier.testTag(OnrampOffersBlockTestTags.OFFER_TOKEN_AMOUNT),
)
diff?.let {
@@ -314,7 +319,7 @@ internal fun TimingBlock(speed: PaymentMethodType.PaymentSpeed) {
}
@Composable
-fun DrawDot(color: Color) {
+private fun DrawDot(color: Color) {
Spacer(
modifier = Modifier
.size(4.dp)
@@ -330,6 +335,30 @@ fun DrawDot(color: Color) {
)
}
+@Composable
+private fun LoadingBlock(modifier: Modifier = Modifier) {
+ Column(
+ modifier = modifier,
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ SpacerH(100.dp)
+
+ Text(
+ text = stringResourceSafe(R.string.onramp_fetching_best_rates),
+ style = TangemTheme.typography.subtitle2,
+ color = TangemTheme.colors.text.tertiary,
+ )
+
+ SpacerH(8.dp)
+
+ CircularProgressIndicator(
+ modifier = Modifier.size(20.dp),
+ color = TangemTheme.colors.icon.informative,
+ strokeWidth = TangemTheme.dimens.size2,
+ )
+ }
+}
+
// will be hardcoded till server will be ready to provide this values
private const val FEW_MINS_VALUE = "3-5"
private const val FEW_DAYS_VALUE = 3
@@ -340,7 +369,6 @@ private const val PLENTY_DAYS_VALUE = 5
@Composable
private fun OnrampOffersContentPreview() {
val state = OnrampOffersBlockUM.Content(
- isBlockVisible = true,
recentOffer = OnrampOfferUM(
category = OnrampOfferCategoryUM.RecentlyUsed,
advantages = OnrampOfferAdvantagesUM.Default,
@@ -350,7 +378,6 @@ private fun OnrampOffersContentPreview() {
imageUrl = "https://s3.eu-central-1.amazonaws.com/tangem.api/express/PaymentMethods/visa-mc.png",
type = PaymentMethodType.CARD,
),
- providerId = "providerId3",
providerName = "Simplex",
rate = "0,00045334 BTC",
diff = stringReference("–27%"),
@@ -359,14 +386,13 @@ private fun OnrampOffersContentPreview() {
recommended = persistentListOf(
OnrampOfferUM(
category = OnrampOfferCategoryUM.Recommended,
- advantages = OnrampOfferAdvantagesUM.BestRate,
+ advantages = OnrampOfferAdvantagesUM.GreatRate,
paymentMethod = OnrampPaymentMethod(
id = "card",
name = "Card",
imageUrl = "https://s3.eu-central-1.amazonaws.com/tangem.api/express/PaymentMethods/visa-mc.png",
type = PaymentMethodType.CARD,
),
- providerId = "providerId1",
providerName = "Simplex",
rate = "0,0245334 BTC",
diff = null,
@@ -381,7 +407,6 @@ private fun OnrampOffersContentPreview() {
imageUrl = "https://s3.eu-central-1.amazonaws.com/tangem.api/express/PaymentMethods/visa-mc.png",
type = PaymentMethodType.CARD,
),
- providerId = "providerId2",
providerName = "Simplex",
rate = "0,00145334 BTC",
diff = stringReference("–0.07%"),
@@ -393,4 +418,13 @@ private fun OnrampOffersContentPreview() {
TangemThemePreview {
OnrampOffersContent(state)
}
+}
+
+@Preview(showBackground = true, widthDp = 360)
+@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
+@Composable
+private fun OnrampOffersLoadingPreview() {
+ TangemThemePreview {
+ OnrampOffersContent(OnrampOffersBlockUM.Loading)
+ }
}
\ No newline at end of file
diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/mainv2/ui/OnrampV2AmountContent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/mainv2/ui/OnrampV2AmountContent.kt
index b0ea37b98b..5e88e12d6b 100644
--- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/mainv2/ui/OnrampV2AmountContent.kt
+++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/mainv2/ui/OnrampV2AmountContent.kt
@@ -2,7 +2,6 @@ package com.tangem.features.onramp.mainv2.ui
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.animateContentSize
-import androidx.compose.animation.core.tween
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
@@ -22,12 +21,10 @@ import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
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.SpacerH
-import com.tangem.core.ui.components.TextShimmer
import com.tangem.core.ui.components.fields.AmountTextField
import com.tangem.core.ui.components.fields.visualtransformations.AmountVisualTransformation
import com.tangem.core.ui.extensions.resolveReference
@@ -36,20 +33,12 @@ import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.test.BuyTokenDetailsScreenTestTags
import com.tangem.core.ui.utils.rememberDecimalFormat
import com.tangem.features.onramp.impl.R
-import com.tangem.features.onramp.mainv2.entity.OnrampNewAmountSecondaryFieldUM
import com.tangem.features.onramp.mainv2.entity.OnrampNewCurrencyUM
+import com.tangem.features.onramp.mainv2.entity.OnrampSecondaryFieldErrorUM
import com.tangem.features.onramp.mainv2.entity.OnrampV2MainComponentUM
@Composable
internal fun OnrampV2AmountContent(state: OnrampV2MainComponentUM.Content, modifier: Modifier = Modifier) {
- val padding = remember(state.offersBlockState.isBlockVisible) {
- if (state.offersBlockState.isBlockVisible) {
- 22.dp
- } else {
- 46.dp
- }
- }
-
Column(
modifier = modifier
.fillMaxWidth()
@@ -57,8 +46,8 @@ internal fun OnrampV2AmountContent(state: OnrampV2MainComponentUM.Content, modif
color = TangemTheme.colors.background.action,
shape = RoundedCornerShape(size = TangemTheme.dimens.radius16),
)
- .padding(vertical = padding)
- .animateContentSize(animationSpec = tween(durationMillis = 300)),
+ .padding(vertical = 24.dp, horizontal = 16.dp)
+ .animateContentSize(),
horizontalAlignment = Alignment.CenterHorizontally,
) {
OnrampHeaderTitle()
@@ -68,8 +57,12 @@ internal fun OnrampV2AmountContent(state: OnrampV2MainComponentUM.Content, modif
currencyCode = state.amountBlockState.currencyUM.code,
)
- AnimatedVisibility(!state.offersBlockState.isBlockVisible) {
- OnrampAmountSecondary(state = state.amountBlockState.secondaryFieldModel)
+ AnimatedVisibility(
+ visible = state.amountBlockState.secondaryFieldModel !is OnrampSecondaryFieldErrorUM.Empty,
+ ) {
+ if (state.amountBlockState.secondaryFieldModel is OnrampSecondaryFieldErrorUM.Error) {
+ OnrampAmountSecondary(state = state.amountBlockState.secondaryFieldModel)
+ }
}
SpacerH(20.dp)
@@ -130,7 +123,7 @@ private fun OnrampAmountField(amountField: AmountFieldModel, currencyCode: Strin
}
@Composable
-private fun OnrampAmountSecondary(state: OnrampNewAmountSecondaryFieldUM) {
+private fun OnrampAmountSecondary(state: OnrampSecondaryFieldErrorUM.Error) {
Box(
modifier = Modifier
.fillMaxWidth()
@@ -142,24 +135,12 @@ private fun OnrampAmountSecondary(state: OnrampNewAmountSecondaryFieldUM) {
.testTag(BuyTokenDetailsScreenTestTags.TOKEN_AMOUNT),
contentAlignment = Alignment.Center,
) {
- when (state) {
- is OnrampNewAmountSecondaryFieldUM.Content -> Text(
- text = state.amount.resolveReference(),
- style = TangemTheme.typography.caption2.copy(textDirection = TextDirection.ContentOrLtr),
- color = TangemTheme.colors.text.tertiary,
- textAlign = TextAlign.Center,
- )
- is OnrampNewAmountSecondaryFieldUM.Error -> Text(
- text = state.error.resolveReference(),
- color = TangemTheme.colors.text.warning,
- style = TangemTheme.typography.caption2,
- textAlign = TextAlign.Center,
- )
- is OnrampNewAmountSecondaryFieldUM.Loading -> TextShimmer(
- style = TangemTheme.typography.caption2,
- modifier = Modifier.width(TangemTheme.dimens.size62),
- )
- }
+ Text(
+ text = state.error.resolveReference(),
+ color = TangemTheme.colors.text.warning,
+ style = TangemTheme.typography.caption2,
+ textAlign = TextAlign.Center,
+ )
}
}