diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index b9db373358..8981d0a0b1 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -30,6 +30,13 @@
+
+
+
+
+
+
diff --git a/app/src/main/java/com/tangem/tap/common/url/CustomTabsUrlOpener.kt b/app/src/main/java/com/tangem/tap/common/url/CustomTabsUrlOpener.kt
index 7fe3ef0081..965d6caacd 100644
--- a/app/src/main/java/com/tangem/tap/common/url/CustomTabsUrlOpener.kt
+++ b/app/src/main/java/com/tangem/tap/common/url/CustomTabsUrlOpener.kt
@@ -1,8 +1,11 @@
package com.tangem.tap.common.url
import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageManager
import android.net.Uri
import androidx.browser.customtabs.CustomTabColorSchemeParams
+import androidx.browser.customtabs.CustomTabsClient
import androidx.browser.customtabs.CustomTabsIntent
import androidx.browser.customtabs.CustomTabsIntent.COLOR_SCHEME_DARK
import androidx.browser.customtabs.CustomTabsIntent.COLOR_SCHEME_LIGHT
@@ -12,6 +15,7 @@ import com.tangem.tap.common.extensions.getColorCompat
import com.tangem.tap.foregroundActivityObserver
import com.tangem.tap.withForegroundActivity
import com.tangem.wallet.R
+import timber.log.Timber
internal class CustomTabsUrlOpener : UrlOpener {
@@ -34,6 +38,37 @@ internal class CustomTabsUrlOpener : UrlOpener {
)
.build()
- customTabsIntent.launchUrl(context, Uri.parse(url))
+ customTabsIntent.intent.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP
+
+ val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
+ runCatching {
+ if (checkCustomTabsAvailability(context, browserIntent)) {
+ context.startActivity(browserIntent)
+ } else {
+ customTabsIntent.launchUrl(context, Uri.parse(url))
+ }
+ }.onFailure {
+ Timber.e(it.message)
+ }
+ }
+
+ /**
+ * Custom Tabs compatibility check. Returns flag whether custom tabs are supported
+ * @see "https://developer.chrome.com/docs/android/custom-tabs/howto-custom-tab-check"
+ */
+ private fun checkCustomTabsAvailability(context: Context, browserIntent: Intent): Boolean {
+ // Get all apps that can handle VIEW intents and Custom Tab service connections.
+ val resolveInfos = context.packageManager.queryIntentActivities(browserIntent, PackageManager.MATCH_ALL)
+
+ // Extract package names from ResolveInfo objects
+ val packageNames = mutableListOf()
+ for (info in resolveInfos) {
+ packageNames.add(info.activityInfo.packageName)
+ }
+
+ // Get a package that supports Custom Tabs
+ val packageName = CustomTabsClient.getPackageName(context, packageNames, true)
+
+ return packageName == null // Custom Tabs are not supported by any browser on the device
}
}
\ No newline at end of file
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/core/deep-links/global/src/main/kotlin/com/tangem/core/deeplink/global/BuyCurrencyDeepLink.kt b/core/deep-links/global/src/main/kotlin/com/tangem/core/deeplink/global/BuyCurrencyDeepLink.kt
index b9427aee10..259ffd2cbb 100644
--- a/core/deep-links/global/src/main/kotlin/com/tangem/core/deeplink/global/BuyCurrencyDeepLink.kt
+++ b/core/deep-links/global/src/main/kotlin/com/tangem/core/deeplink/global/BuyCurrencyDeepLink.kt
@@ -3,15 +3,10 @@ package com.tangem.core.deeplink.global
import com.tangem.core.deeplink.DeepLink
class BuyCurrencyDeepLink(
- isOnrampFeatureEnabled: Boolean,
val onReceive: (externalTxId: String) -> Unit,
) : DeepLink {
- override val uri: String = if (isOnrampFeatureEnabled) {
- ONRAMP_REDIRECT_DEEPLINK
- } else {
- BUY_REDIRECT_DEEPLINK
- }
+ override val uri = BUY_REDIRECT_DEEPLINK
override fun onReceive(params: Map) {
onReceive(
@@ -20,7 +15,7 @@ class BuyCurrencyDeepLink(
}
companion object {
- const val ONRAMP_REDIRECT_DEEPLINK = "tangem://onramp-success?"
+ const val ONRAMP_REDIRECT_DEEPLINK = "https://tangem.com/success?action=dismissBrowser"
private const val BUY_REDIRECT_DEEPLINK = "tangem://redirect?action=dismissBrowser"
}
}
\ No newline at end of file
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 ca78dbeca6..3d7221ff2d 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
@@ -32,10 +32,8 @@ import com.tangem.datasource.local.onramp.quotes.OnrampQuotesStore
import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.datasource.local.preferences.PreferencesKeys
import com.tangem.datasource.local.preferences.utils.getObject
-import com.tangem.datasource.local.preferences.utils.getObjectSyncOrDefault
import com.tangem.datasource.local.preferences.utils.getObjectSyncOrNull
import com.tangem.datasource.local.preferences.utils.storeObject
-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
@@ -281,6 +279,7 @@ internal class DefaultOnrampRepository(
userWalletId: UserWalletId,
cryptoCurrency: CryptoCurrency,
quote: OnrampProviderWithQuote.Data,
+ isDarkTheme: Boolean,
): OnrampTransaction = withContext(dispatchers.io) {
try {
val address = requireNotNull(
@@ -306,7 +305,7 @@ internal class DefaultOnrampRepository(
toAddress = address,
redirectUrl = BuyCurrencyDeepLink.ONRAMP_REDIRECT_DEEPLINK,
language = null,
- theme = getTheme(),
+ theme = getTheme(isDarkTheme),
requestId = requestId,
).bind()
},
@@ -415,7 +414,7 @@ internal class DefaultOnrampRepository(
fromCurrency = currency,
toAmount = quote.toAmount.value,
toCurrencyId = cryptoCurrency.id.value,
- status = OnrampStatus.Status.Expired,
+ status = OnrampStatus.Status.Created,
externalTxUrl = onrampDataJson.externalTxUrl,
externalTxId = onrampDataJson.externalTxId,
timestamp = DateTime.now().millis,
@@ -442,17 +441,10 @@ internal class DefaultOnrampRepository(
)
}
- private suspend fun getTheme(): String {
- val appTheme = appPreferencesStore.getObjectSyncOrDefault(
- key = PreferencesKeys.APP_THEME_MODE_KEY,
- default = AppThemeMode.DEFAULT,
- )
- return when (appTheme) {
- AppThemeMode.FORCE_DARK -> PROVIDER_THEME_DARK
- AppThemeMode.FORCE_LIGHT,
- AppThemeMode.FOLLOW_SYSTEM,
- -> PROVIDER_THEME_LIGHT
- }
+ private fun getTheme(isDarkTheme: Boolean): String = if (isDarkTheme) {
+ PROVIDER_THEME_DARK
+ } else {
+ PROVIDER_THEME_LIGHT
}
private fun List.removeApplePay(): List = filterNot { it.id == "apple-pay" }
@@ -465,7 +457,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 +465,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/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampRedirectUrlUseCase.kt b/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampRedirectUrlUseCase.kt
index 34bb9f5202..55ba74a5bf 100644
--- a/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampRedirectUrlUseCase.kt
+++ b/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampRedirectUrlUseCase.kt
@@ -19,12 +19,14 @@ class GetOnrampRedirectUrlUseCase(
userWalletId: UserWalletId,
quote: OnrampProviderWithQuote.Data,
cryptoCurrency: CryptoCurrency,
+ isDarkTheme: Boolean,
): Either {
return Either.catch {
val transaction = repository.getOnrampData(
userWalletId = userWalletId,
cryptoCurrency = cryptoCurrency,
quote = quote,
+ isDarkTheme = isDarkTheme,
)
transactionRepository.storeTransaction(transaction)
transaction.redirectUrl
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 87e9bff955..302a563b12 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
@@ -24,6 +24,7 @@ interface OnrampRepository {
userWalletId: UserWalletId,
cryptoCurrency: CryptoCurrency,
quote: OnrampProviderWithQuote.Data,
+ isDarkTheme: Boolean,
): OnrampTransaction
// cache
diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListFiatBalanceOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListFiatBalanceOperations.kt
index de711b188f..41f42b4ccd 100644
--- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListFiatBalanceOperations.kt
+++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListFiatBalanceOperations.kt
@@ -4,6 +4,7 @@ import arrow.core.NonEmptyList
import com.tangem.domain.staking.model.stakekit.YieldBalance
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.tokens.model.TotalFiatBalance
+import com.tangem.lib.crypto.BlockchainUtils
import com.tangem.utils.extensions.orZero
import java.math.BigDecimal
@@ -12,6 +13,7 @@ internal class TokenListFiatBalanceOperations(
private val isAnyTokenLoading: Boolean,
) {
+ @Suppress("LoopWithTooManyJumpStatements")
fun calculateFiatBalance(): TotalFiatBalance {
var fiatBalance: TotalFiatBalance = TotalFiatBalance.Loading
if (isAnyTokenLoading) return fiatBalance
@@ -22,14 +24,22 @@ internal class TokenListFiatBalanceOperations(
fiatBalance = TotalFiatBalance.Loading
break
}
- is CryptoCurrencyStatus.MissedDerivation,
- is CryptoCurrencyStatus.Unreachable,
- is CryptoCurrencyStatus.NoAmount,
is CryptoCurrencyStatus.NoQuote,
+ is CryptoCurrencyStatus.MissedDerivation,
-> {
fiatBalance = TotalFiatBalance.Failed
break
}
+ is CryptoCurrencyStatus.Unreachable,
+ is CryptoCurrencyStatus.NoAmount,
+ -> {
+ if (BlockchainUtils.isIncludeToBalanceOnError(token.currency.network.id.value)) {
+ fiatBalance = recalculateNoAccountBalance(fiatBalance)
+ } else {
+ fiatBalance = TotalFiatBalance.Failed
+ break
+ }
+ }
is CryptoCurrencyStatus.NoAccount -> {
fiatBalance = recalculateNoAccountBalance(fiatBalance)
}
diff --git a/features/markets/impl/build.gradle.kts b/features/markets/impl/build.gradle.kts
index 4f73123e32..87db7e37d6 100644
--- a/features/markets/impl/build.gradle.kts
+++ b/features/markets/impl/build.gradle.kts
@@ -14,6 +14,7 @@ android {
dependencies {
/* Project - API */
api(projects.features.markets.api)
+ api(projects.features.onramp.api)
implementation(projects.core.navigation)
/* Domain */
diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/TokenActionsHandler.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/TokenActionsHandler.kt
index 34923a5f2d..ce9e2b7841 100644
--- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/TokenActionsHandler.kt
+++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/TokenActionsHandler.kt
@@ -23,6 +23,7 @@ import com.tangem.features.markets.impl.R
import com.tangem.features.markets.portfolio.impl.loader.PortfolioData
import com.tangem.features.markets.portfolio.impl.ui.WarningDialog
import com.tangem.features.markets.portfolio.impl.ui.state.TokenActionsBSContentUM
+import com.tangem.features.onramp.OnrampFeatureToggles
import com.tangem.utils.Provider
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
@@ -41,12 +42,15 @@ internal class TokenActionsHandler @AssistedInject constructor(
@Assisted private val onHandleQuickAction: (HandledQuickAction) -> Unit,
private val isDemoCardUseCase: IsDemoCardUseCase,
private val messageSender: UiMessageSender,
+ private val onrampFeatureToggles: OnrampFeatureToggles,
) {
- private val disabledActionsInDemoMode = setOf(
- TokenActionsBSContentUM.Action.Buy,
- TokenActionsBSContentUM.Action.Sell,
- )
+ private val disabledActionsInDemoMode = buildSet {
+ if (!onrampFeatureToggles.isFeatureEnabled) {
+ add(TokenActionsBSContentUM.Action.Buy)
+ }
+ add(TokenActionsBSContentUM.Action.Sell)
+ }
fun handle(action: TokenActionsBSContentUM.Action, cryptoCurrencyData: PortfolioData.CryptoCurrencyData) {
onHandleQuickAction(
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/redirect/DefaultOnrampRedirectComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/redirect/DefaultOnrampRedirectComponent.kt
index 71cedbb3a5..17904c58e6 100644
--- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/redirect/DefaultOnrampRedirectComponent.kt
+++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/redirect/DefaultOnrampRedirectComponent.kt
@@ -1,7 +1,9 @@
package com.tangem.features.onramp.redirect
import androidx.activity.compose.BackHandler
+import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Modifier
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.model.getOrCreateModel
@@ -22,6 +24,11 @@ internal class DefaultOnrampRedirectComponent @AssistedInject constructor(
override fun Content(modifier: Modifier) {
BackHandler(onBack = params.onBack)
OnrampRedirectContent(modifier = modifier, state = model.state)
+
+ val isDarkTheme = isSystemInDarkTheme()
+ LaunchedEffect(model.state) {
+ model.getRedirectUrl(isDarkTheme = isDarkTheme)
+ }
}
@AssistedFactory
diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/redirect/model/OnrampRedirectModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/redirect/model/OnrampRedirectModel.kt
index f15be660c5..0ce73d2372 100644
--- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/redirect/model/OnrampRedirectModel.kt
+++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/redirect/model/OnrampRedirectModel.kt
@@ -62,16 +62,13 @@ internal class OnrampRedirectModel @Inject constructor(
),
)
- init {
- getRedirectUrl()
- }
-
- private fun getRedirectUrl() {
+ fun getRedirectUrl(isDarkTheme: Boolean) {
modelScope.launch {
getOnrampRedirectUrlUseCase.invoke(
userWalletId = params.userWalletId,
quote = params.onrampProviderWithQuote,
cryptoCurrency = params.cryptoCurrency,
+ isDarkTheme = isDarkTheme,
)
.onLeft(::handleError)
.onRight {
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,
diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/router/CustomTabsManager.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/router/CustomTabsManager.kt
index 565d27a3b8..af04294b72 100644
--- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/router/CustomTabsManager.kt
+++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/router/CustomTabsManager.kt
@@ -8,6 +8,7 @@ import androidx.browser.customtabs.CustomTabColorSchemeParams
import androidx.browser.customtabs.CustomTabsIntent
import java.lang.ref.WeakReference
+@Deprecated("Replace with CustomTabsUrlOpener")
class CustomTabsManager(private val context: WeakReference) {
fun openUrl(url: String) {
diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/router/DefaultTokenDetailsRouter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/router/DefaultTokenDetailsRouter.kt
index 52eae5de76..d8a3eb4733 100644
--- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/router/DefaultTokenDetailsRouter.kt
+++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/router/DefaultTokenDetailsRouter.kt
@@ -50,6 +50,13 @@ internal class DefaultTokenDetailsRouter(
}
override fun openOnrampSuccess(externalTxId: String) {
- router.push(AppRoute.OnrampSuccess(externalTxId))
+ // finish current onramp flow and show onramp success screen
+ val replaceOnrampScreens = router.stack
+ .filterNot { it is AppRoute.Onramp }
+ .toMutableList()
+
+ replaceOnrampScreens.add(AppRoute.OnrampSuccess(externalTxId))
+
+ router.replaceAll(*replaceOnrampScreens.toTypedArray())
}
}
\ No newline at end of file
diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/TokenDetailsPreviewData.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/TokenDetailsPreviewData.kt
index 625959ab86..358d295129 100644
--- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/TokenDetailsPreviewData.kt
+++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/TokenDetailsPreviewData.kt
@@ -308,6 +308,7 @@ internal object TokenDetailsPreviewData {
dialogConfig = null,
pendingTxs = persistentListOf(),
expressTxs = persistentListOf(),
+ expressTxsToDisplay = persistentListOf(),
pullToRefreshConfig = pullToRefreshConfig,
bottomSheetConfig = null,
isBalanceHidden = false,
@@ -338,6 +339,7 @@ internal object TokenDetailsPreviewData {
dialogConfig = null,
pendingTxs = persistentListOf(),
expressTxs = persistentListOf(),
+ expressTxsToDisplay = persistentListOf(),
pullToRefreshConfig = pullToRefreshConfig,
bottomSheetConfig = null,
isBalanceHidden = false,
diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/TokenDetailsState.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/TokenDetailsState.kt
index 8ece0d02e9..d285c89fc9 100644
--- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/TokenDetailsState.kt
+++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/TokenDetailsState.kt
@@ -21,6 +21,7 @@ internal data class TokenDetailsState(
val stakingBlocksState: StakingBlockUM?,
val notifications: ImmutableList,
val pendingTxs: PersistentList,
+ val expressTxsToDisplay: PersistentList,
val expressTxs: PersistentList,
val txHistoryState: TxHistoryState,
val dialogConfig: TokenDetailsDialogConfig?,
diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSkeletonStateConverter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSkeletonStateConverter.kt
index 07f98c6f10..f22431609c 100644
--- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSkeletonStateConverter.kt
+++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSkeletonStateConverter.kt
@@ -67,6 +67,7 @@ internal class TokenDetailsSkeletonStateConverter(
notifications = persistentListOf(),
pendingTxs = persistentListOf(),
expressTxs = persistentListOf(),
+ expressTxsToDisplay = persistentListOf(),
txHistoryState = TxHistoryState.Content(
contentItems = MutableStateFlow(
value = TxHistoryState.getDefaultLoadingTransactions(clickIntents::onExploreClick),
diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/express/ExpressStatusFactory.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/express/ExpressStatusFactory.kt
index 92a6a2508c..aa5d0ecc92 100644
--- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/express/ExpressStatusFactory.kt
+++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/express/ExpressStatusFactory.kt
@@ -98,8 +98,15 @@ internal class ExpressStatusFactory @AssistedInject constructor(
if (currentTx is ExpressTransactionStateUM.ExchangeUM && currentTx.activeStatus == ExchangeStatus.Finished) {
updateBalance(currentTx.toCryptoCurrency)
}
+ val expressTxsToDisplay = expressTxs.filterNot {
+ when (it) {
+ is ExpressTransactionStateUM.ExchangeUM -> false
+ is ExpressTransactionStateUM.OnrampUM -> it.activeStatus.isHidden
+ }
+ }.toPersistentList()
return state.copy(
expressTxs = expressTxs,
+ expressTxsToDisplay = expressTxsToDisplay,
bottomSheetConfig = currentTx?.let(
::updateStateWithExpressStatusBottomSheet,
) ?: config,
diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/express/OnrampStatusFactory.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/express/OnrampStatusFactory.kt
index eca24c7c13..3e2cbe8813 100644
--- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/express/OnrampStatusFactory.kt
+++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/express/OnrampStatusFactory.kt
@@ -61,7 +61,7 @@ internal class OnrampStatusFactory @AssistedInject constructor(
ifRight = { onrampTxs ->
val transactions = onrampTransactionStateConverter.convertList(onrampTxs)
transactions.clearHiddenTerminal()
- transactions.filterNot { it.activeStatus.isHidden }
+ transactions
},
ifLeft = { persistentListOf() },
)
diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/TokenDetailsScreen.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/TokenDetailsScreen.kt
index a22b97d642..a522838fe3 100644
--- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/TokenDetailsScreen.kt
+++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/TokenDetailsScreen.kt
@@ -178,7 +178,7 @@ internal fun TokenDetailsScreen(state: TokenDetailsState, tokenMarketBlockCompon
}
expressTransactionsItems(
- expressTxs = state.expressTxs,
+ expressTxs = state.expressTxsToDisplay,
modifier = itemModifier,
)
diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt
index 480de2e7f2..6690f0a085 100644
--- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt
+++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt
@@ -198,7 +198,6 @@ internal class TokenDetailsViewModel @Inject constructor(
viewModel = this,
deepLinks = listOf(
BuyCurrencyDeepLink(
- isOnrampFeatureEnabled = onrampFeatureToggles.isFeatureEnabled,
onReceive = ::onBuyCurrencyDeepLink,
),
),
@@ -336,6 +335,7 @@ internal class TokenDetailsViewModel @Inject constructor(
expressTxStatusTaskScheduler.scheduleTask(
viewModelScope,
PeriodicTask(
+ isDelayFirst = false,
delay = EXPRESS_STATUS_UPDATE_DELAY,
task = {
runCatching {
@@ -462,9 +462,8 @@ internal class TokenDetailsViewModel @Inject constructor(
return
}
- showErrorIfDemoModeOrElse {
- val status = cryptoCurrencyStatus ?: return@showErrorIfDemoModeOrElse
-
+ val status = cryptoCurrencyStatus ?: return
+ if (onrampFeatureToggles.isFeatureEnabled) {
viewModelScope.launch(dispatchers.main) {
reduxStateHolder.dispatch(
TradeCryptoAction.Buy(
@@ -475,6 +474,19 @@ internal class TokenDetailsViewModel @Inject constructor(
),
)
}
+ } else {
+ showErrorIfDemoModeOrElse {
+ viewModelScope.launch(dispatchers.main) {
+ reduxStateHolder.dispatch(
+ TradeCryptoAction.Buy(
+ userWallet = userWallet,
+ source = OnrampSource.TOKEN_DETAILS,
+ cryptoCurrencyStatus = status,
+ appCurrencyCode = selectedAppCurrencyFlow.value.code,
+ ),
+ )
+ }
+ }
}
}
@@ -805,7 +817,7 @@ internal class TokenDetailsViewModel @Inject constructor(
}
override fun onExpressTransactionClick(txId: String) {
- val expressTxState = internalUiState.value.expressTxs.first { it.info.txId == txId }
+ val expressTxState = internalUiState.value.expressTxsToDisplay.first { it.info.txId == txId }
internalUiState.value = expressStatusFactory.getStateWithExpressStatusBottomSheet(expressTxState)
}
diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/deeplink/WalletDeepLinksHandler.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/deeplink/WalletDeepLinksHandler.kt
index c9abd72ef6..d729196c4f 100644
--- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/deeplink/WalletDeepLinksHandler.kt
+++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/deeplink/WalletDeepLinksHandler.kt
@@ -65,7 +65,6 @@ internal class WalletDeepLinksHandler @Inject constructor(
if (onrampFeatureToggles.isFeatureEnabled || !userWallet.isMultiCurrency) {
add(
BuyCurrencyDeepLink(
- isOnrampFeatureEnabled = onrampFeatureToggles.isFeatureEnabled,
onReceive = { externalTxId ->
scope.launch { onBuyCurrencyDeepLink(externalTxId, userWallet) }
},
diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt
index 4d2df2bd9f..6780b00e47 100644
--- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt
+++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt
@@ -130,7 +130,14 @@ internal class DefaultWalletRouter(
}
override fun openOnrampSuccessScreen(externalTxId: String) {
- router.push(AppRoute.OnrampSuccess(externalTxId))
+ // finish current onramp flow and show onramp success screen
+ val replaceOnrampScreens = router.stack
+ .filterNot { it is AppRoute.Onramp }
+ .toMutableList()
+
+ replaceOnrampScreens.add(AppRoute.OnrampSuccess(externalTxId))
+
+ router.replaceAll(*replaceOnrampScreens.toTypedArray())
}
override fun openUrl(url: String) {
diff --git a/libs/crypto/src/main/java/com/tangem/lib/crypto/BlockchainUtils.kt b/libs/crypto/src/main/java/com/tangem/lib/crypto/BlockchainUtils.kt
index 456959fddd..80b0af4a35 100644
--- a/libs/crypto/src/main/java/com/tangem/lib/crypto/BlockchainUtils.kt
+++ b/libs/crypto/src/main/java/com/tangem/lib/crypto/BlockchainUtils.kt
@@ -116,6 +116,17 @@ object BlockchainUtils {
return l2BlockchainsList.contains(blockchain)
}
+ /**
+ * Blockchains not affecting total balance counting on errors
+ */
+ fun isIncludeToBalanceOnError(blockchainId: String): Boolean {
+ val blockchain = Blockchain.fromId(blockchainId)
+ return when (blockchain) {
+ Blockchain.Binance, Blockchain.BinanceTestnet -> true
+ else -> false
+ }
+ }
+
private fun getNetworkStandardName(blockchain: Blockchain): String {
return when (blockchain) {
Blockchain.Ethereum, Blockchain.EthereumTestnet -> "ERC20"