Updated on 2026-08-14
This commit is contained in:
parent
ed0b07bc82
commit
f4422dd3fb
25 changed files with 274 additions and 115 deletions
|
|
@ -200,12 +200,10 @@ sealed class AnalyticsParam {
|
|||
const val INPUT = "Input"
|
||||
const val COUNT = "Count"
|
||||
const val DERIVATION = "Derivation"
|
||||
|
||||
// region swap
|
||||
const val TOKEN_CATEGORY = "Token"
|
||||
const val STATUS = "Status"
|
||||
const val PROVIDER = "Provider"
|
||||
const val PLACE = "Place"
|
||||
//
|
||||
const val RESIDENCE = "Residence"
|
||||
const val PAYMENT_METHOD = "Payment Method"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,20 +1,17 @@
|
|||
package com.tangem.data.onramp
|
||||
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.data.onramp.converters.error.OnrampErrorConverter
|
||||
import com.tangem.datasource.api.common.response.ApiResponseError
|
||||
import com.tangem.domain.onramp.analytics.OnrampAnalyticsEvent
|
||||
import com.tangem.domain.onramp.model.error.OnrampError
|
||||
import com.tangem.domain.onramp.model.error.OnrampRedirectError
|
||||
import com.tangem.domain.onramp.repositories.OnrampErrorResolver
|
||||
|
||||
internal class DefaultOnrampErrorResolver(
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
private val onrampErrorConverter: OnrampErrorConverter,
|
||||
) : OnrampErrorResolver {
|
||||
|
||||
override fun resolve(throwable: Throwable): OnrampError {
|
||||
val error = when (throwable) {
|
||||
return when (throwable) {
|
||||
is ApiResponseError.HttpException -> {
|
||||
onrampErrorConverter.convert(throwable.errorBody.orEmpty())
|
||||
}
|
||||
|
|
@ -24,20 +21,5 @@ internal class DefaultOnrampErrorResolver(
|
|||
OnrampError.DomainError(throwable.message)
|
||||
}
|
||||
}
|
||||
|
||||
when (error) {
|
||||
is OnrampError.AmountError.TooBigError -> analyticsEventHandler.send(OnrampAnalyticsEvent.MaxAmountError)
|
||||
is OnrampError.AmountError.TooSmallError -> analyticsEventHandler.send(OnrampAnalyticsEvent.MinAmountError)
|
||||
is OnrampError.DataError -> analyticsEventHandler.send(
|
||||
OnrampAnalyticsEvent.Errors(
|
||||
tokenSymbol = "tokenSymbol",
|
||||
providerName = "providerName",
|
||||
errorCode = error.code,
|
||||
),
|
||||
)
|
||||
else -> { /* no-op */
|
||||
}
|
||||
}
|
||||
return error
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
package com.tangem.data.onramp.di
|
||||
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.data.onramp.DefaultOnrampErrorResolver
|
||||
import com.tangem.data.onramp.DefaultOnrampRepository
|
||||
import com.tangem.data.onramp.DefaultOnrampTransactionRepository
|
||||
|
|
@ -72,13 +71,9 @@ internal object OnrampDataModule {
|
|||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideOnrampErrorResolver(
|
||||
@NetworkMoshi moshi: Moshi,
|
||||
analyticsEventHandler: AnalyticsEventHandler,
|
||||
): OnrampErrorResolver {
|
||||
fun provideOnrampErrorResolver(@NetworkMoshi moshi: Moshi): OnrampErrorResolver {
|
||||
val jsonAdapter = moshi.adapter(ExpressErrorResponse::class.java)
|
||||
return DefaultOnrampErrorResolver(
|
||||
analyticsEventHandler,
|
||||
OnrampErrorConverter(jsonAdapter),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ sealed class OnrampError {
|
|||
|
||||
sealed class AmountError : OnrampError() {
|
||||
abstract val requiredAmount: BigDecimal
|
||||
|
||||
data class TooSmallError(
|
||||
override val requiredAmount: BigDecimal,
|
||||
) : AmountError()
|
||||
|
|
@ -25,5 +26,7 @@ sealed class OnrampError {
|
|||
val description: String?,
|
||||
) : OnrampError()
|
||||
|
||||
data class DomainError(val error: String?) : OnrampError()
|
||||
data class DomainError(
|
||||
val description: String?,
|
||||
) : OnrampError()
|
||||
}
|
||||
|
|
@ -1,6 +1,14 @@
|
|||
package com.tangem.domain.onramp.analytics
|
||||
|
||||
import com.tangem.core.analytics.models.AnalyticsEvent
|
||||
import com.tangem.core.analytics.models.AnalyticsParam.Key.CURRENCY
|
||||
import com.tangem.core.analytics.models.AnalyticsParam.Key.ERROR_CODE
|
||||
import com.tangem.core.analytics.models.AnalyticsParam.Key.ERROR_DESCRIPTION
|
||||
import com.tangem.core.analytics.models.AnalyticsParam.Key.PAYMENT_METHOD
|
||||
import com.tangem.core.analytics.models.AnalyticsParam.Key.PROVIDER
|
||||
import com.tangem.core.analytics.models.AnalyticsParam.Key.RESIDENCE
|
||||
import com.tangem.core.analytics.models.AnalyticsParam.Key.SOURCE
|
||||
import com.tangem.core.analytics.models.AnalyticsParam.Key.TOKEN_PARAM
|
||||
import com.tangem.domain.onramp.model.OnrampSource
|
||||
|
||||
sealed class OnrampAnalyticsEvent(
|
||||
|
|
@ -18,8 +26,8 @@ sealed class OnrampAnalyticsEvent(
|
|||
) : OnrampAnalyticsEvent(
|
||||
event = "Buy Screen Opened",
|
||||
params = mapOf(
|
||||
"Source" to source.analyticsName,
|
||||
"Token" to tokenSymbol,
|
||||
SOURCE to source.analyticsName,
|
||||
TOKEN_PARAM to tokenSymbol,
|
||||
),
|
||||
)
|
||||
|
||||
|
|
@ -29,7 +37,7 @@ sealed class OnrampAnalyticsEvent(
|
|||
private val currency: String,
|
||||
) : OnrampAnalyticsEvent(
|
||||
event = "Currency Chosen",
|
||||
params = mapOf("Currency" to currency),
|
||||
params = mapOf(CURRENCY to currency),
|
||||
)
|
||||
|
||||
data object CloseOnramp : OnrampAnalyticsEvent(event = "Button - Close")
|
||||
|
|
@ -42,14 +50,14 @@ sealed class OnrampAnalyticsEvent(
|
|||
private val residence: String,
|
||||
) : OnrampAnalyticsEvent(
|
||||
event = "Residence Chosen",
|
||||
params = mapOf("Residence" to residence),
|
||||
params = mapOf(RESIDENCE to residence),
|
||||
)
|
||||
|
||||
data class ResidenceConfirmScreenOpened(
|
||||
private val residence: String,
|
||||
) : OnrampAnalyticsEvent(
|
||||
event = "Residence Confirm Screen",
|
||||
params = mapOf("Residence" to residence),
|
||||
params = mapOf(RESIDENCE to residence),
|
||||
)
|
||||
|
||||
data object OnResidenceChange : OnrampAnalyticsEvent(event = "Button - Change")
|
||||
|
|
@ -58,7 +66,7 @@ sealed class OnrampAnalyticsEvent(
|
|||
private val residence: String,
|
||||
) : OnrampAnalyticsEvent(
|
||||
event = "Button - Confirm",
|
||||
params = mapOf("Residence" to residence),
|
||||
params = mapOf(RESIDENCE to residence),
|
||||
)
|
||||
|
||||
data object ProvidersScreenOpened : OnrampAnalyticsEvent(event = "Providers Screen Opened")
|
||||
|
|
@ -70,9 +78,9 @@ sealed class OnrampAnalyticsEvent(
|
|||
) : OnrampAnalyticsEvent(
|
||||
event = "Provider Calculated",
|
||||
params = mapOf(
|
||||
"Provider" to providerName,
|
||||
"Token" to tokenSymbol,
|
||||
"Payment Method" to paymentMethod,
|
||||
PROVIDER to providerName,
|
||||
TOKEN_PARAM to tokenSymbol,
|
||||
PAYMENT_METHOD to paymentMethod,
|
||||
),
|
||||
)
|
||||
|
||||
|
|
@ -83,7 +91,7 @@ sealed class OnrampAnalyticsEvent(
|
|||
) : OnrampAnalyticsEvent(
|
||||
event = "Method Chosen",
|
||||
params = mapOf(
|
||||
"Payment Method" to paymentMethod,
|
||||
PAYMENT_METHOD to paymentMethod,
|
||||
),
|
||||
)
|
||||
|
||||
|
|
@ -93,8 +101,8 @@ sealed class OnrampAnalyticsEvent(
|
|||
) : OnrampAnalyticsEvent(
|
||||
event = "Provider Chosen",
|
||||
params = mapOf(
|
||||
"Provider" to providerName,
|
||||
"Token" to tokenSymbol,
|
||||
PROVIDER to providerName,
|
||||
TOKEN_PARAM to tokenSymbol,
|
||||
),
|
||||
)
|
||||
|
||||
|
|
@ -105,9 +113,9 @@ sealed class OnrampAnalyticsEvent(
|
|||
) : OnrampAnalyticsEvent(
|
||||
event = "Button - Buy",
|
||||
params = mapOf(
|
||||
"Provider" to providerName,
|
||||
"Currency" to currency,
|
||||
"Token" to tokenSymbol,
|
||||
PROVIDER to providerName,
|
||||
CURRENCY to currency,
|
||||
TOKEN_PARAM to tokenSymbol,
|
||||
),
|
||||
)
|
||||
|
||||
|
|
@ -120,11 +128,11 @@ sealed class OnrampAnalyticsEvent(
|
|||
) : OnrampAnalyticsEvent(
|
||||
event = "Buying In Progress Screen Opened",
|
||||
params = mapOf(
|
||||
"Token" to tokenSymbol,
|
||||
"Provider" to providerName,
|
||||
"Residence" to residence,
|
||||
"Currency" to currency,
|
||||
"Method" to paymentMethod,
|
||||
TOKEN_PARAM to tokenSymbol,
|
||||
PROVIDER to providerName,
|
||||
RESIDENCE to residence,
|
||||
CURRENCY to currency,
|
||||
PAYMENT_METHOD to paymentMethod,
|
||||
),
|
||||
)
|
||||
|
||||
|
|
@ -133,14 +141,25 @@ sealed class OnrampAnalyticsEvent(
|
|||
|
||||
data class Errors(
|
||||
private val tokenSymbol: String,
|
||||
private val providerName: String,
|
||||
private val errorCode: String,
|
||||
private val providerName: String?,
|
||||
) : OnrampAnalyticsEvent(
|
||||
event = "Errors",
|
||||
params = buildMap {
|
||||
put(TOKEN_PARAM, tokenSymbol)
|
||||
providerName?.let { put(PROVIDER, providerName) }
|
||||
put(ERROR_CODE, errorCode)
|
||||
},
|
||||
)
|
||||
|
||||
data class AppErrors(
|
||||
private val tokenSymbol: String,
|
||||
private val errorDescription: String,
|
||||
) : OnrampAnalyticsEvent(
|
||||
event = "App Errors",
|
||||
params = mapOf(
|
||||
"Token" to tokenSymbol,
|
||||
"Provider" to providerName,
|
||||
"Error Code" to errorCode,
|
||||
TOKEN_PARAM to tokenSymbol,
|
||||
ERROR_DESCRIPTION to errorDescription,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -4,13 +4,12 @@ import com.tangem.core.analytics.models.AnalyticsEvent
|
|||
import com.tangem.core.analytics.models.AnalyticsParam.Key.PLACE
|
||||
import com.tangem.core.analytics.models.AnalyticsParam.Key.PROVIDER
|
||||
import com.tangem.core.analytics.models.AnalyticsParam.Key.STATUS
|
||||
import com.tangem.core.analytics.models.AnalyticsParam.Key.TOKEN_CATEGORY
|
||||
import com.tangem.core.analytics.models.AnalyticsParam.Key.TOKEN_PARAM
|
||||
|
||||
class TokenExchangeAnalyticsEvent(
|
||||
event: String,
|
||||
params: Map<String, String> = mapOf(),
|
||||
) : AnalyticsEvent(TOKEN_CATEGORY, event, params, null) {
|
||||
) : AnalyticsEvent("Token", event, params, null) {
|
||||
|
||||
class CexTxStatusOpened(token: String) : TokenScreenAnalyticsEvent(
|
||||
event = "Swap Status Opened",
|
||||
|
|
|
|||
|
|
@ -3,10 +3,15 @@ package com.tangem.features.onramp.confirmresidency
|
|||
import com.tangem.core.decompose.factory.ComponentFactory
|
||||
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
|
||||
import com.tangem.domain.onramp.model.OnrampCountry
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
|
||||
internal interface ConfirmResidencyComponent : ComposableBottomSheetComponent {
|
||||
|
||||
data class Params(val country: OnrampCountry, val onDismiss: (OnrampCountry) -> Unit)
|
||||
data class Params(
|
||||
val cryptoCurrency: CryptoCurrency,
|
||||
val country: OnrampCountry,
|
||||
val onDismiss: (OnrampCountry) -> Unit,
|
||||
)
|
||||
|
||||
interface Factory : ComponentFactory<Params, ConfirmResidencyComponent>
|
||||
}
|
||||
|
|
@ -14,10 +14,10 @@ import com.tangem.core.decompose.model.getOrCreateModel
|
|||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
|
||||
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
|
||||
import com.tangem.features.onramp.confirmresidency.entity.ConfirmResidencyBottomSheetConfig
|
||||
import com.tangem.features.onramp.confirmresidency.model.ConfirmResidencyModel
|
||||
import com.tangem.features.onramp.confirmresidency.ui.ConfirmResidencyBottomSheet
|
||||
import com.tangem.features.onramp.confirmresidency.ui.ConfirmResidencyBottomSheetContent
|
||||
import com.tangem.features.onramp.confirmresidency.entity.ConfirmResidencyBottomSheetConfig
|
||||
import com.tangem.features.onramp.selectcountry.SelectCountryComponent
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
|
|
@ -68,7 +68,10 @@ internal class DefaultConfirmResidencyComponent @AssistedInject constructor(
|
|||
): ComposableBottomSheetComponent = when (config) {
|
||||
ConfirmResidencyBottomSheetConfig.SelectCountry -> selectCountryComponentFactory.create(
|
||||
context = childByContext(componentContext),
|
||||
params = SelectCountryComponent.Params(onDismiss = model.bottomSheetNavigation::dismiss),
|
||||
params = SelectCountryComponent.Params(
|
||||
cryptoCurrency = params.cryptoCurrency,
|
||||
onDismiss = model.bottomSheetNavigation::dismiss,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ 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.sendOnrampErrorEvent
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
|
|
@ -51,6 +52,9 @@ internal class ConfirmResidencyModel @Inject constructor(
|
|||
analyticsEventHandler.send(OnrampAnalyticsEvent.ResidenceConfirmScreenOpened(params.country.name))
|
||||
getOnrampCountryUseCase.invoke()
|
||||
.onEach { maybeCountry ->
|
||||
maybeCountry.onLeft {
|
||||
analyticsEventHandler.sendOnrampErrorEvent(it, params.cryptoCurrency.symbol)
|
||||
}
|
||||
val country = maybeCountry.getOrNull()
|
||||
if (country != null) {
|
||||
params.onDismiss(country)
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ internal class DefaultOnrampMainComponent @AssistedInject constructor(
|
|||
is OnrampMainBottomSheetConfig.ConfirmResidency -> confirmResidencyComponentFactory.create(
|
||||
context = childByContext(componentContext),
|
||||
params = ConfirmResidencyComponent.Params(
|
||||
cryptoCurrency = params.cryptoCurrency,
|
||||
country = config.country,
|
||||
onDismiss = {
|
||||
model.bottomSheetNavigation.dismiss()
|
||||
|
|
@ -63,7 +64,10 @@ internal class DefaultOnrampMainComponent @AssistedInject constructor(
|
|||
)
|
||||
is OnrampMainBottomSheetConfig.CurrenciesList -> selectCurrencyComponentFactory.create(
|
||||
context = childByContext(componentContext),
|
||||
params = SelectCurrencyComponent.Params(model.bottomSheetNavigation::dismiss),
|
||||
params = SelectCurrencyComponent.Params(
|
||||
cryptoCurrency = params.cryptoCurrency,
|
||||
onDismiss = model.bottomSheetNavigation::dismiss,
|
||||
),
|
||||
)
|
||||
is OnrampMainBottomSheetConfig.ProvidersList -> selectProviderComponentFactory.create(
|
||||
context = childByContext(componentContext),
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
package com.tangem.features.onramp.main.entity.factory.amount
|
||||
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.core.ui.format.bigdecimal.crypto
|
||||
import com.tangem.core.ui.format.bigdecimal.fiat
|
||||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
import com.tangem.domain.onramp.analytics.OnrampAnalyticsEvent
|
||||
import com.tangem.domain.onramp.model.OnrampCurrency
|
||||
import com.tangem.domain.onramp.model.OnrampProviderWithQuote
|
||||
import com.tangem.domain.onramp.model.OnrampQuote
|
||||
|
|
@ -18,6 +20,7 @@ import com.tangem.utils.Provider
|
|||
|
||||
internal class OnrampAmountStateFactory(
|
||||
private val currentStateProvider: Provider<OnrampMainComponentUM>,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
private val onrampIntents: OnrampIntents,
|
||||
) {
|
||||
|
||||
|
|
@ -171,8 +174,14 @@ internal class OnrampAmountStateFactory(
|
|||
}
|
||||
|
||||
val errorTextRes = when (error) {
|
||||
is OnrampError.AmountError.TooBigError -> R.string.onramp_max_amount_restriction
|
||||
is OnrampError.AmountError.TooSmallError -> R.string.onramp_min_amount_restriction
|
||||
is OnrampError.AmountError.TooBigError -> {
|
||||
analyticsEventHandler.send(OnrampAnalyticsEvent.MaxAmountError)
|
||||
R.string.onramp_max_amount_restriction
|
||||
}
|
||||
is OnrampError.AmountError.TooSmallError -> {
|
||||
analyticsEventHandler.send(OnrampAnalyticsEvent.MaxAmountError)
|
||||
R.string.onramp_min_amount_restriction
|
||||
}
|
||||
}
|
||||
|
||||
return OnrampAmountSecondaryFieldUM.Error(
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import com.tangem.domain.onramp.model.OnrampAvailability
|
|||
import com.tangem.domain.onramp.model.OnrampCurrency
|
||||
import com.tangem.domain.onramp.model.OnrampProviderWithQuote
|
||||
import com.tangem.domain.onramp.model.OnrampQuote
|
||||
import com.tangem.domain.onramp.model.error.OnrampError
|
||||
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
|
||||
import com.tangem.features.onramp.impl.R
|
||||
import com.tangem.features.onramp.main.OnrampMainComponent
|
||||
|
|
@ -32,6 +33,7 @@ import com.tangem.features.onramp.main.entity.factory.OnrampStateFactory
|
|||
import com.tangem.features.onramp.main.entity.factory.amount.OnrampAmountStateFactory
|
||||
import com.tangem.features.onramp.providers.entity.SelectProviderResult
|
||||
import com.tangem.features.onramp.utils.InputManager
|
||||
import com.tangem.features.onramp.utils.sendOnrampErrorEvent
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.coroutines.PeriodicTask
|
||||
|
|
@ -69,6 +71,7 @@ internal class OnrampMainComponentModel @Inject constructor(
|
|||
)
|
||||
private val amountStateFactory = OnrampAmountStateFactory(
|
||||
currentStateProvider = Provider { _state.value },
|
||||
analyticsEventHandler = analyticsEventHandler,
|
||||
onrampIntents = this,
|
||||
)
|
||||
private val selectedUserWallet = getWalletsUseCase.invokeSync().first { it.walletId == params.userWalletId }
|
||||
|
|
@ -111,9 +114,12 @@ internal class OnrampMainComponentModel @Inject constructor(
|
|||
|
||||
private fun checkResidenceCountry() {
|
||||
modelScope.launch {
|
||||
checkOnrampAvailabilityUseCase.invoke()
|
||||
checkOnrampAvailabilityUseCase()
|
||||
.onRight(::handleOnrampAvailability)
|
||||
.onLeft { Timber.e(it.toString()) }
|
||||
.onLeft { error ->
|
||||
Timber.e(error.toString())
|
||||
sendOnrampErrorAnalytic(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -129,9 +135,14 @@ internal class OnrampMainComponentModel @Inject constructor(
|
|||
private fun subscribeToCurrencyUpdates() {
|
||||
getOnrampCurrencyUseCase.invoke()
|
||||
.onEach { maybeCurrency ->
|
||||
val currency = maybeCurrency.getOrNull() ?: return@onEach
|
||||
maybeCurrency.fold(
|
||||
ifLeft = ::sendOnrampErrorAnalytic,
|
||||
ifRight = { currency ->
|
||||
if (currency == null) return@onEach
|
||||
_state.update { amountStateFactory.getUpdatedCurrencyState(currency) }
|
||||
updatePairsAndQuotes()
|
||||
},
|
||||
)
|
||||
}
|
||||
.launchIn(modelScope)
|
||||
}
|
||||
|
|
@ -146,7 +157,7 @@ internal class OnrampMainComponentModel @Inject constructor(
|
|||
}
|
||||
|
||||
private suspend fun updatePairsAndQuotes() {
|
||||
fetchPairsUseCase.invoke(params.cryptoCurrency)
|
||||
fetchPairsUseCase.invoke(params.cryptoCurrency).onLeft(::sendOnrampErrorAnalytic)
|
||||
startLoadingQuotes()
|
||||
}
|
||||
|
||||
|
|
@ -165,7 +176,7 @@ internal class OnrampMainComponentModel @Inject constructor(
|
|||
fetchQuotesUseCase.invoke(
|
||||
amount = content.amountBlockState.amountFieldModel.fiatAmount,
|
||||
cryptoCurrency = params.cryptoCurrency,
|
||||
)
|
||||
).onLeft(::sendOnrampErrorAnalytic)
|
||||
}
|
||||
},
|
||||
onSuccess = {},
|
||||
|
|
@ -176,7 +187,12 @@ internal class OnrampMainComponentModel @Inject constructor(
|
|||
private fun subscribeToQuotesUpdate() {
|
||||
getOnrampQuotesUseCase.invoke()
|
||||
.onEach { maybeQuotes ->
|
||||
val quotes = maybeQuotes.getOrNull() ?: return@onEach
|
||||
maybeQuotes.fold(
|
||||
ifLeft = ::sendOnrampErrorAnalytic,
|
||||
ifRight = { quotes ->
|
||||
quotes.filterIsInstance<OnrampQuote.Error>().forEach { errorState ->
|
||||
sendOnrampErrorAnalytic(errorState.error)
|
||||
}
|
||||
val isNoQuotes = quotes.none { it is OnrampQuote.Data }
|
||||
val quote = quotes.firstOrNull() ?: return@onEach
|
||||
if (quote is OnrampQuote.Data && lastAmount.value != quote.fromAmount.value) {
|
||||
|
|
@ -190,6 +206,8 @@ internal class OnrampMainComponentModel @Inject constructor(
|
|||
)
|
||||
}
|
||||
_state.update { amountStateFactory.getAmountSecondaryUpdatedState(quote, isNoQuotes) }
|
||||
},
|
||||
)
|
||||
}
|
||||
.launchIn(modelScope)
|
||||
}
|
||||
|
|
@ -270,6 +288,16 @@ internal class OnrampMainComponentModel @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
private fun sendOnrampErrorAnalytic(error: OnrampError) {
|
||||
val content = state.value as? OnrampMainComponentUM.Content
|
||||
val providerContent = content?.providerBlockState as? OnrampProviderBlockUM.Content
|
||||
analyticsEventHandler.sendOnrampErrorEvent(
|
||||
error = error,
|
||||
tokenSymbol = params.cryptoCurrency.symbol,
|
||||
providerName = providerContent?.providerName,
|
||||
)
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val UPDATE_DELAY = 10_000L
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,11 +5,12 @@ import com.tangem.core.ui.extensions.TextReference
|
|||
internal sealed interface ProviderListItemUM {
|
||||
|
||||
val providerId: String
|
||||
val name: String
|
||||
|
||||
data class Available(
|
||||
override val providerId: String,
|
||||
override val name: String,
|
||||
val imageUrl: String,
|
||||
val name: String,
|
||||
val rate: String,
|
||||
val isBestRate: Boolean,
|
||||
val diffRate: TextReference,
|
||||
|
|
@ -19,16 +20,16 @@ internal sealed interface ProviderListItemUM {
|
|||
|
||||
data class AvailableWithError(
|
||||
override val providerId: String,
|
||||
override val name: String,
|
||||
val imageUrl: String,
|
||||
val name: String,
|
||||
val subtitle: TextReference,
|
||||
val onClick: () -> Unit,
|
||||
) : ProviderListItemUM
|
||||
|
||||
data class Unavailable(
|
||||
override val providerId: String,
|
||||
override val name: String,
|
||||
val imageUrl: String,
|
||||
val name: String,
|
||||
val subtitle: TextReference,
|
||||
) : ProviderListItemUM
|
||||
}
|
||||
|
|
@ -25,6 +25,7 @@ import com.tangem.features.onramp.impl.R
|
|||
import com.tangem.features.onramp.paymentmethod.entity.PaymentMethodUM
|
||||
import com.tangem.features.onramp.providers.SelectProviderComponent
|
||||
import com.tangem.features.onramp.providers.entity.*
|
||||
import com.tangem.features.onramp.utils.sendOnrampErrorEvent
|
||||
import com.tangem.utils.StringsSigns.MINUS
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
|
@ -60,7 +61,12 @@ internal class SelectProviderModel @Inject constructor(
|
|||
|
||||
private fun subscribeToPaymentMethodUpdates() {
|
||||
getOnrampSelectedPaymentMethodUseCase.invoke()
|
||||
.onEach { it.getOrNull()?.let(::getProviders) }
|
||||
.onEach { maybePaymentMethod ->
|
||||
maybePaymentMethod.fold(
|
||||
ifLeft = ::sendOnrampErrorEvent,
|
||||
ifRight = ::getProviders,
|
||||
)
|
||||
}
|
||||
.launchIn(modelScope)
|
||||
}
|
||||
|
||||
|
|
@ -76,7 +82,10 @@ internal class SelectProviderModel @Inject constructor(
|
|||
)
|
||||
}
|
||||
}
|
||||
.onLeft { Timber.e(it.toString()) }
|
||||
.onLeft { error ->
|
||||
sendOnrampErrorEvent(error)
|
||||
Timber.e(error.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -96,7 +105,13 @@ internal class SelectProviderModel @Inject constructor(
|
|||
private fun openPaymentMethods() {
|
||||
analyticsEventHandler.send(OnrampAnalyticsEvent.PaymentMethodsScreenOpened)
|
||||
modelScope.launch {
|
||||
val methods = getSelectedPaymentMethodsUseCase.invoke().getOrNull().orEmpty()
|
||||
val methods = getSelectedPaymentMethodsUseCase().fold(
|
||||
ifLeft = { error ->
|
||||
sendOnrampErrorEvent(error)
|
||||
emptySet()
|
||||
},
|
||||
ifRight = { it },
|
||||
)
|
||||
bottomSheetNavigation.activate(
|
||||
ProviderListBottomSheetConfig.PaymentMethods(
|
||||
selectedMethodId = _state.value.paymentMethod.id,
|
||||
|
|
@ -221,6 +236,17 @@ internal class SelectProviderModel @Inject constructor(
|
|||
params.onDismiss()
|
||||
}
|
||||
|
||||
private fun sendOnrampErrorEvent(error: OnrampError) {
|
||||
val selectedProvider = state.value.providers.firstOrNull {
|
||||
it.providerId == params.selectedProviderId
|
||||
}
|
||||
analyticsEventHandler.sendOnrampErrorEvent(
|
||||
error = error,
|
||||
tokenSymbol = params.cryptoCurrency.symbol,
|
||||
providerName = selectedProvider?.name,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorting providers by rule:
|
||||
*
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.features.onramp.redirect.model
|
||||
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.core.decompose.navigation.Router
|
||||
|
|
@ -20,16 +21,19 @@ import com.tangem.features.onramp.impl.R
|
|||
import com.tangem.features.onramp.redirect.OnrampRedirectComponent
|
||||
import com.tangem.features.onramp.redirect.entity.OnrampRedirectTopBarUM
|
||||
import com.tangem.features.onramp.redirect.entity.OnrampRedirectUM
|
||||
import com.tangem.features.onramp.utils.sendOnrampErrorEvent
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
internal class OnrampRedirectModel @Inject constructor(
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val urlOpener: UrlOpener,
|
||||
private val getOnrampRedirectUrlUseCase: GetOnrampRedirectUrlUseCase,
|
||||
private val messageSender: UiMessageSender,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
router: Router,
|
||||
paramsContainer: ParamsContainer,
|
||||
) : Model() {
|
||||
|
|
@ -79,7 +83,11 @@ internal class OnrampRedirectModel @Inject constructor(
|
|||
|
||||
private fun handleError(error: OnrampError) {
|
||||
Timber.e(error.toString())
|
||||
|
||||
analyticsEventHandler.sendOnrampErrorEvent(
|
||||
error = error,
|
||||
tokenSymbol = params.cryptoCurrency.symbol,
|
||||
providerName = params.onrampProviderWithQuote.provider.info.name,
|
||||
)
|
||||
val contentMessage = ContentMessage { onDismiss ->
|
||||
BasicDialog(
|
||||
message = stringResource(id = R.string.common_unknown_error),
|
||||
|
|
|
|||
|
|
@ -60,7 +60,10 @@ internal class DefaultOnrampComponent @AssistedInject constructor(
|
|||
when (config) {
|
||||
OnrampChild.Settings -> settingsComponentFactory.create(
|
||||
context = childByContext(componentContext),
|
||||
params = OnrampSettingsComponent.Params(navigation::pop),
|
||||
params = OnrampSettingsComponent.Params(
|
||||
params.cryptoCurrency,
|
||||
navigation::pop,
|
||||
),
|
||||
)
|
||||
OnrampChild.Main -> onrampMainComponentFactory.create(
|
||||
context = childByContext(componentContext),
|
||||
|
|
|
|||
|
|
@ -2,10 +2,11 @@ package com.tangem.features.onramp.selectcountry
|
|||
|
||||
import com.tangem.core.decompose.factory.ComponentFactory
|
||||
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
|
||||
internal interface SelectCountryComponent : ComposableBottomSheetComponent {
|
||||
|
||||
data class Params(val onDismiss: () -> Unit)
|
||||
data class Params(val cryptoCurrency: CryptoCurrency, val onDismiss: () -> Unit)
|
||||
|
||||
interface Factory : ComponentFactory<Params, SelectCountryComponent>
|
||||
}
|
||||
|
|
@ -21,6 +21,7 @@ import com.tangem.features.onramp.selectcountry.entity.transformer.UpdateCountry
|
|||
import com.tangem.features.onramp.utils.InputManager
|
||||
import com.tangem.features.onramp.utils.UpdateSearchBarActiveStateTransformer
|
||||
import com.tangem.features.onramp.utils.UpdateSearchQueryTransformer
|
||||
import com.tangem.features.onramp.utils.sendOnrampErrorEvent
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
|
@ -61,10 +62,16 @@ internal class OnrampSelectCountryModel @Inject constructor(
|
|||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
private suspend fun subscribeOnUpdateState() {
|
||||
combine(
|
||||
flow = refreshTrigger.onStart { emit(Unit) }.flatMapLatest { flowOf(getOnrampCountriesUseCase.invoke()) },
|
||||
flow2 = getOnrampCountryUseCase.invoke(),
|
||||
flow = refreshTrigger.onStart { emit(Unit) }.flatMapLatest { flowOf(getOnrampCountriesUseCase()) },
|
||||
flow2 = getOnrampCountryUseCase(),
|
||||
flow3 = searchManager.query,
|
||||
) { maybeCountries, maybeCountry, query ->
|
||||
maybeCountries.onLeft {
|
||||
analyticsEventHandler.sendOnrampErrorEvent(it, params.cryptoCurrency.symbol)
|
||||
}
|
||||
maybeCountry.onLeft {
|
||||
analyticsEventHandler.sendOnrampErrorEvent(it, params.cryptoCurrency.symbol)
|
||||
}
|
||||
UpdateCountryItemsTransformer(
|
||||
maybeCountries = maybeCountries,
|
||||
defaultCountry = maybeCountry.getOrNull(),
|
||||
|
|
|
|||
|
|
@ -2,10 +2,11 @@ package com.tangem.features.onramp.selectcurrency
|
|||
|
||||
import com.tangem.core.decompose.factory.ComponentFactory
|
||||
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
|
||||
internal interface SelectCurrencyComponent : ComposableBottomSheetComponent {
|
||||
|
||||
data class Params(val onDismiss: () -> Unit)
|
||||
data class Params(val cryptoCurrency: CryptoCurrency, val onDismiss: () -> Unit)
|
||||
|
||||
interface Factory : ComponentFactory<Params, SelectCurrencyComponent>
|
||||
}
|
||||
|
|
@ -22,6 +22,7 @@ import com.tangem.features.onramp.selectcurrency.entity.transformer.UpdateCurren
|
|||
import com.tangem.features.onramp.utils.InputManager
|
||||
import com.tangem.features.onramp.utils.UpdateSearchBarActiveStateTransformer
|
||||
import com.tangem.features.onramp.utils.UpdateSearchQueryTransformer
|
||||
import com.tangem.features.onramp.utils.sendOnrampErrorEvent
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
|
@ -59,6 +60,9 @@ internal class OnrampSelectCurrencyModel @Inject constructor(
|
|||
flow = refreshTrigger.onStart { emit(Unit) }.flatMapLatest { flowOf(getOnrampCurrenciesUseCase.invoke()) },
|
||||
flow2 = searchManager.query,
|
||||
) { maybeCurrencies, query ->
|
||||
maybeCurrencies.onLeft {
|
||||
analyticsEventHandler.sendOnrampErrorEvent(it, params.cryptoCurrency.symbol)
|
||||
}
|
||||
UpdateCurrencyItemsTransformer(
|
||||
maybeCurrencies = maybeCurrencies,
|
||||
query = query,
|
||||
|
|
|
|||
|
|
@ -51,7 +51,10 @@ internal class DefaultOnrampSettingsComponent @AssistedInject constructor(
|
|||
): ComposableBottomSheetComponent = when (config) {
|
||||
OnrampSettingsConfig.SelectCountry -> selectCountryComponentFactory.create(
|
||||
context = childByContext(componentContext),
|
||||
params = SelectCountryComponent.Params(model.bottomSheetNavigation::dismiss),
|
||||
params = SelectCountryComponent.Params(
|
||||
params.cryptoCurrency,
|
||||
model.bottomSheetNavigation::dismiss,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,10 +2,11 @@ package com.tangem.features.onramp.settings
|
|||
|
||||
import com.tangem.core.decompose.factory.ComponentFactory
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
|
||||
internal interface OnrampSettingsComponent : ComposableContentComponent {
|
||||
|
||||
data class Params(val onBack: () -> Unit)
|
||||
data class Params(val cryptoCurrency: CryptoCurrency, val onBack: () -> Unit)
|
||||
|
||||
interface Factory : ComponentFactory<Params, OnrampSettingsComponent>
|
||||
}
|
||||
|
|
@ -15,6 +15,7 @@ import com.tangem.features.onramp.settings.OnrampSettingsComponent
|
|||
import com.tangem.features.onramp.settings.entity.OnrampSettingsConfig
|
||||
import com.tangem.features.onramp.settings.entity.OnrampSettingsItemUM
|
||||
import com.tangem.features.onramp.settings.entity.OnrampSettingsUM
|
||||
import com.tangem.features.onramp.utils.sendOnrampErrorEvent
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.flow.*
|
||||
|
|
@ -46,7 +47,11 @@ internal class OnrampSettingsModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun updateResidenceState(maybeCountry: Either<OnrampError, OnrampCountry?>) {
|
||||
maybeCountry.onRight { country ->
|
||||
maybeCountry.fold(
|
||||
ifLeft = {
|
||||
analyticsEventHandler.sendOnrampErrorEvent(it, params.cryptoCurrency.symbol)
|
||||
},
|
||||
ifRight = { country ->
|
||||
_state.update { state ->
|
||||
state.copy(
|
||||
items = listOf(
|
||||
|
|
@ -58,7 +63,8 @@ internal class OnrampSettingsModel @Inject constructor(
|
|||
).toImmutableList(),
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun openSelectCountryBottomSheet() {
|
||||
|
|
|
|||
|
|
@ -12,10 +12,12 @@ import com.tangem.domain.onramp.analytics.OnrampAnalyticsEvent
|
|||
import com.tangem.domain.onramp.model.OnrampStatus
|
||||
import com.tangem.domain.onramp.model.cache.OnrampTransaction
|
||||
import com.tangem.domain.tokens.GetCryptoCurrencyUseCase
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.features.onramp.component.OnrampSuccessComponent
|
||||
import com.tangem.features.onramp.success.entity.OnrampSuccessClickIntents
|
||||
import com.tangem.features.onramp.success.entity.OnrampSuccessComponentUM
|
||||
import com.tangem.features.onramp.success.entity.conterter.SetOnrampSuccessContentConverter
|
||||
import com.tangem.features.onramp.utils.sendOnrampErrorEvent
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
|
@ -74,8 +76,13 @@ internal class OnrampSuccessComponentModel @Inject constructor(
|
|||
|
||||
getOnrampStatusUseCase(externalTxId = params.externalTxId)
|
||||
.fold(
|
||||
ifLeft = {
|
||||
Timber.e(it.toString())
|
||||
ifLeft = { error ->
|
||||
analyticsEventHandler.sendOnrampErrorEvent(
|
||||
error = error,
|
||||
tokenSymbol = cryptoCurrency.symbol,
|
||||
providerName = transaction.providerName,
|
||||
)
|
||||
Timber.e(error.toString())
|
||||
},
|
||||
ifRight = { status ->
|
||||
analyticsEventHandler.send(
|
||||
|
|
@ -94,15 +101,25 @@ internal class OnrampSuccessComponentModel @Inject constructor(
|
|||
goToProviderClick = ::goToProviderClick,
|
||||
).convert(status)
|
||||
}
|
||||
removeTransactionIfTerminalStatus(status)
|
||||
removeTransactionIfTerminalStatus(cryptoCurrency, transaction.providerName, status)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun removeTransactionIfTerminalStatus(status: OnrampStatus) {
|
||||
private fun removeTransactionIfTerminalStatus(
|
||||
cryptoCurrency: CryptoCurrency,
|
||||
providerName: String,
|
||||
status: OnrampStatus,
|
||||
) {
|
||||
modelScope.launch {
|
||||
if (status.status.isTerminal) {
|
||||
onrampRemoveTransactionUseCase(status.txId)
|
||||
onrampRemoveTransactionUseCase(status.txId).onLeft { error ->
|
||||
analyticsEventHandler.sendOnrampErrorEvent(
|
||||
error = error,
|
||||
tokenSymbol = cryptoCurrency.symbol,
|
||||
providerName = providerName,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,32 @@
|
|||
package com.tangem.features.onramp.utils
|
||||
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.domain.onramp.analytics.OnrampAnalyticsEvent
|
||||
import com.tangem.domain.onramp.model.error.OnrampError
|
||||
|
||||
internal fun AnalyticsEventHandler.sendOnrampErrorEvent(
|
||||
error: OnrampError,
|
||||
tokenSymbol: String,
|
||||
providerName: String? = null,
|
||||
) = when (error) {
|
||||
is OnrampError.DataError -> send(
|
||||
OnrampAnalyticsEvent.Errors(
|
||||
tokenSymbol = tokenSymbol,
|
||||
providerName = providerName,
|
||||
errorCode = error.code,
|
||||
),
|
||||
)
|
||||
is OnrampError.DomainError -> {
|
||||
send(
|
||||
OnrampAnalyticsEvent.AppErrors(
|
||||
tokenSymbol = tokenSymbol,
|
||||
errorDescription = error.description.orEmpty(),
|
||||
),
|
||||
)
|
||||
}
|
||||
is OnrampError.AmountError.TooBigError,
|
||||
is OnrampError.AmountError.TooSmallError,
|
||||
OnrampError.RedirectError.VerificationFailed,
|
||||
OnrampError.RedirectError.WrongRequestId,
|
||||
-> { /* no-op */ }
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue