Updated on 2026-08-14
This commit is contained in:
commit
d4d197dfe0
34 changed files with 199 additions and 75 deletions
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
|
|
@ -23,7 +23,7 @@ sealed interface OnrampProviderWithQuote {
|
|||
|
||||
data class Error(
|
||||
override val provider: OnrampProvider,
|
||||
val quoteError: OnrampQuote.Error,
|
||||
val quoteError: OnrampQuote.AmountError,
|
||||
) : Unavailable
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
|
|
@ -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 }
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ interface OnrampRepository {
|
|||
userWalletId: UserWalletId,
|
||||
cryptoCurrency: CryptoCurrency,
|
||||
quote: OnrampProviderWithQuote.Data,
|
||||
isDarkTheme: Boolean,
|
||||
): OnrampTransaction
|
||||
|
||||
// cache
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ android {
|
|||
dependencies {
|
||||
/* Project - API */
|
||||
api(projects.features.markets.api)
|
||||
api(projects.features.onramp.api)
|
||||
implementation(projects.core.navigation)
|
||||
|
||||
/* Domain */
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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?,
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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() },
|
||||
)
|
||||
|
|
|
|||
|
|
@ -178,7 +178,7 @@ internal fun TokenDetailsScreen(state: TokenDetailsState, tokenMarketBlockCompon
|
|||
}
|
||||
|
||||
expressTransactionsItems(
|
||||
expressTxs = state.expressTxs,
|
||||
expressTxs = state.expressTxsToDisplay,
|
||||
modifier = itemModifier,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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) }
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue