Updated on 2026-08-14
This commit is contained in:
commit
acf110dbcc
30 changed files with 171 additions and 63 deletions
|
|
@ -12,6 +12,7 @@ internal interface ConfirmResidencyComponent : ComposableBottomSheetComponent {
|
|||
val userWalletId: UserWalletId,
|
||||
val cryptoCurrency: CryptoCurrency,
|
||||
val country: OnrampCountry,
|
||||
val launchSepa: Boolean,
|
||||
val onDismiss: () -> Unit,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -9,12 +9,14 @@ import com.tangem.core.decompose.model.ParamsContainer
|
|||
import com.tangem.core.decompose.navigation.Router
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.domain.onramp.OnrampSaveDefaultCountryUseCase
|
||||
import com.tangem.domain.onramp.OnrampSaveDefaultCurrencyUseCase
|
||||
import com.tangem.domain.onramp.analytics.OnrampAnalyticsEvent
|
||||
import com.tangem.domain.onramp.model.OnrampCountry
|
||||
import com.tangem.features.onramp.confirmresidency.ConfirmResidencyComponent
|
||||
import com.tangem.features.onramp.confirmresidency.entity.ConfirmResidencyBottomSheetConfig
|
||||
import com.tangem.features.onramp.confirmresidency.entity.ConfirmResidencyUM
|
||||
import com.tangem.features.onramp.impl.R
|
||||
import com.tangem.features.onramp.utils.model.EUR_CURRENCY
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
@ -26,6 +28,7 @@ internal class ConfirmResidencyModel @Inject constructor(
|
|||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
private val router: Router,
|
||||
private val saveDefaultCountryUseCase: OnrampSaveDefaultCountryUseCase,
|
||||
private val onrampSaveDefaultCurrencyUseCase: OnrampSaveDefaultCurrencyUseCase,
|
||||
paramsContainer: ParamsContainer,
|
||||
) : Model() {
|
||||
|
||||
|
|
@ -54,6 +57,10 @@ internal class ConfirmResidencyModel @Inject constructor(
|
|||
analyticsEventHandler.send(OnrampAnalyticsEvent.OnResidenceConfirm(country.name))
|
||||
modelScope.launch {
|
||||
saveDefaultCountryUseCase.invoke(country)
|
||||
if (params.launchSepa) {
|
||||
onrampSaveDefaultCurrencyUseCase.invoke(EUR_CURRENCY)
|
||||
}
|
||||
|
||||
params.onDismiss()
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ internal class DefaultOnrampMainComponent @AssistedInject constructor(
|
|||
userWalletId = params.userWalletId,
|
||||
cryptoCurrency = params.cryptoCurrency,
|
||||
country = config.country,
|
||||
launchSepa = params.launchSepa,
|
||||
onDismiss = {
|
||||
model.bottomSheetNavigation.dismiss()
|
||||
model.handleOnrampAvailable()
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ package com.tangem.features.onramp.main.entity
|
|||
import com.tangem.domain.onramp.model.OnrampProviderWithQuote
|
||||
|
||||
interface OnrampIntents {
|
||||
fun onAmountValueChanged(value: String)
|
||||
fun onAmountValueChanged(value: String, isValuePasted: Boolean)
|
||||
fun openSettings()
|
||||
fun openCurrenciesList()
|
||||
fun onBuyClick(quote: OnrampProviderWithQuote.Data)
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
package com.tangem.features.onramp.main.entity
|
||||
|
||||
import com.tangem.domain.onramp.model.OnrampAmount
|
||||
import com.tangem.domain.onramp.model.OnrampPaymentMethod
|
||||
|
||||
data class OnrampLastUpdate(
|
||||
val lastAmount: OnrampAmount,
|
||||
val lastCountryString: String,
|
||||
val fromAmount: OnrampAmount,
|
||||
val countryCode: String,
|
||||
val paymentMethod: OnrampPaymentMethod,
|
||||
)
|
||||
|
|
@ -122,7 +122,7 @@ internal class OnrampStateFactory(
|
|||
amountFieldModel = AmountFieldModel(
|
||||
value = "",
|
||||
fiatValue = "",
|
||||
onValueChange = onrampIntents::onAmountValueChanged,
|
||||
onValueChange = { onrampIntents.onAmountValueChanged(value = it, isValuePasted = false) },
|
||||
keyboardOptions = KeyboardOptions(
|
||||
imeAction = ImeAction.None,
|
||||
keyboardType = KeyboardType.Number,
|
||||
|
|
|
|||
|
|
@ -15,9 +15,12 @@ import java.math.BigDecimal
|
|||
|
||||
internal class OnrampAmountFieldChangeConverter(
|
||||
private val currentStateProvider: Provider<OnrampMainComponentUM>,
|
||||
) : Converter<String, OnrampMainComponentUM> {
|
||||
) : Converter<OnrampAmountFieldChangeConverter.Input, OnrampMainComponentUM> {
|
||||
|
||||
override fun convert(input: Input): OnrampMainComponentUM {
|
||||
val value = input.value
|
||||
val isValuePasted = input.isValuePasted
|
||||
|
||||
override fun convert(value: String): OnrampMainComponentUM {
|
||||
val state = currentStateProvider()
|
||||
if (state !is OnrampMainComponentUM.Content) return state
|
||||
|
||||
|
|
@ -34,6 +37,7 @@ internal class OnrampAmountFieldChangeConverter(
|
|||
imeAction = if (isDoneActionEnabled) ImeAction.Done else ImeAction.None,
|
||||
keyboardType = KeyboardType.Number,
|
||||
),
|
||||
isValuePasted = isValuePasted,
|
||||
)
|
||||
|
||||
return state.copy(
|
||||
|
|
@ -66,4 +70,6 @@ internal class OnrampAmountFieldChangeConverter(
|
|||
providerBlockState = OnrampProviderBlockUM.Empty,
|
||||
)
|
||||
}
|
||||
|
||||
data class Input(val value: String, val isValuePasted: Boolean)
|
||||
}
|
||||
|
|
@ -33,7 +33,8 @@ internal class OnrampAmountStateFactory(
|
|||
currentStateProvider = currentStateProvider,
|
||||
)
|
||||
|
||||
fun getOnAmountValueChange(value: String) = onrampAmountFieldChangeConverter.convert(value)
|
||||
fun getOnAmountValueChange(value: String, isValuePasted: Boolean) =
|
||||
onrampAmountFieldChangeConverter.convert(OnrampAmountFieldChangeConverter.Input(value, isValuePasted))
|
||||
|
||||
fun getUpdatedCurrencyState(currency: OnrampCurrency): OnrampMainComponentUM {
|
||||
val currentState = currentStateProvider()
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ import com.tangem.features.onramp.main.entity.factory.OnrampStateFactory
|
|||
import com.tangem.features.onramp.main.entity.factory.OnrampStateFactory.Companion.PREDEFINED_SEPA_AMOUNT
|
||||
import com.tangem.features.onramp.main.entity.factory.amount.OnrampAmountStateFactory
|
||||
import com.tangem.features.onramp.providers.entity.SelectProviderResult
|
||||
import com.tangem.features.onramp.utils.model.EUR_CURRENCY
|
||||
import com.tangem.features.onramp.utils.sendOnrampErrorEvent
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
|
|
@ -68,7 +69,7 @@ internal class OnrampMainComponentModel @Inject constructor(
|
|||
|
||||
private val params: OnrampMainComponent.Params = paramsContainer.require()
|
||||
|
||||
private var isSepaLaunched = false
|
||||
private var shouldForceChooseSepa = params.launchSepa
|
||||
private var currencyToRestore: OnrampCurrency? = null
|
||||
|
||||
val userWallet = getWalletsUseCase.invokeSync().first { it.walletId == params.userWalletId }
|
||||
|
|
@ -132,6 +133,10 @@ internal class OnrampMainComponentModel @Inject constructor(
|
|||
|
||||
fun onProviderSelected(result: SelectProviderResult, isBestRate: Boolean) {
|
||||
_state.update { amountStateFactory.getAmountSecondaryUpdatedState(result, isBestRate) }
|
||||
|
||||
if (result.paymentMethod.id != SEPA_METHOD_ID) {
|
||||
shouldForceChooseSepa = false
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkResidenceCountry() {
|
||||
|
|
@ -171,7 +176,7 @@ internal class OnrampMainComponentModel @Inject constructor(
|
|||
updatePairsAndQuotes()
|
||||
|
||||
if (wasInitialLoading && params.launchSepa) {
|
||||
onAmountValueChanged(PREDEFINED_SEPA_AMOUNT)
|
||||
onAmountValueChanged(value = PREDEFINED_SEPA_AMOUNT, isValuePasted = true)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
|
@ -243,8 +248,8 @@ internal class OnrampMainComponentModel @Inject constructor(
|
|||
.launchIn(modelScope)
|
||||
}
|
||||
|
||||
override fun onAmountValueChanged(value: String) {
|
||||
_state.update { amountStateFactory.getOnAmountValueChange(value) }
|
||||
override fun onAmountValueChanged(value: String, isValuePasted: Boolean) {
|
||||
_state.update { amountStateFactory.getOnAmountValueChange(value, isValuePasted) }
|
||||
modelScope.launch { amountInputManager.update(value) }
|
||||
}
|
||||
|
||||
|
|
@ -341,9 +346,7 @@ internal class OnrampMainComponentModel @Inject constructor(
|
|||
private fun selectOrUpdateQuote(quotes: List<OnrampQuote>): OnrampQuote? {
|
||||
val quoteToCheck = quotes.firstOrNull { it !is OnrampQuote.Error }
|
||||
|
||||
val sepaQuote = if (params.launchSepa && !isSepaLaunched) {
|
||||
isSepaLaunched = true
|
||||
|
||||
val bestSepaQuote = if (params.launchSepa && shouldForceChooseSepa) {
|
||||
quotes.filterIsInstance<OnrampQuote.Data>()
|
||||
.filter { it.paymentMethod.id == SEPA_METHOD_ID }
|
||||
.maxByOrNull { it.toAmount.value }
|
||||
|
|
@ -352,7 +355,7 @@ internal class OnrampMainComponentModel @Inject constructor(
|
|||
}
|
||||
|
||||
// Check if amount, country or currency has changed
|
||||
val newQuote = sepaQuote ?: if (checkLastInputState(quoteToCheck)) {
|
||||
val newQuote = bestSepaQuote ?: if (isAmountOrCountryChanged(quoteToCheck)) {
|
||||
quoteToCheck
|
||||
} else {
|
||||
val state = state.value as? OnrampMainComponentUM.Content
|
||||
|
|
@ -371,7 +374,9 @@ internal class OnrampMainComponentModel @Inject constructor(
|
|||
lastSelectedQuote
|
||||
}
|
||||
}
|
||||
newQuote?.let { updateProvider(newQuote, quotes) }
|
||||
if (newQuote != null) {
|
||||
updateProvider(newQuote, quotes)
|
||||
}
|
||||
|
||||
return newQuote
|
||||
}
|
||||
|
|
@ -380,8 +385,13 @@ internal class OnrampMainComponentModel @Inject constructor(
|
|||
lastUpdateState.value = OnrampLastUpdate(
|
||||
quote.fromAmount,
|
||||
quote.countryCode,
|
||||
quote.paymentMethod,
|
||||
)
|
||||
|
||||
if (quote.paymentMethod.id != SEPA_METHOD_ID) {
|
||||
shouldForceChooseSepa = false
|
||||
}
|
||||
|
||||
_state.update {
|
||||
amountStateFactory.getUpdatedProviderState(selectedQuote = quote, quotes = quotes)
|
||||
}
|
||||
|
|
@ -447,22 +457,14 @@ internal class OnrampMainComponentModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun checkLastInputState(quote: OnrampQuote?): Boolean {
|
||||
return lastUpdateState.value?.lastAmount != quote?.fromAmount ||
|
||||
lastUpdateState.value?.lastCountryString != quote?.countryCode
|
||||
private fun isAmountOrCountryChanged(quote: OnrampQuote?): Boolean {
|
||||
return lastUpdateState.value?.fromAmount != quote?.fromAmount ||
|
||||
lastUpdateState.value?.countryCode != quote?.countryCode
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val UPDATE_DELAY = 10_000L
|
||||
|
||||
const val SEPA_METHOD_ID = "sepa"
|
||||
|
||||
val EUR_CURRENCY = OnrampCurrency(
|
||||
code = "EUR",
|
||||
name = "Euro",
|
||||
unit = "€",
|
||||
precision = 2,
|
||||
image = "https://s3.eu-central-1.amazonaws.com/tangem.api/express/Currencies/EUR.png",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -58,6 +58,7 @@ internal class DefaultOnrampV2MainComponent @AssistedInject constructor(
|
|||
userWalletId = params.userWalletId,
|
||||
cryptoCurrency = params.cryptoCurrency,
|
||||
country = config.country,
|
||||
launchSepa = false,
|
||||
onDismiss = { model.bottomSheetNavigation.dismiss() },
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -348,8 +348,9 @@ internal class OnrampV2MainComponentModel @Inject constructor(
|
|||
|
||||
private fun updateProvider(quote: OnrampQuote) {
|
||||
lastUpdateState.value = OnrampLastUpdate(
|
||||
quote.fromAmount,
|
||||
quote.countryCode,
|
||||
fromAmount = quote.fromAmount,
|
||||
countryCode = quote.countryCode,
|
||||
paymentMethod = quote.paymentMethod,
|
||||
)
|
||||
|
||||
_state.update {
|
||||
|
|
@ -378,8 +379,8 @@ internal class OnrampV2MainComponentModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun checkLastInputState(quote: OnrampQuote?): Boolean {
|
||||
return lastUpdateState.value?.lastAmount != quote?.fromAmount ||
|
||||
lastUpdateState.value?.lastCountryString != quote?.countryCode
|
||||
return lastUpdateState.value?.fromAmount != quote?.fromAmount ||
|
||||
lastUpdateState.value?.countryCode != quote?.countryCode
|
||||
}
|
||||
|
||||
private fun sendScreenOpenAnalytics() {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
package com.tangem.features.onramp.utils.model
|
||||
|
||||
import com.tangem.domain.onramp.model.OnrampCurrency
|
||||
|
||||
internal val EUR_CURRENCY = OnrampCurrency(
|
||||
code = "EUR",
|
||||
name = "Euro",
|
||||
unit = "€",
|
||||
precision = 2,
|
||||
image = "https://s3.eu-central-1.amazonaws.com/tangem.api/express/Currencies/EUR.png",
|
||||
)
|
||||
|
|
@ -510,7 +510,7 @@ internal class SendConfirmModel @Inject constructor(
|
|||
val txUrl = getExplorerTransactionUrlUseCase(
|
||||
txHash = txData.hash.orEmpty(),
|
||||
networkId = cryptoCurrency.network.id,
|
||||
).getOrElse { "" }
|
||||
).getOrNull().orEmpty()
|
||||
_uiState.update(SendConfirmSentStateTransformer(txData, txUrl))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ internal class SendConfirmSuccessModel @Inject constructor(
|
|||
},
|
||||
),
|
||||
prevButton = null,
|
||||
secondaryPairButtonsUM = NavigationButton(
|
||||
secondaryPairButtonsUM = (NavigationButton(
|
||||
textReference = resourceReference(R.string.common_explore),
|
||||
iconRes = R.drawable.ic_web_24,
|
||||
onClick = ::onExploreClick,
|
||||
|
|
@ -84,7 +84,7 @@ internal class SendConfirmSuccessModel @Inject constructor(
|
|||
textReference = resourceReference(R.string.common_share),
|
||||
iconRes = R.drawable.ic_share_24,
|
||||
onClick = ::onShareClick,
|
||||
),
|
||||
)).takeIf { params.txUrl.isNotEmpty() },
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -498,7 +498,7 @@ internal class NFTSendConfirmModel @Inject constructor(
|
|||
iconRes = R.drawable.ic_share_24,
|
||||
onClick = ::onShareClick,
|
||||
)
|
||||
).takeIf { confirmUM is ConfirmUM.Success },
|
||||
).takeUnless { (confirmUM as? ConfirmUM.Success)?.txUrl.isNullOrBlank() },
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ internal class SendWithSwapSuccessModel @Inject constructor(
|
|||
onClick = appRouter::pop,
|
||||
),
|
||||
prevButton = null,
|
||||
secondaryPairButtonsUM = NavigationButton(
|
||||
secondaryPairButtonsUM = (NavigationButton(
|
||||
textReference = resourceReference(R.string.common_explore),
|
||||
iconRes = R.drawable.ic_web_24,
|
||||
onClick = ::onExploreClick,
|
||||
|
|
@ -60,7 +60,7 @@ internal class SendWithSwapSuccessModel @Inject constructor(
|
|||
textReference = resourceReference(R.string.common_share),
|
||||
iconRes = R.drawable.ic_share_24,
|
||||
onClick = ::onShareClick,
|
||||
),
|
||||
)).takeUnless { confirmUM?.txUrl.isNullOrBlank() },
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -410,7 +410,7 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
rateType = RateType.FLOAT,
|
||||
)
|
||||
|
||||
return if (isBalanceWithoutFeeEnough) {
|
||||
return if (isBalanceWithoutFeeEnough && maybeQuotes.isRight()) {
|
||||
provider to loadDexSwapData(
|
||||
provider = provider,
|
||||
networkId = networkId,
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ import com.tangem.domain.transaction.error.SendTransactionError.UserCancelledErr
|
|||
import com.tangem.domain.transaction.usecase.GetFeeUseCase
|
||||
import com.tangem.domain.walletconnect.WcAnalyticEvents
|
||||
import com.tangem.domain.walletconnect.WcAnalyticEvents.SignatureRequestReceived.EmulationStatus
|
||||
import com.tangem.domain.walletconnect.WcAnalyticEvents.SolanaLargeTransaction
|
||||
import com.tangem.domain.walletconnect.WcRequestUseCaseFactory
|
||||
import com.tangem.domain.walletconnect.model.WcRequestError
|
||||
import com.tangem.domain.walletconnect.model.WcRequestError.Companion.message
|
||||
|
|
@ -143,6 +144,7 @@ internal class WcSendTransactionModel @Inject constructor(
|
|||
wcApproval = useCase as? WcApproval
|
||||
sign = {
|
||||
if (isMultipleSignRequired(useCase)) {
|
||||
analytics.send(SolanaLargeTransaction(useCase.rawSdkRequest.dAppMetaData.name))
|
||||
openMultipleTransaction(useCase)
|
||||
} else {
|
||||
useCase.sign()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue