Updated on 2026-08-14
This commit is contained in:
commit
18af16b795
25 changed files with 502 additions and 289 deletions
|
|
@ -95,6 +95,11 @@ internal class FeedbackDataBuilder {
|
|||
)
|
||||
}
|
||||
|
||||
fun addSwapInfo(providerName: String, txId: String) {
|
||||
builder.appendKeyValue("Provider", providerName)
|
||||
builder.appendKeyValue("Transaction ID", txId)
|
||||
}
|
||||
|
||||
fun addDelimiter(): StringBuilder = builder.appendDelimiter()
|
||||
|
||||
fun build(): String = builder.trimEnd().toString()
|
||||
|
|
|
|||
|
|
@ -30,4 +30,10 @@ sealed interface FeedbackEmailType {
|
|||
val transactionTypes: List<String>,
|
||||
val unsignedTransactions: List<String?>,
|
||||
) : FeedbackEmailType
|
||||
|
||||
data class SwapProblem(
|
||||
override val cardInfo: CardInfo,
|
||||
val providerName: String,
|
||||
val txId: String,
|
||||
) : FeedbackEmailType
|
||||
}
|
||||
|
|
@ -29,6 +29,7 @@ internal class EmailMessageBodyResolver(
|
|||
type.transactionTypes,
|
||||
type.unsignedTransactions,
|
||||
)
|
||||
is FeedbackEmailType.SwapProblem -> addSwapProblemBody(type.cardInfo, type.providerName, type.txId)
|
||||
}
|
||||
|
||||
return build()
|
||||
|
|
@ -108,6 +109,35 @@ internal class EmailMessageBodyResolver(
|
|||
addPhoneInfo(phoneInfo = feedbackRepository.getPhoneInfo())
|
||||
}
|
||||
|
||||
private suspend fun FeedbackDataBuilder.addSwapProblemBody(
|
||||
cardInfo: CardInfo,
|
||||
providerName: String,
|
||||
txId: String,
|
||||
) {
|
||||
addCardInfo(cardInfo)
|
||||
addDelimiter()
|
||||
|
||||
val userWalletId = requireNotNull(cardInfo.userWalletId) { "UserWalletId must be not null" }
|
||||
val blockchainError = feedbackRepository.getBlockchainErrorInfo(userWalletId = userWalletId)
|
||||
val blockchainInfo = blockchainError?.let {
|
||||
feedbackRepository.getBlockchainInfo(
|
||||
userWalletId = userWalletId,
|
||||
blockchainId = blockchainError.blockchainId,
|
||||
derivationPath = blockchainError.derivationPath,
|
||||
)
|
||||
}
|
||||
|
||||
if (blockchainInfo != null) {
|
||||
addBlockchainError(blockchainInfo, blockchainError)
|
||||
addDelimiter()
|
||||
}
|
||||
|
||||
addSwapInfo(providerName, txId)
|
||||
addDelimiter()
|
||||
|
||||
addPhoneInfo(phoneInfo = feedbackRepository.getPhoneInfo())
|
||||
}
|
||||
|
||||
private fun FeedbackDataBuilder.addCardAndPhoneInfo(cardInfo: CardInfo) {
|
||||
addCardInfo(cardInfo)
|
||||
addDelimiter()
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ internal class EmailMessageTitleResolver(private val resources: Resources) {
|
|||
is FeedbackEmailType.ScanningProblem -> R.string.feedback_preface_scan_failed
|
||||
is FeedbackEmailType.TransactionSendingProblem,
|
||||
is FeedbackEmailType.StakingProblem,
|
||||
is FeedbackEmailType.SwapProblem,
|
||||
-> R.string.feedback_preface_tx_failed
|
||||
}
|
||||
.let(resources::getString)
|
||||
|
|
|
|||
|
|
@ -25,8 +25,10 @@ internal class EmailSubjectResolver(private val resources: Resources) {
|
|||
}
|
||||
is FeedbackEmailType.RateCanBeBetter -> R.string.feedback_subject_rate_negative
|
||||
is FeedbackEmailType.ScanningProblem -> R.string.feedback_subject_scan_failed
|
||||
is FeedbackEmailType.TransactionSendingProblem -> R.string.feedback_subject_tx_failed
|
||||
is FeedbackEmailType.StakingProblem -> R.string.feedback_subject_tx_failed
|
||||
is FeedbackEmailType.TransactionSendingProblem,
|
||||
is FeedbackEmailType.StakingProblem,
|
||||
is FeedbackEmailType.SwapProblem,
|
||||
-> R.string.feedback_subject_tx_failed
|
||||
}
|
||||
.let(resources::getString)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,29 +50,17 @@ internal fun StakingEventEffect(event: StateEvent<StakingEvent>, snackbarHostSta
|
|||
|
||||
@Composable
|
||||
internal fun StakingAlert(state: AlertUM, onDismiss: () -> Unit) {
|
||||
val confirmButton: DialogButtonUM
|
||||
val dismissButton: DialogButtonUM?
|
||||
|
||||
val onActionClick = state.onConfirmClick
|
||||
if (onActionClick != null) {
|
||||
confirmButton = DialogButtonUM(
|
||||
title = state.confirmButtonText.resolveReference(),
|
||||
onClick = {
|
||||
onActionClick()
|
||||
onDismiss()
|
||||
},
|
||||
)
|
||||
dismissButton = DialogButtonUM(
|
||||
title = stringResource(id = R.string.common_cancel),
|
||||
onClick = onDismiss,
|
||||
)
|
||||
} else {
|
||||
confirmButton = DialogButtonUM(
|
||||
title = state.confirmButtonText.resolveReference(),
|
||||
onClick = onDismiss,
|
||||
)
|
||||
dismissButton = null
|
||||
}
|
||||
val confirmButton = DialogButtonUM(
|
||||
title = state.confirmButtonText.resolveReference(),
|
||||
onClick = {
|
||||
state.onConfirmClick()
|
||||
onDismiss()
|
||||
},
|
||||
)
|
||||
val dismissButton = DialogButtonUM(
|
||||
title = stringResource(id = R.string.common_cancel),
|
||||
onClick = onDismiss,
|
||||
)
|
||||
|
||||
BasicDialog(
|
||||
message = state.message.resolveReference(),
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
|||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.feature.swap.converters.*
|
||||
import com.tangem.feature.swap.domain.api.SwapRepository
|
||||
import com.tangem.feature.swap.domain.models.DataError
|
||||
import com.tangem.feature.swap.domain.models.ExpressDataError
|
||||
import com.tangem.feature.swap.domain.models.ExpressException
|
||||
import com.tangem.feature.swap.domain.models.createFromAmountWithOffset
|
||||
import com.tangem.feature.swap.domain.models.domain.*
|
||||
|
|
@ -196,7 +196,7 @@ internal class DefaultSwapRepository @Inject constructor(
|
|||
toDecimals: Int,
|
||||
providerId: String,
|
||||
rateType: RateType,
|
||||
): Either<DataError, QuoteModel> {
|
||||
): Either<ExpressDataError, QuoteModel> {
|
||||
return withContext(coroutineDispatcher.io) {
|
||||
try {
|
||||
val response = tangemExpressApi.getExchangeQuote(
|
||||
|
|
@ -234,7 +234,7 @@ internal class DefaultSwapRepository @Inject constructor(
|
|||
toAddress: String,
|
||||
refundAddress: String?, // for cex only
|
||||
refundExtraId: String?, // for cex only
|
||||
): Either<DataError, SwapDataModel> {
|
||||
): Either<ExpressDataError, SwapDataModel> {
|
||||
return withContext(coroutineDispatcher.io) {
|
||||
try {
|
||||
val requestId = UUID.randomUUID().toString()
|
||||
|
|
@ -256,12 +256,12 @@ internal class DefaultSwapRepository @Inject constructor(
|
|||
).getOrThrow()
|
||||
if (dataSignatureVerifier.verifySignature(response.signature, response.txDetailsJson)) {
|
||||
val txDetails = parseTxDetails(response.txDetailsJson)
|
||||
?: return@withContext DataError.UnknownError.left()
|
||||
?: return@withContext ExpressDataError.UnknownError.left()
|
||||
if (txDetails.requestId != requestId) {
|
||||
return@withContext DataError.InvalidRequestIdError().left()
|
||||
return@withContext ExpressDataError.InvalidRequestIdError().left()
|
||||
}
|
||||
if (!toAddress.equals(txDetails.payoutAddress, ignoreCase = true)) {
|
||||
return@withContext DataError.InvalidPayoutAddressError().left()
|
||||
return@withContext ExpressDataError.InvalidPayoutAddressError().left()
|
||||
}
|
||||
expressDataConverter.convert(
|
||||
ExchangeDataResponseWithTxDetails(
|
||||
|
|
@ -270,7 +270,7 @@ internal class DefaultSwapRepository @Inject constructor(
|
|||
),
|
||||
).right()
|
||||
} else {
|
||||
DataError.InvalidSignatureError().left()
|
||||
ExpressDataError.InvalidSignatureError().left()
|
||||
}
|
||||
} catch (ex: Exception) {
|
||||
getDataError(ex).left()
|
||||
|
|
@ -285,7 +285,7 @@ internal class DefaultSwapRepository @Inject constructor(
|
|||
payInAddress: String,
|
||||
txHash: String,
|
||||
payInExtraId: String?,
|
||||
): Either<DataError, Unit> = withContext(coroutineDispatcher.io) {
|
||||
): Either<ExpressDataError, Unit> = withContext(coroutineDispatcher.io) {
|
||||
try {
|
||||
tangemExpressApi.exchangeSent(
|
||||
ExchangeSentRequestBody(
|
||||
|
|
@ -400,11 +400,11 @@ internal class DefaultSwapRepository @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
private fun getDataError(ex: Exception): DataError {
|
||||
private fun getDataError(ex: Exception): ExpressDataError {
|
||||
return if (ex is ApiResponseError.HttpException) {
|
||||
errorsDataConverter.convert(ex.errorBody ?: "")
|
||||
} else {
|
||||
DataError.UnknownError
|
||||
ExpressDataError.UnknownError
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,76 +3,77 @@ package com.tangem.feature.swap.converters
|
|||
import com.squareup.moshi.JsonAdapter
|
||||
import com.tangem.datasource.api.express.models.response.ExpressError
|
||||
import com.tangem.datasource.api.express.models.response.ExpressErrorResponse
|
||||
import com.tangem.feature.swap.domain.models.DataError
|
||||
import com.tangem.feature.swap.domain.models.ExpressDataError
|
||||
import com.tangem.feature.swap.domain.models.createFromAmountWithOffset
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
||||
internal class ErrorsDataConverter(
|
||||
private val jsonAdapter: JsonAdapter<ExpressErrorResponse>,
|
||||
) : Converter<String, DataError> {
|
||||
) : Converter<String, ExpressDataError> {
|
||||
|
||||
@Suppress("MagicNumber", "CyclomaticComplexMethod")
|
||||
override fun convert(value: String): DataError {
|
||||
override fun convert(value: String): ExpressDataError {
|
||||
try {
|
||||
val error = jsonAdapter.fromJson(value)?.error ?: return DataError.UnknownError
|
||||
val error = jsonAdapter.fromJson(value)?.error ?: return ExpressDataError.UnknownError
|
||||
|
||||
return when (error.code) {
|
||||
2010 -> DataError.BadRequest(code = error.code)
|
||||
2200 -> DataError.SwapsAreUnavailableNowError(code = error.code)
|
||||
2210 -> DataError.ExchangeProviderNotFoundError(code = error.code)
|
||||
2220 -> DataError.ExchangeProviderNotActiveError(code = error.code)
|
||||
2230 -> DataError.ExchangeProviderNotAvailableError(code = error.code)
|
||||
2231 -> DataError.ExchangeProviderProviderInternalError(code = error.code)
|
||||
2240 -> DataError.ExchangeNotPossibleError(code = error.code)
|
||||
2010 -> ExpressDataError.BadRequest(code = error.code)
|
||||
2200 -> ExpressDataError.SwapsAreUnavailableNowError(code = error.code)
|
||||
2210 -> ExpressDataError.ExchangeProviderNotFoundError(code = error.code)
|
||||
2220 -> ExpressDataError.ExchangeProviderNotActiveError(code = error.code)
|
||||
2230 -> ExpressDataError.ExchangeProviderNotAvailableError(code = error.code)
|
||||
2231 -> ExpressDataError.ExchangeProviderProviderInternalError(code = error.code)
|
||||
2240 -> ExpressDataError.ExchangeNotPossibleError(code = error.code)
|
||||
2250 -> tryParseExchangeTooSmallAmountError(error = error)
|
||||
2251 -> tryParseExchangeTooBigAmountError(error = error)
|
||||
2260 -> tryParseExchangeNotEnoughAllowanceError(error = error)
|
||||
2270 -> DataError.ExchangeNotEnoughBalanceError(code = error.code)
|
||||
2280 -> DataError.ExchangeInvalidAddressError(code = error.code)
|
||||
2270 -> ExpressDataError.ExchangeNotEnoughBalanceError(code = error.code)
|
||||
2280 -> ExpressDataError.ExchangeInvalidAddressError(code = error.code)
|
||||
2290 -> tryParseExchangeInvalidFromDecimalsError(error = error)
|
||||
else -> DataError.UnknownErrorWithCode(error.code)
|
||||
else -> ExpressDataError.UnknownErrorWithCode(error.code)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
return DataError.UnknownError
|
||||
return ExpressDataError.UnknownError
|
||||
}
|
||||
}
|
||||
|
||||
private fun tryParseExchangeTooSmallAmountError(error: ExpressError): DataError {
|
||||
val minAmount = error.value?.minAmount ?: return DataError.UnknownErrorWithCode(error.code)
|
||||
val decimals = error.value?.decimals ?: return DataError.UnknownErrorWithCode(error.code)
|
||||
private fun tryParseExchangeTooSmallAmountError(error: ExpressError): ExpressDataError {
|
||||
val minAmount = error.value?.minAmount ?: return ExpressDataError.UnknownErrorWithCode(error.code)
|
||||
val decimals = error.value?.decimals ?: return ExpressDataError.UnknownErrorWithCode(error.code)
|
||||
|
||||
return DataError.ExchangeTooSmallAmountError(
|
||||
return ExpressDataError.ExchangeTooSmallAmountError(
|
||||
code = error.code,
|
||||
amount = createFromAmountWithOffset(minAmount, decimals),
|
||||
)
|
||||
}
|
||||
|
||||
private fun tryParseExchangeTooBigAmountError(error: ExpressError): DataError {
|
||||
val minAmount = error.value?.maxAmount ?: return DataError.UnknownErrorWithCode(error.code)
|
||||
val decimals = error.value?.decimals ?: return DataError.UnknownErrorWithCode(error.code)
|
||||
private fun tryParseExchangeTooBigAmountError(error: ExpressError): ExpressDataError {
|
||||
val minAmount = error.value?.maxAmount ?: return ExpressDataError.UnknownErrorWithCode(error.code)
|
||||
val decimals = error.value?.decimals ?: return ExpressDataError.UnknownErrorWithCode(error.code)
|
||||
|
||||
return DataError.ExchangeTooBigAmountError(
|
||||
return ExpressDataError.ExchangeTooBigAmountError(
|
||||
code = error.code,
|
||||
amount = createFromAmountWithOffset(minAmount, decimals),
|
||||
)
|
||||
}
|
||||
|
||||
private fun tryParseExchangeNotEnoughAllowanceError(error: ExpressError): DataError {
|
||||
val currentAllowance = error.value?.currentAllowance ?: return DataError.UnknownErrorWithCode(error.code)
|
||||
private fun tryParseExchangeNotEnoughAllowanceError(error: ExpressError): ExpressDataError {
|
||||
val currentAllowance = error.value?.currentAllowance ?: return ExpressDataError.UnknownErrorWithCode(error.code)
|
||||
|
||||
return DataError.ExchangeNotEnoughAllowanceError(
|
||||
return ExpressDataError.ExchangeNotEnoughAllowanceError(
|
||||
code = error.code,
|
||||
currentAllowance = currentAllowance,
|
||||
)
|
||||
}
|
||||
|
||||
private fun tryParseExchangeInvalidFromDecimalsError(error: ExpressError): DataError {
|
||||
val receivedFromDecimals = error.value?.receivedFromDecimals ?: return DataError.UnknownErrorWithCode(
|
||||
private fun tryParseExchangeInvalidFromDecimalsError(error: ExpressError): ExpressDataError {
|
||||
val receivedFromDecimals = error.value?.receivedFromDecimals ?: return ExpressDataError.UnknownErrorWithCode(
|
||||
code = error.code,
|
||||
)
|
||||
val expressFromDecimals = error.value?.expressFromDecimals ?: return DataError.UnknownErrorWithCode(error.code)
|
||||
val expressFromDecimals =
|
||||
error.value?.expressFromDecimals ?: return ExpressDataError.UnknownErrorWithCode(error.code)
|
||||
|
||||
return DataError.ExchangeInvalidFromDecimalsError(
|
||||
return ExpressDataError.ExchangeInvalidFromDecimalsError(
|
||||
code = error.code,
|
||||
receivedFromDecimals = receivedFromDecimals,
|
||||
expressFromDecimals = expressFromDecimals,
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ package com.tangem.feature.swap.domain.api
|
|||
import arrow.core.Either
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.feature.swap.domain.models.DataError
|
||||
import com.tangem.feature.swap.domain.models.ExpressDataError
|
||||
import com.tangem.feature.swap.domain.models.domain.*
|
||||
import java.math.BigDecimal
|
||||
|
||||
|
|
@ -27,7 +27,7 @@ interface SwapRepository {
|
|||
toDecimals: Int,
|
||||
providerId: String,
|
||||
rateType: RateType,
|
||||
): Either<DataError, QuoteModel>
|
||||
): Either<ExpressDataError, QuoteModel>
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@Throws(IllegalStateException::class)
|
||||
|
|
@ -66,7 +66,7 @@ interface SwapRepository {
|
|||
toAddress: String,
|
||||
refundAddress: String? = null, // for cex only
|
||||
refundExtraId: String? = null, // for cex only
|
||||
): Either<DataError, SwapDataModel>
|
||||
): Either<ExpressDataError, SwapDataModel>
|
||||
|
||||
// TODO: Add target error handling, remove either ([REDACTED_JIRA])
|
||||
@Suppress("LongParameterList")
|
||||
|
|
@ -77,7 +77,7 @@ interface SwapRepository {
|
|||
payInAddress: String,
|
||||
txHash: String,
|
||||
payInExtraId: String?,
|
||||
): Either<DataError, Unit>
|
||||
): Either<ExpressDataError, Unit>
|
||||
|
||||
fun getNativeTokenForNetwork(networkId: String): CryptoCurrency
|
||||
}
|
||||
|
|
@ -12,6 +12,7 @@ android {
|
|||
dependencies {
|
||||
/** Domain */
|
||||
implementation(projects.domain.tokens.models)
|
||||
implementation(projects.domain.transaction.models)
|
||||
|
||||
/** Core modules */
|
||||
implementation(projects.core.utils)
|
||||
|
|
|
|||
|
|
@ -2,49 +2,52 @@ package com.tangem.feature.swap.domain.models
|
|||
|
||||
import java.math.BigDecimal
|
||||
|
||||
sealed class DataError {
|
||||
sealed class ExpressDataError {
|
||||
|
||||
abstract val code: Int
|
||||
|
||||
data class BadRequest(override val code: Int) : DataError()
|
||||
data class BadRequest(override val code: Int) : ExpressDataError()
|
||||
|
||||
data class SwapsAreUnavailableNowError(override val code: Int) : DataError()
|
||||
data class SwapsAreUnavailableNowError(override val code: Int) : ExpressDataError()
|
||||
|
||||
data class ExchangeProviderNotFoundError(override val code: Int) : DataError()
|
||||
data class ExchangeProviderNotFoundError(override val code: Int) : ExpressDataError()
|
||||
|
||||
data class ExchangeProviderNotActiveError(override val code: Int) : DataError()
|
||||
data class ExchangeProviderNotActiveError(override val code: Int) : ExpressDataError()
|
||||
|
||||
data class ExchangeProviderNotAvailableError(override val code: Int) : DataError()
|
||||
data class ExchangeProviderNotAvailableError(override val code: Int) : ExpressDataError()
|
||||
|
||||
data class ExchangeProviderProviderInternalError(override val code: Int) : DataError()
|
||||
data class ExchangeProviderProviderInternalError(override val code: Int) : ExpressDataError()
|
||||
|
||||
data class ExchangeNotPossibleError(override val code: Int) : DataError()
|
||||
data class ExchangeNotPossibleError(override val code: Int) : ExpressDataError()
|
||||
|
||||
data class ExchangeTooSmallAmountError(override val code: Int, val amount: SwapAmount) : DataError()
|
||||
data class ExchangeTooSmallAmountError(override val code: Int, val amount: SwapAmount) : ExpressDataError()
|
||||
|
||||
data class ExchangeTooBigAmountError(override val code: Int, val amount: SwapAmount) : DataError()
|
||||
data class ExchangeTooBigAmountError(override val code: Int, val amount: SwapAmount) : ExpressDataError()
|
||||
|
||||
data class ExchangeNotEnoughAllowanceError(override val code: Int, val currentAllowance: BigDecimal) : DataError()
|
||||
data class ExchangeNotEnoughAllowanceError(
|
||||
override val code: Int,
|
||||
val currentAllowance: BigDecimal,
|
||||
) : ExpressDataError()
|
||||
|
||||
data class ExchangeNotEnoughBalanceError(override val code: Int) : DataError()
|
||||
data class ExchangeNotEnoughBalanceError(override val code: Int) : ExpressDataError()
|
||||
|
||||
data class ExchangeInvalidAddressError(override val code: Int) : DataError()
|
||||
data class ExchangeInvalidAddressError(override val code: Int) : ExpressDataError()
|
||||
|
||||
data class ExchangeInvalidFromDecimalsError(
|
||||
override val code: Int,
|
||||
val receivedFromDecimals: Int,
|
||||
val expressFromDecimals: Int,
|
||||
) : DataError()
|
||||
) : ExpressDataError()
|
||||
|
||||
data class UnknownErrorWithCode(override val code: Int) : DataError()
|
||||
data class UnknownErrorWithCode(override val code: Int) : ExpressDataError()
|
||||
|
||||
data class InvalidSignatureError(override val code: Int = 990) : DataError()
|
||||
data class InvalidSignatureError(override val code: Int = 990) : ExpressDataError()
|
||||
|
||||
data class InvalidRequestIdError(override val code: Int = 991) : DataError()
|
||||
data class InvalidRequestIdError(override val code: Int = 991) : ExpressDataError()
|
||||
|
||||
data class InvalidPayoutAddressError(override val code: Int = 992) : DataError()
|
||||
data class InvalidPayoutAddressError(override val code: Int = 992) : ExpressDataError()
|
||||
|
||||
data object UnknownError : DataError() {
|
||||
data object UnknownError : ExpressDataError() {
|
||||
override val code: Int = -1
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +1,3 @@
|
|||
package com.tangem.feature.swap.domain.models
|
||||
|
||||
class ExpressException(val dataError: DataError) : Exception()
|
||||
class ExpressException(val expressDataError: ExpressDataError) : Exception()
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
package com.tangem.feature.swap.domain.models.ui
|
||||
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.feature.swap.domain.models.DataError
|
||||
import com.tangem.feature.swap.domain.models.ExpressDataError
|
||||
import com.tangem.feature.swap.domain.models.SwapAmount
|
||||
import com.tangem.feature.swap.domain.models.domain.*
|
||||
import java.math.BigDecimal
|
||||
|
|
@ -34,7 +34,7 @@ sealed interface SwapState {
|
|||
|
||||
data class SwapError(
|
||||
val fromTokenInfo: TokenSwapInfo,
|
||||
val error: DataError,
|
||||
val error: ExpressDataError,
|
||||
val includeFeeInAmount: IncludeFeeInAmount,
|
||||
) : SwapState
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.feature.swap.domain.models.ui
|
||||
|
||||
import com.tangem.feature.swap.domain.models.DataError
|
||||
import com.tangem.domain.transaction.error.SendTransactionError
|
||||
import com.tangem.feature.swap.domain.models.ExpressDataError
|
||||
import java.math.BigDecimal
|
||||
|
||||
sealed class SwapTransactionState {
|
||||
|
|
@ -15,17 +16,13 @@ sealed class SwapTransactionState {
|
|||
val timestamp: Long,
|
||||
) : SwapTransactionState()
|
||||
|
||||
data object UserCancelled : SwapTransactionState()
|
||||
|
||||
data object BlockchainError : SwapTransactionState()
|
||||
|
||||
data object TangemSdkError : SwapTransactionState()
|
||||
|
||||
data object NetworkError : SwapTransactionState()
|
||||
|
||||
data object UnknownError : SwapTransactionState()
|
||||
|
||||
data class ExpressError(val dataError: DataError) : SwapTransactionState()
|
||||
|
||||
data object DemoMode : SwapTransactionState()
|
||||
|
||||
sealed class Error : SwapTransactionState() {
|
||||
data class TransactionError(val error: SendTransactionError?) : Error()
|
||||
|
||||
data class ExpressError(val error: ExpressDataError) : Error()
|
||||
|
||||
data object UnknownError : Error()
|
||||
}
|
||||
}
|
||||
|
|
@ -22,7 +22,6 @@ import com.tangem.domain.tokens.repository.CurrenciesRepository
|
|||
import com.tangem.domain.tokens.repository.CurrencyChecksRepository
|
||||
import com.tangem.domain.tokens.repository.QuotesRepository
|
||||
import com.tangem.domain.transaction.error.GetFeeError
|
||||
import com.tangem.domain.transaction.error.SendTransactionError
|
||||
import com.tangem.domain.transaction.models.TransactionType
|
||||
import com.tangem.domain.transaction.usecase.*
|
||||
import com.tangem.domain.utils.convertToSdkAmount
|
||||
|
|
@ -31,7 +30,7 @@ import com.tangem.domain.wallets.models.UserWalletId
|
|||
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
||||
import com.tangem.feature.swap.domain.api.SwapRepository
|
||||
import com.tangem.feature.swap.domain.converters.SwapCurrencyConverter
|
||||
import com.tangem.feature.swap.domain.models.DataError
|
||||
import com.tangem.feature.swap.domain.models.ExpressDataError
|
||||
import com.tangem.feature.swap.domain.models.SwapAmount
|
||||
import com.tangem.feature.swap.domain.models.domain.*
|
||||
import com.tangem.feature.swap.domain.models.toStringWithRightOffset
|
||||
|
|
@ -227,7 +226,7 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
),
|
||||
).getOrElse {
|
||||
Timber.e(it, "Failed to create approveTransaction")
|
||||
return SwapTransactionState.UnknownError
|
||||
return SwapTransactionState.Error.UnknownError
|
||||
}
|
||||
|
||||
val result = sendTransactionUseCase(
|
||||
|
|
@ -243,16 +242,7 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
timestamp = System.currentTimeMillis(),
|
||||
)
|
||||
},
|
||||
ifLeft = {
|
||||
when (it) {
|
||||
SendTransactionError.UserCancelledError -> SwapTransactionState.UserCancelled
|
||||
is SendTransactionError.BlockchainSdkError -> SwapTransactionState.BlockchainError
|
||||
is SendTransactionError.TangemSdkError -> SwapTransactionState.TangemSdkError
|
||||
is SendTransactionError.NetworkError -> SwapTransactionState.NetworkError
|
||||
is SendTransactionError.DemoCardError -> SwapTransactionState.DemoMode
|
||||
else -> SwapTransactionState.UnknownError
|
||||
}
|
||||
},
|
||||
ifLeft = { SwapTransactionState.Error.TransactionError(it) },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -591,7 +581,6 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
""".trimIndent(),
|
||||
)
|
||||
|
||||
val userWallet = getUserWalletUseCase(userWalletId).getOrNull() ?: return SwapTransactionState.UnknownError
|
||||
val cardId = userWallet.scanResponse.card.cardId
|
||||
if (isDemoCardUseCase(cardId)) return SwapTransactionState.DemoMode
|
||||
|
||||
|
|
@ -693,12 +682,12 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
hash = dataToSign,
|
||||
).getOrElse {
|
||||
Timber.e(it, "Failed to create swap dex tx data")
|
||||
return SwapTransactionState.UnknownError
|
||||
return SwapTransactionState.Error.UnknownError
|
||||
}
|
||||
|
||||
val result = sendTransactionUseCase(
|
||||
txData = txData,
|
||||
userWallet = getUserWalletUseCase(userWalletId).getOrElse { return SwapTransactionState.UnknownError },
|
||||
userWallet = userWallet,
|
||||
network = currencyToSendStatus.currency.network,
|
||||
)
|
||||
return result.fold(
|
||||
|
|
@ -738,7 +727,7 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
timestamp = System.currentTimeMillis(),
|
||||
)
|
||||
},
|
||||
ifLeft = { handleSendTxError(it) },
|
||||
ifLeft = { SwapTransactionState.Error.TransactionError(it) },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -773,14 +762,14 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
toAddress = currencyToGet.value.networkAddress?.defaultAddress?.value.orEmpty(),
|
||||
refundAddress = currencyToSend.value.networkAddress?.defaultAddress?.value,
|
||||
refundExtraId = null, // currently always null
|
||||
).getOrElse { return SwapTransactionState.ExpressError(it) }
|
||||
).getOrElse { return SwapTransactionState.Error.ExpressError(it) }
|
||||
|
||||
val exchangeDataCex =
|
||||
exchangeData.transaction as? ExpressTransactionModel.CEX ?: return SwapTransactionState.UnknownError
|
||||
exchangeData.transaction as? ExpressTransactionModel.CEX ?: return SwapTransactionState.Error.UnknownError
|
||||
|
||||
val cardId = userWallet.scanResponse.card.cardId
|
||||
|
||||
if (isDemoCardUseCase(cardId)) return SwapTransactionState.UnknownError
|
||||
if (isDemoCardUseCase(cardId)) return SwapTransactionState.Error.UnknownError
|
||||
|
||||
val txData = createTransactionUseCase(
|
||||
amount = amount.value.convertToSdkAmount(currencyToSend.currency),
|
||||
|
|
@ -794,11 +783,11 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
network = currencyToSend.currency.network,
|
||||
).getOrElse {
|
||||
Timber.e(it, "Failed to create swap CEX tx data")
|
||||
return SwapTransactionState.UnknownError
|
||||
return SwapTransactionState.Error.UnknownError
|
||||
}
|
||||
|
||||
if (txData.extras == null && exchangeDataCex.txExtraId != null) {
|
||||
return SwapTransactionState.UnknownError
|
||||
return SwapTransactionState.Error.UnknownError
|
||||
}
|
||||
|
||||
val result = sendTransactionUseCase(
|
||||
|
|
@ -809,9 +798,7 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
|
||||
val derivationPath = currencyToSend.currency.network.derivationPath.value
|
||||
return result.fold(
|
||||
ifLeft = {
|
||||
handleSendTxError(it)
|
||||
},
|
||||
ifLeft = { SwapTransactionState.Error.TransactionError(it) },
|
||||
ifRight = { txHash ->
|
||||
repository.exchangeSent(
|
||||
txId = exchangeDataCex.txId,
|
||||
|
|
@ -856,17 +843,6 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
private fun handleSendTxError(txError: SendTransactionError?): SwapTransactionState {
|
||||
return when (txError) {
|
||||
SendTransactionError.UserCancelledError -> SwapTransactionState.UserCancelled
|
||||
is SendTransactionError.BlockchainSdkError -> SwapTransactionState.BlockchainError
|
||||
is SendTransactionError.TangemSdkError -> SwapTransactionState.TangemSdkError
|
||||
is SendTransactionError.NetworkError -> SwapTransactionState.NetworkError
|
||||
is SendTransactionError.DemoCardError -> SwapTransactionState.DemoMode
|
||||
else -> SwapTransactionState.UnknownError
|
||||
}
|
||||
}
|
||||
|
||||
private fun getFeeForTransaction(fee: TxFee, blockchain: Blockchain): Fee {
|
||||
val feeAmountValue = fee.feeValue
|
||||
val feeAmount = Amount(
|
||||
|
|
@ -1081,7 +1057,7 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
@Suppress("LongMethod")
|
||||
private suspend fun getQuotesState(
|
||||
provider: SwapProvider,
|
||||
quoteDataModel: Either<DataError, QuoteModel>,
|
||||
quoteDataModel: Either<ExpressDataError, QuoteModel>,
|
||||
amount: SwapAmount,
|
||||
fromToken: CryptoCurrencyStatus,
|
||||
toToken: CryptoCurrencyStatus,
|
||||
|
|
@ -1160,7 +1136,7 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
fromToken = fromToken,
|
||||
amount = amount,
|
||||
includeFeeInAmount = includeFeeInAmount,
|
||||
dataError = error,
|
||||
expressDataError = error,
|
||||
)
|
||||
},
|
||||
)
|
||||
|
|
@ -1170,7 +1146,7 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
fromToken: CryptoCurrencyStatus,
|
||||
amount: SwapAmount,
|
||||
includeFeeInAmount: IncludeFeeInAmount,
|
||||
dataError: DataError,
|
||||
expressDataError: ExpressDataError,
|
||||
): SwapState.SwapError {
|
||||
val rates = getQuotes(fromToken.currency.id)
|
||||
val fromTokenSwapInfo = TokenSwapInfo(
|
||||
|
|
@ -1179,7 +1155,7 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
?: BigDecimal.ZERO,
|
||||
cryptoCurrencyStatus = fromToken,
|
||||
)
|
||||
return SwapState.SwapError(fromTokenSwapInfo, dataError, includeFeeInAmount)
|
||||
return SwapState.SwapError(fromTokenSwapInfo, expressDataError, includeFeeInAmount)
|
||||
}
|
||||
|
||||
@Suppress("CyclomaticComplexMethod")
|
||||
|
|
@ -1526,7 +1502,7 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
fromToken = fromTokenStatus,
|
||||
amount = swapAmount,
|
||||
includeFeeInAmount = IncludeFeeInAmount.Excluded,
|
||||
dataError = DataError.UnknownError,
|
||||
expressDataError = ExpressDataError.UnknownError,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ dependencies {
|
|||
implementation(projects.core.decompose) // For Route supertype
|
||||
|
||||
/** Domain modules **/
|
||||
implementation(projects.domain.models)
|
||||
implementation(projects.domain.appCurrency)
|
||||
implementation(projects.domain.appCurrency.models)
|
||||
implementation(projects.domain.balanceHiding)
|
||||
|
|
@ -31,13 +32,17 @@ dependencies {
|
|||
implementation(projects.domain.tokens)
|
||||
implementation(projects.domain.tokens.models)
|
||||
implementation(projects.domain.transaction)
|
||||
implementation(projects.domain.transaction.models)
|
||||
implementation(projects.domain.wallets)
|
||||
implementation(projects.domain.wallets.models)
|
||||
implementation(projects.domain.settings)
|
||||
implementation(projects.domain.staking)
|
||||
implementation(projects.domain.feedback)
|
||||
|
||||
/** Feature modules */
|
||||
implementation(projects.features.swap.domain)
|
||||
implementation(projects.features.swap.domain.api)
|
||||
implementation(projects.features.swap.domain.models)
|
||||
implementation(projects.domain.staking)
|
||||
|
||||
/** AndroidX */
|
||||
implementation(deps.androidx.activity.compose)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,33 @@
|
|||
package com.tangem.feature.swap.converters
|
||||
|
||||
import com.tangem.common.ui.alerts.TransactionErrorAlertConverter
|
||||
import com.tangem.common.ui.alerts.models.AlertUM
|
||||
import com.tangem.domain.transaction.error.SendTransactionError
|
||||
import com.tangem.feature.swap.domain.models.ui.SwapTransactionState
|
||||
import com.tangem.feature.swap.models.SwapAlertUM
|
||||
import com.tangem.feature.swap.utils.getExpressErrorMessage
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
||||
internal class SwapTransactionErrorStateConverter(
|
||||
private val onDismiss: () -> Unit,
|
||||
private val onSupportClick: (String) -> Unit,
|
||||
) : Converter<SwapTransactionState.Error, AlertUM?> {
|
||||
override fun convert(value: SwapTransactionState.Error): AlertUM? {
|
||||
return when (value) {
|
||||
is SwapTransactionState.Error.TransactionError -> {
|
||||
when (val error = value.error) {
|
||||
is SendTransactionError.UserCancelledError -> return null
|
||||
null -> SwapAlertUM.GenericError(onDismiss)
|
||||
else -> TransactionErrorAlertConverter(onDismiss, onSupportClick).convert(error)
|
||||
}
|
||||
}
|
||||
is SwapTransactionState.Error.ExpressError -> {
|
||||
SwapAlertUM.ExpressErrorAlert(
|
||||
message = getExpressErrorMessage(value.error),
|
||||
onConfirmClick = { onSupportClick(value.error.code.toString()) },
|
||||
)
|
||||
}
|
||||
SwapTransactionState.Error.UnknownError -> SwapAlertUM.GenericError(onDismiss)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
package com.tangem.feature.swap.models
|
||||
|
||||
import com.tangem.common.ui.alerts.models.AlertUM
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
|
||||
sealed class SwapAlertUM : AlertUM {
|
||||
|
||||
data class GenericError(
|
||||
override val onConfirmClick: (() -> Unit),
|
||||
override val message: TextReference = resourceReference(R.string.common_unknown_error),
|
||||
) : SwapAlertUM() {
|
||||
override val title: TextReference? = null
|
||||
override val confirmButtonText: TextReference =
|
||||
resourceReference(id = R.string.common_support)
|
||||
}
|
||||
|
||||
data class ExpressErrorAlert(
|
||||
override val message: TextReference = resourceReference(R.string.common_unknown_error),
|
||||
override val onConfirmClick: (() -> Unit),
|
||||
) : SwapAlertUM() {
|
||||
override val title: TextReference? = null
|
||||
override val confirmButtonText: TextReference =
|
||||
resourceReference(id = R.string.common_support)
|
||||
}
|
||||
|
||||
data class FeesAlert(
|
||||
override val message: TextReference,
|
||||
override val onConfirmClick: (() -> Unit),
|
||||
) : SwapAlertUM() {
|
||||
override val title: TextReference = resourceReference(
|
||||
com.tangem.feature.swap.presentation.R.string.swapping_alert_title,
|
||||
)
|
||||
override val confirmButtonText: TextReference =
|
||||
resourceReference(id = R.string.common_ok)
|
||||
}
|
||||
}
|
||||
|
|
@ -6,18 +6,21 @@ import com.tangem.common.ui.bottomsheet.permission.state.GiveTxPermissionState
|
|||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.core.ui.components.notifications.NotificationConfig
|
||||
import com.tangem.core.ui.event.StateEvent
|
||||
import com.tangem.core.ui.event.consumedEvent
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.feature.swap.domain.models.ui.PriceImpact
|
||||
import com.tangem.feature.swap.models.states.FeeItemState
|
||||
import com.tangem.feature.swap.models.states.ProviderState
|
||||
import com.tangem.feature.swap.models.states.events.SwapEvent
|
||||
|
||||
data class SwapStateHolder(
|
||||
internal data class SwapStateHolder(
|
||||
val sendCardData: SwapCardState,
|
||||
val receiveCardData: SwapCardState,
|
||||
val blockchainId: String, // not the same as networkId, its local id in app
|
||||
val warnings: List<SwapWarning> = emptyList(),
|
||||
val alert: SwapWarning.GenericWarning? = null,
|
||||
val event: StateEvent<SwapEvent> = consumedEvent(),
|
||||
val changeCardsButtonState: ChangeCardsButtonState = ChangeCardsButtonState.ENABLED,
|
||||
val providerState: ProviderState,
|
||||
|
||||
|
|
@ -110,7 +113,6 @@ sealed interface SwapWarning {
|
|||
data class GenericWarning(
|
||||
val title: TextReference? = null,
|
||||
val message: TextReference? = null,
|
||||
val type: GenericWarningType = GenericWarningType.OTHER,
|
||||
val onClick: () -> Unit,
|
||||
) : SwapWarning
|
||||
|
||||
|
|
@ -133,10 +135,6 @@ sealed interface SwapWarning {
|
|||
}
|
||||
}
|
||||
|
||||
enum class GenericWarningType {
|
||||
NETWORK, OTHER
|
||||
}
|
||||
|
||||
enum class ChangeCardsButtonState {
|
||||
ENABLED, DISABLED, UPDATE_IN_PROGRESS
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.tangem.feature.swap.models.states.events
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.tangem.common.ui.alerts.models.AlertUM
|
||||
|
||||
@Immutable
|
||||
internal sealed class SwapEvent {
|
||||
data class ShowAlert(val alert: AlertUM) : SwapEvent()
|
||||
|
||||
data class ShowShareDialog(val txUrl: String) : SwapEvent()
|
||||
}
|
||||
|
|
@ -2,24 +2,31 @@ package com.tangem.feature.swap.ui
|
|||
|
||||
import androidx.compose.ui.text.TextRange
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import com.tangem.common.ui.alerts.models.AlertDemoModeUM
|
||||
import com.tangem.common.ui.bottomsheet.permission.state.*
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.core.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter
|
||||
import com.tangem.core.ui.components.notifications.NotificationConfig
|
||||
import com.tangem.core.ui.event.consumedEvent
|
||||
import com.tangem.core.ui.event.triggeredEvent
|
||||
import com.tangem.core.ui.extensions.*
|
||||
import com.tangem.core.ui.utils.BigDecimalFormatter
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.feature.swap.converters.SwapTransactionErrorStateConverter
|
||||
import com.tangem.feature.swap.converters.TokensDataConverter
|
||||
import com.tangem.feature.swap.domain.models.DataError
|
||||
import com.tangem.feature.swap.domain.models.ExpressDataError
|
||||
import com.tangem.feature.swap.domain.models.SwapAmount
|
||||
import com.tangem.feature.swap.domain.models.domain.*
|
||||
import com.tangem.feature.swap.domain.models.formatToUIRepresentation
|
||||
import com.tangem.feature.swap.domain.models.ui.*
|
||||
import com.tangem.feature.swap.models.*
|
||||
import com.tangem.feature.swap.models.states.*
|
||||
import com.tangem.feature.swap.models.states.events.SwapEvent
|
||||
import com.tangem.feature.swap.presentation.R
|
||||
import com.tangem.feature.swap.utils.getExpressErrorMessage
|
||||
import com.tangem.feature.swap.utils.getExpressErrorTitle
|
||||
import com.tangem.feature.swap.viewmodels.SwapProcessDataState
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.StringsSigns.DASH_SIGN
|
||||
|
|
@ -625,13 +632,13 @@ internal class StateBuilder(
|
|||
fromToken: TokenSwapInfo,
|
||||
toToken: CryptoCurrencyStatus?,
|
||||
includeFeeInAmount: IncludeFeeInAmount,
|
||||
dataError: DataError,
|
||||
expressDataError: ExpressDataError,
|
||||
isReverseSwapPossible: Boolean,
|
||||
): SwapStateHolder {
|
||||
if (uiStateHolder.sendCardData !is SwapCardState.SwapCardData) return uiStateHolder
|
||||
if (uiStateHolder.receiveCardData !is SwapCardState.SwapCardData) return uiStateHolder
|
||||
val warnings = mutableListOf<SwapWarning>()
|
||||
warnings.add(getWarningForError(dataError, fromToken.cryptoCurrencyStatus.currency))
|
||||
warnings.add(getWarningForError(expressDataError, fromToken.cryptoCurrencyStatus.currency))
|
||||
if (includeFeeInAmount is IncludeFeeInAmount.Included && uiStateHolder.fee is FeeItemState.Content) {
|
||||
val feeCoverageNotification = createNetworkFeeCoverageNotificationConfig(
|
||||
uiStateHolder.fee.amountCrypto,
|
||||
|
|
@ -642,7 +649,7 @@ internal class StateBuilder(
|
|||
val providerState = getProviderStateForError(
|
||||
swapProvider = swapProvider,
|
||||
fromToken = fromToken.cryptoCurrencyStatus.currency,
|
||||
dataError = dataError,
|
||||
expressDataError = expressDataError,
|
||||
onProviderClick = actions.onProviderClick,
|
||||
selectionType = ProviderState.SelectionType.CLICK,
|
||||
)
|
||||
|
|
@ -698,28 +705,28 @@ internal class StateBuilder(
|
|||
private fun getProviderStateForError(
|
||||
swapProvider: SwapProvider,
|
||||
fromToken: CryptoCurrency,
|
||||
dataError: DataError,
|
||||
expressDataError: ExpressDataError,
|
||||
onProviderClick: (String) -> Unit,
|
||||
selectionType: ProviderState.SelectionType,
|
||||
): ProviderState {
|
||||
return when (dataError) {
|
||||
is DataError.ExchangeTooSmallAmountError -> {
|
||||
return when (expressDataError) {
|
||||
is ExpressDataError.ExchangeTooSmallAmountError -> {
|
||||
swapProvider.convertToAvailableFromProviderState(
|
||||
swapProvider = swapProvider,
|
||||
alertText = resourceReference(
|
||||
R.string.express_provider_min_amount,
|
||||
wrappedList(dataError.amount.getFormattedCryptoAmount(fromToken)),
|
||||
wrappedList(expressDataError.amount.getFormattedCryptoAmount(fromToken)),
|
||||
),
|
||||
selectionType = selectionType,
|
||||
onProviderClick = onProviderClick,
|
||||
)
|
||||
}
|
||||
is DataError.ExchangeTooBigAmountError -> {
|
||||
is ExpressDataError.ExchangeTooBigAmountError -> {
|
||||
swapProvider.convertToAvailableFromProviderState(
|
||||
swapProvider = swapProvider,
|
||||
alertText = resourceReference(
|
||||
R.string.express_provider_max_amount,
|
||||
wrappedList(dataError.amount.getFormattedCryptoAmount(fromToken)),
|
||||
wrappedList(expressDataError.amount.getFormattedCryptoAmount(fromToken)),
|
||||
),
|
||||
selectionType = selectionType,
|
||||
onProviderClick = onProviderClick,
|
||||
|
|
@ -731,25 +738,25 @@ internal class StateBuilder(
|
|||
}
|
||||
}
|
||||
|
||||
private fun getWarningForError(dataError: DataError, fromToken: CryptoCurrency): SwapWarning {
|
||||
val providerErrorMessage = getProviderErrorMessage(dataError)
|
||||
val providerErrorTitle = getProviderErrorTitle(dataError)
|
||||
return when (dataError) {
|
||||
is DataError.ExchangeTooSmallAmountError -> SwapWarning.GeneralError(
|
||||
private fun getWarningForError(expressDataError: ExpressDataError, fromToken: CryptoCurrency): SwapWarning {
|
||||
val providerErrorMessage = getExpressErrorMessage(expressDataError)
|
||||
val providerErrorTitle = getExpressErrorTitle(expressDataError)
|
||||
return when (expressDataError) {
|
||||
is ExpressDataError.ExchangeTooSmallAmountError -> SwapWarning.GeneralError(
|
||||
notificationConfig = NotificationConfig(
|
||||
title = resourceReference(
|
||||
id = R.string.warning_express_too_minimal_amount_title,
|
||||
formatArgs = wrappedList(dataError.amount.getFormattedCryptoAmount(fromToken)),
|
||||
formatArgs = wrappedList(expressDataError.amount.getFormattedCryptoAmount(fromToken)),
|
||||
),
|
||||
subtitle = resourceReference(R.string.warning_express_wrong_amount_description),
|
||||
iconResId = R.drawable.ic_alert_circle_24,
|
||||
),
|
||||
)
|
||||
is DataError.ExchangeTooBigAmountError -> SwapWarning.GeneralError(
|
||||
is ExpressDataError.ExchangeTooBigAmountError -> SwapWarning.GeneralError(
|
||||
notificationConfig = NotificationConfig(
|
||||
title = resourceReference(
|
||||
id = R.string.warning_express_too_maximum_amount_title,
|
||||
formatArgs = wrappedList(dataError.amount.getFormattedCryptoAmount(fromToken)),
|
||||
formatArgs = wrappedList(expressDataError.amount.getFormattedCryptoAmount(fromToken)),
|
||||
),
|
||||
subtitle = resourceReference(R.string.warning_express_wrong_amount_description),
|
||||
iconResId = R.drawable.ic_alert_circle_24,
|
||||
|
|
@ -1045,81 +1052,43 @@ internal class StateBuilder(
|
|||
)
|
||||
}
|
||||
|
||||
fun createErrorTransaction(
|
||||
fun createErrorTransactionAlert(
|
||||
uiState: SwapStateHolder,
|
||||
swapTransactionState: SwapTransactionState,
|
||||
onAlertClick: () -> Unit,
|
||||
error: SwapTransactionState.Error,
|
||||
onDismiss: () -> Unit,
|
||||
onSupportClick: (String) -> Unit,
|
||||
): SwapStateHolder {
|
||||
val errorAlert = SwapTransactionErrorStateConverter(
|
||||
onSupportClick = onSupportClick,
|
||||
onDismiss = onDismiss,
|
||||
).convert(error)
|
||||
return uiState.copy(
|
||||
alert = SwapWarning.GenericWarning(
|
||||
message = if (swapTransactionState is SwapTransactionState.ExpressError) {
|
||||
getProviderErrorMessage(swapTransactionState.dataError)
|
||||
} else {
|
||||
null
|
||||
},
|
||||
onClick = onAlertClick,
|
||||
type = if (swapTransactionState is SwapTransactionState.NetworkError) {
|
||||
GenericWarningType.NETWORK
|
||||
} else {
|
||||
GenericWarningType.OTHER
|
||||
},
|
||||
),
|
||||
event = errorAlert?.let {
|
||||
triggeredEvent(
|
||||
data = SwapEvent.ShowAlert(errorAlert),
|
||||
onConsume = onDismiss,
|
||||
)
|
||||
} ?: consumedEvent(),
|
||||
changeCardsButtonState = ChangeCardsButtonState.ENABLED,
|
||||
)
|
||||
}
|
||||
|
||||
fun createDemoModeAlert(uiState: SwapStateHolder, onAlertClick: () -> Unit): SwapStateHolder {
|
||||
fun createDemoModeAlert(uiState: SwapStateHolder, onDismiss: () -> Unit): SwapStateHolder {
|
||||
return uiState.copy(
|
||||
alert = SwapWarning.GenericWarning(
|
||||
title = resourceReference(id = R.string.warning_demo_mode_title),
|
||||
message = resourceReference(id = R.string.warning_demo_mode_message),
|
||||
onClick = onAlertClick,
|
||||
type = GenericWarningType.OTHER,
|
||||
event = triggeredEvent(
|
||||
data = SwapEvent.ShowAlert(AlertDemoModeUM(onDismiss)),
|
||||
onConsume = onDismiss,
|
||||
),
|
||||
changeCardsButtonState = ChangeCardsButtonState.ENABLED,
|
||||
)
|
||||
}
|
||||
|
||||
private fun getProviderErrorMessage(dataError: DataError): TextReference {
|
||||
return when (dataError) {
|
||||
is DataError.SwapsAreUnavailableNowError -> resourceReference(
|
||||
id = R.string.express_error_swap_unavailable,
|
||||
formatArgs = wrappedList(dataError.code),
|
||||
)
|
||||
is DataError.ExchangeNotPossibleError -> resourceReference(
|
||||
id = R.string.warning_express_pair_unavailable_message,
|
||||
formatArgs = wrappedList(dataError.code),
|
||||
)
|
||||
is DataError.UnknownError -> resourceReference(R.string.common_unknown_error)
|
||||
is DataError.ExchangeProviderNotActiveError,
|
||||
is DataError.ExchangeProviderNotFoundError,
|
||||
is DataError.ExchangeProviderNotAvailableError,
|
||||
is DataError.ExchangeProviderProviderInternalError,
|
||||
-> resourceReference(
|
||||
id = R.string.express_error_swap_pair_unavailable,
|
||||
formatArgs = wrappedList(dataError.code),
|
||||
)
|
||||
else -> resourceReference(R.string.express_error_code, wrappedList(dataError.code.toString()))
|
||||
}
|
||||
}
|
||||
|
||||
private fun getProviderErrorTitle(dataError: DataError): TextReference {
|
||||
return when (dataError) {
|
||||
is DataError.ExchangeNotPossibleError -> resourceReference(
|
||||
id = R.string.warning_express_pair_unavailable_title,
|
||||
formatArgs = wrappedList(dataError.code),
|
||||
)
|
||||
is DataError.UnknownError -> resourceReference(R.string.common_error)
|
||||
else -> resourceReference(R.string.warning_express_refresh_required_title)
|
||||
}
|
||||
}
|
||||
|
||||
fun createAlert(
|
||||
uiState: SwapStateHolder,
|
||||
isPriceImpact: Boolean,
|
||||
token: String,
|
||||
providerType: ExchangeProviderType,
|
||||
onAlertClick: () -> Unit,
|
||||
onDismiss: () -> Unit,
|
||||
): SwapStateHolder {
|
||||
val message = when (providerType) {
|
||||
ExchangeProviderType.CEX -> resourceReference(R.string.swapping_alert_cex_description, wrappedList(token))
|
||||
|
|
@ -1136,29 +1105,38 @@ internal class StateBuilder(
|
|||
}
|
||||
}
|
||||
return uiState.copy(
|
||||
alert = SwapWarning.GenericWarning(
|
||||
title = resourceReference(R.string.swapping_alert_title),
|
||||
message = message,
|
||||
onClick = onAlertClick,
|
||||
type = GenericWarningType.OTHER,
|
||||
event = triggeredEvent(
|
||||
SwapEvent.ShowAlert(
|
||||
SwapAlertUM.FeesAlert(
|
||||
message = message,
|
||||
onConfirmClick = onDismiss,
|
||||
),
|
||||
),
|
||||
onConsume = onDismiss,
|
||||
),
|
||||
changeCardsButtonState = ChangeCardsButtonState.ENABLED,
|
||||
)
|
||||
}
|
||||
|
||||
fun addAlert(uiState: SwapStateHolder, message: TextReference? = null, onClick: () -> Unit): SwapStateHolder {
|
||||
fun addAlert(
|
||||
uiState: SwapStateHolder,
|
||||
message: TextReference = resourceReference(R.string.common_unknown_error),
|
||||
onDismiss: () -> Unit = { clearAlert(uiState) },
|
||||
): SwapStateHolder {
|
||||
return uiState.copy(
|
||||
alert = SwapWarning.GenericWarning(
|
||||
message = message,
|
||||
onClick = onClick,
|
||||
event = triggeredEvent(
|
||||
SwapEvent.ShowAlert(
|
||||
SwapAlertUM.GenericError(onDismiss, message),
|
||||
),
|
||||
onConsume = onDismiss,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun clearAlert(uiState: SwapStateHolder): SwapStateHolder = uiState.copy(alert = null)
|
||||
fun clearAlert(uiState: SwapStateHolder): SwapStateHolder = uiState.copy(event = consumedEvent())
|
||||
|
||||
fun addWarning(uiState: SwapStateHolder, message: TextReference?, onClick: () -> Unit): SwapStateHolder {
|
||||
val renewWarnings = uiState.warnings.filterNot { it is SwapWarning.GenericWarning }.toMutableList()
|
||||
val renewWarnings = uiState.warnings.toMutableList()
|
||||
renewWarnings.add(
|
||||
SwapWarning.GenericWarning(
|
||||
message = message,
|
||||
|
|
@ -1421,7 +1399,7 @@ internal class StateBuilder(
|
|||
is SwapState.SwapError -> getProviderStateForError(
|
||||
swapProvider = provider,
|
||||
fromToken = state.fromTokenInfo.cryptoCurrencyStatus.currency,
|
||||
dataError = state.error,
|
||||
expressDataError = state.error,
|
||||
onProviderClick = onProviderSelect,
|
||||
selectionType = ProviderState.SelectionType.SELECT,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,67 @@
|
|||
package com.tangem.feature.swap.ui
|
||||
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.tangem.common.ui.alerts.models.AlertUM
|
||||
import com.tangem.core.ui.components.BasicDialog
|
||||
import com.tangem.core.ui.components.DialogButtonUM
|
||||
import com.tangem.core.ui.event.EventEffect
|
||||
import com.tangem.core.ui.event.StateEvent
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.extensions.shareText
|
||||
import com.tangem.feature.swap.models.states.events.SwapEvent
|
||||
import com.tangem.feature.swap.presentation.R
|
||||
|
||||
@Composable
|
||||
internal fun SwapEventEffect(event: StateEvent<SwapEvent>) {
|
||||
val context = LocalContext.current
|
||||
var alertConfig by remember { mutableStateOf<AlertUM?>(value = null) }
|
||||
|
||||
val keyboardController = LocalSoftwareKeyboardController.current
|
||||
LaunchedEffect(key1 = alertConfig) {
|
||||
keyboardController?.hide()
|
||||
}
|
||||
|
||||
alertConfig?.let {
|
||||
SwapAlert(state = it, onDismiss = { alertConfig = null })
|
||||
}
|
||||
|
||||
EventEffect(
|
||||
event = event,
|
||||
onTrigger = { value ->
|
||||
when (value) {
|
||||
is SwapEvent.ShowAlert -> {
|
||||
alertConfig = value.alert
|
||||
}
|
||||
is SwapEvent.ShowShareDialog -> {
|
||||
context.shareText(value.txUrl)
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun SwapAlert(state: AlertUM, onDismiss: () -> Unit) {
|
||||
val confirmButton = DialogButtonUM(
|
||||
title = state.confirmButtonText.resolveReference(),
|
||||
onClick = {
|
||||
state.onConfirmClick()
|
||||
onDismiss()
|
||||
},
|
||||
)
|
||||
val dismissButton = DialogButtonUM(
|
||||
title = stringResource(id = R.string.common_cancel),
|
||||
onClick = onDismiss,
|
||||
)
|
||||
|
||||
BasicDialog(
|
||||
message = state.message.resolveReference(),
|
||||
confirmButton = confirmButton,
|
||||
onDismissDialog = onDismiss,
|
||||
title = state.title?.resolveReference(),
|
||||
dismissButton = dismissButton,
|
||||
)
|
||||
}
|
||||
|
|
@ -101,22 +101,9 @@ internal fun SwapScreenContent(state: SwapStateHolder, modifier: Modifier = Modi
|
|||
)
|
||||
}
|
||||
|
||||
if (state.alert != null) {
|
||||
val message = if (state.alert.type == GenericWarningType.NETWORK) {
|
||||
stringResource(id = R.string.disclaimer_error_loading)
|
||||
} else {
|
||||
state.alert.message?.resolveReference() ?: stringResource(id = R.string.common_unknown_error)
|
||||
}
|
||||
BasicDialog(
|
||||
title = state.alert.title?.resolveReference(),
|
||||
message = message,
|
||||
confirmButton = DialogButtonUM(
|
||||
title = stringResource(id = R.string.common_ok),
|
||||
onClick = state.alert.onClick,
|
||||
),
|
||||
onDismissDialog = state.alert.onClick,
|
||||
)
|
||||
}
|
||||
SwapEventEffect(
|
||||
event = state.event,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,41 @@
|
|||
package com.tangem.feature.swap.utils
|
||||
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.feature.swap.domain.models.ExpressDataError
|
||||
import com.tangem.feature.swap.presentation.R
|
||||
|
||||
internal fun getExpressErrorMessage(expressDataError: ExpressDataError): TextReference {
|
||||
return when (expressDataError) {
|
||||
is ExpressDataError.SwapsAreUnavailableNowError -> resourceReference(
|
||||
id = R.string.express_error_swap_unavailable,
|
||||
formatArgs = wrappedList(expressDataError.code),
|
||||
)
|
||||
is ExpressDataError.ExchangeNotPossibleError -> resourceReference(
|
||||
id = R.string.warning_express_pair_unavailable_message,
|
||||
formatArgs = wrappedList(expressDataError.code),
|
||||
)
|
||||
is ExpressDataError.UnknownError -> resourceReference(R.string.common_unknown_error)
|
||||
is ExpressDataError.ExchangeProviderNotActiveError,
|
||||
is ExpressDataError.ExchangeProviderNotFoundError,
|
||||
is ExpressDataError.ExchangeProviderNotAvailableError,
|
||||
is ExpressDataError.ExchangeProviderProviderInternalError,
|
||||
-> resourceReference(
|
||||
id = R.string.express_error_swap_pair_unavailable,
|
||||
formatArgs = wrappedList(expressDataError.code),
|
||||
)
|
||||
else -> resourceReference(R.string.express_error_code, wrappedList(expressDataError.code.toString()))
|
||||
}
|
||||
}
|
||||
|
||||
internal fun getExpressErrorTitle(expressDataError: ExpressDataError): TextReference {
|
||||
return when (expressDataError) {
|
||||
is ExpressDataError.ExchangeNotPossibleError -> resourceReference(
|
||||
id = R.string.warning_express_pair_unavailable_title,
|
||||
formatArgs = wrappedList(expressDataError.code),
|
||||
)
|
||||
is ExpressDataError.UnknownError -> resourceReference(R.string.common_error)
|
||||
else -> resourceReference(R.string.warning_express_refresh_required_title)
|
||||
}
|
||||
}
|
||||
|
|
@ -19,17 +19,24 @@ import com.tangem.core.ui.utils.InputNumberFormatter
|
|||
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
|
||||
import com.tangem.domain.feedback.FeedbackManager
|
||||
import com.tangem.domain.feedback.GetCardInfoUseCase
|
||||
import com.tangem.domain.feedback.SaveBlockchainErrorUseCase
|
||||
import com.tangem.domain.feedback.models.BlockchainErrorInfo
|
||||
import com.tangem.domain.feedback.models.FeedbackEmailType
|
||||
import com.tangem.domain.tokens.GetCryptoCurrencyStatusSyncUseCase
|
||||
import com.tangem.domain.tokens.GetCurrencyStatusUpdatesUseCase
|
||||
import com.tangem.domain.tokens.UpdateDelayedNetworkStatusUseCase
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
||||
import com.tangem.feature.swap.analytics.SwapEvents
|
||||
import com.tangem.feature.swap.domain.BlockchainInteractor
|
||||
import com.tangem.feature.swap.domain.SwapInteractor
|
||||
import com.tangem.feature.swap.domain.models.DataError
|
||||
import com.tangem.feature.swap.domain.models.ExpressDataError
|
||||
import com.tangem.feature.swap.domain.models.ExpressException
|
||||
import com.tangem.feature.swap.domain.models.SwapAmount
|
||||
import com.tangem.feature.swap.domain.models.domain.*
|
||||
|
|
@ -64,7 +71,6 @@ typealias SuccessLoadedSwapData = Map<SwapProvider, SwapState.QuotesLoadedState>
|
|||
@Suppress("LargeClass", "LongParameterList")
|
||||
@HiltViewModel
|
||||
internal class SwapViewModel @Inject constructor(
|
||||
private val swapInteractorFactory: SwapInteractor.Factory,
|
||||
private val blockchainInteractor: BlockchainInteractor,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
|
|
@ -73,6 +79,11 @@ internal class SwapViewModel @Inject constructor(
|
|||
private val getCryptoCurrencyStatusUseCase: GetCryptoCurrencyStatusSyncUseCase,
|
||||
private val updateDelayedCurrencyStatusUseCase: UpdateDelayedNetworkStatusUseCase,
|
||||
private val getCurrencyStatusUpdatesUseCase: GetCurrencyStatusUpdatesUseCase,
|
||||
private val getUserWalletUseCase: GetUserWalletUseCase,
|
||||
private val getCardInfoUseCase: GetCardInfoUseCase,
|
||||
private val saveBlockchainErrorUseCase: SaveBlockchainErrorUseCase,
|
||||
private val feedbackManager: FeedbackManager,
|
||||
swapInteractorFactory: SwapInteractor.Factory,
|
||||
savedStateHandle: SavedStateHandle,
|
||||
) : ViewModel(), DefaultLifecycleObserver {
|
||||
|
||||
|
|
@ -90,6 +101,7 @@ internal class SwapViewModel @Inject constructor(
|
|||
private val swapInteractor = swapInteractorFactory.create(userWalletId)
|
||||
|
||||
private lateinit var initialCryptoCurrencyStatus: CryptoCurrencyStatus
|
||||
private var userWallet: UserWallet by Delegates.notNull()
|
||||
|
||||
private var isBalanceHidden = true
|
||||
|
||||
|
|
@ -123,7 +135,10 @@ internal class SwapViewModel @Inject constructor(
|
|||
|
||||
private val isUserResolvableError: (SwapState) -> Boolean = {
|
||||
it is SwapState.SwapError &&
|
||||
(it.error is DataError.ExchangeTooSmallAmountError || it.error is DataError.ExchangeTooBigAmountError)
|
||||
(
|
||||
it.error is ExpressDataError.ExchangeTooSmallAmountError ||
|
||||
it.error is ExpressDataError.ExchangeTooBigAmountError
|
||||
)
|
||||
}
|
||||
|
||||
private val fromTokenBalanceJobHolder = JobHolder()
|
||||
|
|
@ -136,9 +151,11 @@ internal class SwapViewModel @Inject constructor(
|
|||
viewModelScope.launch(dispatchers.io) {
|
||||
val cryptoCurrencyStatus =
|
||||
getCryptoCurrencyStatusUseCase(userWalletId, initialCryptoCurrency.id).getOrNull()
|
||||
if (cryptoCurrencyStatus == null) {
|
||||
uiState = stateBuilder.addAlert(uiState = uiState, onClick = swapRouter::back)
|
||||
val wallet = getUserWalletUseCase(userWalletId).getOrNull()
|
||||
if (cryptoCurrencyStatus == null || wallet == null) {
|
||||
uiState = stateBuilder.addAlert(uiState = uiState, onDismiss = swapRouter::back)
|
||||
} else {
|
||||
userWallet = wallet
|
||||
initialCryptoCurrencyStatus = cryptoCurrencyStatus
|
||||
initTokens(isInitiallyReversed)
|
||||
}
|
||||
|
|
@ -226,7 +243,7 @@ internal class SwapViewModel @Inject constructor(
|
|||
|
||||
uiState = stateBuilder.createInitialErrorState(
|
||||
uiState,
|
||||
(it as? ExpressException)?.dataError?.code ?: DataError.UnknownError.code,
|
||||
(it as? ExpressException)?.expressDataError?.code ?: ExpressDataError.UnknownError.code,
|
||||
) {
|
||||
uiState = stateBuilder.createInitialLoadingState(
|
||||
initialCurrency = initialCryptoCurrency,
|
||||
|
|
@ -414,7 +431,7 @@ internal class SwapViewModel @Inject constructor(
|
|||
swapProvider = provider,
|
||||
fromToken = state.fromTokenInfo,
|
||||
toToken = dataState.toCryptoCurrency,
|
||||
dataError = state.error,
|
||||
expressDataError = state.error,
|
||||
includeFeeInAmount = state.includeFeeInAmount,
|
||||
isReverseSwapPossible = isReverseSwapPossible(),
|
||||
)
|
||||
|
|
@ -423,7 +440,7 @@ internal class SwapViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun sendErrorAnalyticsEvent(error: DataError, provider: SwapProvider) {
|
||||
private fun sendErrorAnalyticsEvent(error: ExpressDataError, provider: SwapProvider) {
|
||||
val receiveToken = dataState.toCryptoCurrency?.currency?.let {
|
||||
"${it.network.backendId}:${it.symbol}"
|
||||
}
|
||||
|
|
@ -573,20 +590,19 @@ internal class SwapViewModel @Inject constructor(
|
|||
|
||||
swapRouter.openScreen(SwapNavScreen.Success)
|
||||
}
|
||||
is SwapTransactionState.UserCancelled -> {
|
||||
startLoadingQuotesFromLastState()
|
||||
}
|
||||
is SwapTransactionState.DemoMode -> {
|
||||
startLoadingQuotesFromLastState()
|
||||
SwapTransactionState.DemoMode -> {
|
||||
uiState = stateBuilder.createDemoModeAlert(uiState) {
|
||||
uiState = stateBuilder.clearAlert(uiState)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
is SwapTransactionState.Error -> {
|
||||
startLoadingQuotesFromLastState()
|
||||
uiState = stateBuilder.createErrorTransaction(uiState, it) {
|
||||
uiState = stateBuilder.clearAlert(uiState)
|
||||
}
|
||||
uiState = stateBuilder.createErrorTransactionAlert(
|
||||
uiState = uiState,
|
||||
error = it,
|
||||
onDismiss = { uiState = stateBuilder.clearAlert(uiState) },
|
||||
onSupportClick = ::onFailedTxEmailClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
}.onFailure {
|
||||
|
|
@ -657,23 +673,28 @@ internal class SwapViewModel @Inject constructor(
|
|||
uiState = stateBuilder.dismissBottomSheet(uiState)
|
||||
startLoadingQuotesFromLastState(isSilent = true)
|
||||
}
|
||||
is SwapTransactionState.UserCancelled -> Unit
|
||||
else -> {
|
||||
uiState = stateBuilder.createErrorTransaction(uiState, it) {
|
||||
is SwapTransactionState.Error -> {
|
||||
uiState = stateBuilder.createErrorTransactionAlert(
|
||||
uiState = uiState,
|
||||
error = it,
|
||||
onDismiss = { uiState = stateBuilder.clearAlert(uiState) },
|
||||
onSupportClick = ::onFailedTxEmailClick,
|
||||
)
|
||||
}
|
||||
SwapTransactionState.DemoMode -> {
|
||||
uiState = stateBuilder.createDemoModeAlert(uiState) {
|
||||
uiState = stateBuilder.clearAlert(uiState)
|
||||
}
|
||||
}
|
||||
}
|
||||
}.onFailure { makeDefaultAlert() }
|
||||
}.onFailure { showGenericError(it.message.orEmpty()) }
|
||||
}.onFailure {
|
||||
Timber.e(it.message.orEmpty())
|
||||
makeDefaultAlert()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun showGenericError(message: String) {
|
||||
makeDefaultAlert(resourceReference(R.string.common_unknown_error))
|
||||
Timber.e(message)
|
||||
}
|
||||
|
||||
private fun onSearchEntered(searchQuery: String) {
|
||||
viewModelScope.launch(dispatchers.io) {
|
||||
val tokenDataState = dataState.tokensDataState ?: return@launch
|
||||
|
|
@ -880,15 +901,11 @@ internal class SwapViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun makeDefaultAlert() {
|
||||
uiState = stateBuilder.addAlert(uiState) {
|
||||
uiState = stateBuilder.clearAlert(uiState)
|
||||
}
|
||||
uiState = stateBuilder.addAlert(uiState)
|
||||
}
|
||||
|
||||
private fun makeDefaultAlert(message: TextReference) {
|
||||
uiState = stateBuilder.addAlert(uiState, message) {
|
||||
uiState = stateBuilder.clearAlert(uiState)
|
||||
}
|
||||
uiState = stateBuilder.addAlert(uiState, message)
|
||||
}
|
||||
|
||||
@Suppress("LongMethod", "CyclomaticComplexMethod")
|
||||
|
|
@ -1130,7 +1147,8 @@ internal class SwapViewModel @Inject constructor(
|
|||
toToken.currency.id.value
|
||||
}
|
||||
|
||||
return groupToFind.available.find { idToFind == it.currencyStatus.currency.id.value }?.providers ?: emptyList()
|
||||
return groupToFind.available.find { idToFind == it.currencyStatus.currency.id.value }?.providers
|
||||
?: emptyList()
|
||||
}
|
||||
|
||||
private fun Map<SwapProvider, SwapState>.getLastLoadedSuccessStates(): SuccessLoadedSwapData {
|
||||
|
|
@ -1218,6 +1236,33 @@ internal class SwapViewModel @Inject constructor(
|
|||
analyticsEventHandler.send(event = event)
|
||||
}
|
||||
|
||||
private fun onFailedTxEmailClick(errorMessage: String) {
|
||||
viewModelScope.launch {
|
||||
val network = initialCryptoCurrencyStatus.currency.network
|
||||
val cardInfo = getCardInfoUseCase(userWallet.scanResponse).getOrElse { error("CardInfo must be not null") }
|
||||
|
||||
saveBlockchainErrorUseCase(
|
||||
error = BlockchainErrorInfo(
|
||||
errorMessage = errorMessage,
|
||||
blockchainId = network.id.value,
|
||||
derivationPath = network.derivationPath.value,
|
||||
destinationAddress = dataState.swapDataModel?.transaction?.txTo.orEmpty(),
|
||||
tokenSymbol = initialCryptoCurrency.symbol,
|
||||
amount = dataState.amount.orEmpty(),
|
||||
fee = dataState.selectedFee?.feeCryptoFormatted.orEmpty(),
|
||||
),
|
||||
)
|
||||
|
||||
val email = FeedbackEmailType.SwapProblem(
|
||||
cardInfo = cardInfo,
|
||||
providerName = dataState.selectedProvider?.name.orEmpty(),
|
||||
txId = dataState.swapDataModel?.transaction?.txId.orEmpty(),
|
||||
)
|
||||
|
||||
feedbackManager.sendEmail(email)
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val INITIAL_AMOUNT = ""
|
||||
const val UPDATE_DELAY = 10000L
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue