Updated on 2026-08-14

This commit is contained in:
Tangem 2024-12-17 14:21:36 +03:00
commit d4d197dfe0
34 changed files with 199 additions and 75 deletions

View file

@ -30,6 +30,13 @@
</intent>
</queries>
<queries>
<intent>
<action android:name=
"android.support.customtabs.action.CustomTabsService" />
</intent>
</queries>
<application
android:name="com.tangem.tap.TangemHiltApplication"
android:allowBackup="false"
@ -53,7 +60,7 @@
android:name="com.tangem.tap.MainActivity"
android:configChanges="uiMode"
android:exported="true"
android:launchMode="singleTask"
android:launchMode="singleTop"
android:screenOrientation="portrait"
android:theme="@style/SplashTheme"
android:windowSoftInputMode="adjustResize">

View file

@ -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<String>()
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
}
}

View file

@ -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,

View file

@ -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<String, String>) {
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"
}
}

View file

@ -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<PaymentMethodDTO>.removeApplePay(): List<PaymentMethodDTO> = 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")

View file

@ -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,
)

View file

@ -23,7 +23,7 @@ sealed interface OnrampProviderWithQuote {
data class Error(
override val provider: OnrampProvider,
val quoteError: OnrampQuote.Error,
val quoteError: OnrampQuote.AmountError,
) : Unavailable
}
}

View file

@ -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()
}

View file

@ -31,8 +31,8 @@ class GetOnrampProviderWithQuoteUseCase(
private fun List<OnrampQuote>.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<OnrampQuote>.getAvailablePaymentMethods(provider: OnrampProvider) = provider.paymentMethods
.filter { pm ->
filter { it !is OnrampQuote.Error }.any { it.paymentMethod.id == pm.id }
}
}

View file

@ -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

View file

@ -19,12 +19,14 @@ class GetOnrampRedirectUrlUseCase(
userWalletId: UserWalletId,
quote: OnrampProviderWithQuote.Data,
cryptoCurrency: CryptoCurrency,
isDarkTheme: Boolean,
): Either<OnrampError, String> {
return Either.catch {
val transaction = repository.getOnrampData(
userWalletId = userWalletId,
cryptoCurrency = cryptoCurrency,
quote = quote,
isDarkTheme = isDarkTheme,
)
transactionRepository.storeTransaction(transaction)
transaction.redirectUrl

View file

@ -24,6 +24,7 @@ interface OnrampRepository {
userWalletId: UserWalletId,
cryptoCurrency: CryptoCurrency,
quote: OnrampProviderWithQuote.Data,
isDarkTheme: Boolean,
): OnrampTransaction
// cache

View file

@ -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)
}

View file

@ -14,6 +14,7 @@ android {
dependencies {
/* Project - API */
api(projects.features.markets.api)
api(projects.features.onramp.api)
implementation(projects.core.navigation)
/* Domain */

View file

@ -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(

View file

@ -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 {

View file

@ -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 {

View file

@ -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<OnrampQuote>) {
quotes.filterIsInstance<OnrampQuote.Error>().forEach { errorState ->
quotes.filterIsInstance<OnrampQuote.AmountError>().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,
)
}
}
}

View file

@ -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()
}

View file

@ -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

View file

@ -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 {

View file

@ -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,

View file

@ -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<Context>) {
fun openUrl(url: String) {

View file

@ -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())
}
}

View file

@ -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,

View file

@ -21,6 +21,7 @@ internal data class TokenDetailsState(
val stakingBlocksState: StakingBlockUM?,
val notifications: ImmutableList<TokenDetailsNotification>,
val pendingTxs: PersistentList<TransactionState>,
val expressTxsToDisplay: PersistentList<ExpressTransactionStateUM>,
val expressTxs: PersistentList<ExpressTransactionStateUM>,
val txHistoryState: TxHistoryState,
val dialogConfig: TokenDetailsDialogConfig?,

View file

@ -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),

View file

@ -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,

View file

@ -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() },
)

View file

@ -178,7 +178,7 @@ internal fun TokenDetailsScreen(state: TokenDetailsState, tokenMarketBlockCompon
}
expressTransactionsItems(
expressTxs = state.expressTxs,
expressTxs = state.expressTxsToDisplay,
modifier = itemModifier,
)

View file

@ -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)
}

View file

@ -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) }
},

View file

@ -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) {

View file

@ -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"