Updated on 2026-08-14

This commit is contained in:
Tangem 2024-01-22 16:20:14 +03:00
parent f7f2ab7f88
commit 0b037f2b2d
26 changed files with 385 additions and 80 deletions

View file

@ -2,6 +2,7 @@ package com.tangem.tap.common.redux.legacy
import com.tangem.domain.redux.LegacyAction
import com.tangem.tap.common.feedback.RateCanBeBetterEmail
import com.tangem.tap.common.feedback.SendTransactionFailedEmail
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.store
@ -20,6 +21,11 @@ internal object LegacyMiddleware {
GlobalAction.Onboarding.Start(action.scanResponse, canSkipBackup = action.canSkipBackup),
)
}
is LegacyAction.SendEmailTransactionFailed -> {
store.state.globalState.feedbackManager?.sendEmail(
SendTransactionFailedEmail(action.errorMessage),
)
}
}
next(action)
}

View file

@ -31,13 +31,13 @@ internal object TransactionDomainModule {
@ViewModelScoped
fun provideSendTransactionUseCase(
isDemoCardUseCase: IsDemoCardUseCase,
walletManagersFacade: WalletManagersFacade,
cardSdkConfigRepository: CardSdkConfigRepository,
transactionRepository: TransactionRepository,
): SendTransactionUseCase {
return SendTransactionUseCase(
isDemoCardUseCase = isDemoCardUseCase,
cardSdkConfigRepository = cardSdkConfigRepository,
walletManagersFacade = walletManagersFacade,
transactionRepository = transactionRepository,
)
}

View file

@ -136,6 +136,7 @@
<string name="common_transfer">Перевод</string>
<string name="common_understand">Я понял</string>
<string name="common_unreachable">Недоступно</string>
<string name="common_unknown_error">Произошла ошибка. Пожалуйста, попробуйте снова.</string>
<string name="common_yes">Да</string>
<string name="contract_address_copied_message">Адрес контракта скопирован!</string>
<string name="currency_subtitle_expanded">Доступные сети</string>
@ -448,8 +449,9 @@
<string name="send_amount_label">Сумма</string>
<string name="send_amount_substract">Вычесть из суммы отправки</string>
<string name="send_amount_substract_footer">Сумма к получению %s</string>
<string name="send_alert_button_request_support">Поддержка</string>
<string name="send_alert_transaction_failed_title">Транзакция не выполнена</string>
<string name="send_alert_transaction_failed_text">Причина: %1$s\Код:%2$s</string>
<string name="send_alert_transaction_failed_text">Причина: %1$s\nКод: %2$s</string>
<string name="send_date_format">%1$s в %2$s</string>
<string name="send_destination_hint_address">Адрес</string>
<string name="send_destination_tag_field">Код назначения</string>
@ -528,7 +530,6 @@
<string name="swapping_approve_information_title">Подтвердить</string>
<string name="swapping_error_wrapper">Ошибка: %s</string>
<string name="swapping_from_title">Вы отправляете</string>
<string name="swapping_generic_error">Произошла ошибка. Пожалуйста, попробуйте еще раз.</string>
<string name="swapping_give_permission">Дать разрешение</string>
<string name="swapping_high_price_impact_description">Обмен этой суммы выбранных токенов может вызвать значительные колебания цены и уменьшить получаемую сумму.</string>
<string name="swapping_insufficient_funds">Недостаточно средств</string>

View file

@ -327,7 +327,6 @@
<string name="swapping_approve_information_text">批准被視為所有去中心化交易所的行業標準,並保護您的錢包在未經您許可的情況下不被智能合約訪問。按照設計,智能合約無法訪問您的代幣,除非您從您的終端批准訪問。通過“解鎖”您的代幣,您將獲得 1inch 智能合約使用您的資產的權限。網絡的礦工將獲得Gas Fee由您支付作為補償以在區塊鏈上記錄此操作。一旦獲得許可您就可以交易您的代幣。</string>
<string name="swapping_approve_information_title">批准</string>
<string name="swapping_error_wrapper">錯誤: %s</string>
<string name="swapping_generic_error">有錯誤。請再試一遍</string>
<string name="swapping_give_permission">賦予權限</string>
<string name="swapping_high_price_impact_description">在此代幣交換的數量將對價格產生重大影響,並降低您收到的數量</string>
<string name="swapping_insufficient_funds">餘額不足</string>

View file

@ -135,6 +135,7 @@
<string name="common_transfer">Transfer</string>
<string name="common_understand">I understand</string>
<string name="common_unreachable">Unreachable</string>
<string name="common_unknown_error">There was an error. Please try again.</string>
<string name="common_yes">Yes</string>
<string name="contract_address_copied_message">Contract address copied!</string>
<string name="currency_subtitle_expanded">Available networks</string>
@ -447,8 +448,9 @@
<string name="send_amount_label">Amount</string>
<string name="send_amount_substract">Subtract from send amount</string>
<string name="send_amount_substract_footer">The recipient will receive %s</string>
<string name="send_alert_button_request_support">Support</string>
<string name="send_alert_transaction_failed_title">The transaction is not completed</string>
<string name="send_alert_transaction_failed_text">Reason: %1$s\nCode:%2$s</string>
<string name="send_alert_transaction_failed_text">Reason: %1$s\nCode: %2$s</string>
<string name="send_confirm_label">Confirm</string>
<string name="send_date_format">%1$s at %2$s</string>
<string name="send_destination_hint_address">Address</string>
@ -519,6 +521,7 @@
<string name="send_transaction_success">Transaction has been successfully signed and sent to the blockchain node. Wallet balance will be updated in a while</string>
<string name="send_validation_invalid_address">Invalid address</string>
<string name="sent_transaction_sent_title">Transaction sent</string>
<string name="send_wallet_balance_format">%s (%s)</string>
<string name="shop_buy_now">Buy now</string>
<string name="shop_i_have_a_promo_code">I have a promo code…</string>
<string name="shop_one_wallet">Tangem Wallet</string>
@ -545,7 +548,6 @@
<string name="swapping_approve_information_title">Approve</string>
<string name="swapping_error_wrapper">Error: %s</string>
<string name="swapping_from_title">You swap</string>
<string name="swapping_generic_error">There was an error. Please try again.</string>
<string name="swapping_give_permission">Give Permission</string>
<string name="swapping_high_price_impact_description">Swapping this amount of selected tokens will cause a significant price impact and reduce your outcome.</string>
<string name="swapping_insufficient_funds">Insufficient funds</string>

View file

@ -7,10 +7,7 @@ import com.tangem.blockchain.blockchains.stellar.StellarMemo
import com.tangem.blockchain.blockchains.stellar.StellarTransactionExtras
import com.tangem.blockchain.blockchains.ton.TonTransactionExtras
import com.tangem.blockchain.blockchains.xrp.XrpTransactionBuilder
import com.tangem.blockchain.common.Amount
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.TransactionData
import com.tangem.blockchain.common.TransactionExtras
import com.tangem.blockchain.common.*
import com.tangem.blockchain.common.transaction.Fee
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.transaction.TransactionRepository
@ -44,6 +41,21 @@ internal class DefaultTransactionRepository(
)
}
override suspend fun sendTransaction(
txData: TransactionData,
signer: CommonSigner,
userWalletId: UserWalletId,
network: Network,
) = withContext(coroutineDispatcherProvider.io) {
val blockchain = Blockchain.fromId(network.id.value)
val walletManager = walletManagersFacade.getOrCreateWalletManager(
userWalletId = userWalletId,
blockchain = blockchain,
derivationPath = network.derivationPath.value,
)
(walletManager as TransactionSender).send(txData, signer)
}
private fun getMemoExtras(networkId: String, memo: String?): TransactionExtras? {
val blockchain = Blockchain.fromId(networkId)
if (memo == null) return null

View file

@ -13,4 +13,9 @@ sealed interface LegacyAction : Action {
* BackupAction.CheckForUnfinishedBackup, GlobalAction.Onboarding.StartForUnfinishedBackup
*/
data class StartOnboardingProcess(val scanResponse: ScanResponse, val canSkipBackup: Boolean = true) : LegacyAction
/**
* Sending an email to support when sending transaction failed
*/
data class SendEmailTransactionFailed(val errorMessage: String) : LegacyAction
}

View file

@ -13,9 +13,13 @@ dependencies {
implementation(deps.arrow.core)
implementation(projects.core.utils)
implementation(projects.core.ui)
/** Tangem SDKs */
implementation(deps.tangem.card.core)
implementation(deps.tangem.card.android) {
exclude(module = "joda-time")
}
implementation(deps.tangem.blockchain)
implementation(projects.domain.models)

View file

@ -1,8 +1,10 @@
package com.tangem.domain.transaction
import com.tangem.blockchain.common.Amount
import com.tangem.blockchain.common.CommonSigner
import com.tangem.blockchain.common.TransactionData
import com.tangem.blockchain.common.transaction.Fee
import com.tangem.blockchain.extensions.SimpleResult
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.wallets.models.UserWalletId
@ -17,4 +19,11 @@ interface TransactionRepository {
userWalletId: UserWalletId,
network: Network,
): TransactionData?
suspend fun sendTransaction(
txData: TransactionData,
signer: CommonSigner,
userWalletId: UserWalletId,
network: Network,
): SimpleResult
}

View file

@ -1,5 +1,7 @@
package com.tangem.domain.transaction.error
import com.tangem.core.ui.extensions.TextReference
sealed class SendTransactionError {
object DemoCardError : SendTransactionError()
@ -8,9 +10,12 @@ sealed class SendTransactionError {
data class NetworkError(val message: String?) : SendTransactionError()
data class BlockchainSdkError(val code: Int, val cause: Throwable?) : SendTransactionError()
data class BlockchainSdkError(val code: Int, val message: String?) : SendTransactionError()
object UserCancelledError : SendTransactionError()
data class TangemSdkError(val code: Int, val cause: Throwable?) : SendTransactionError()
data class TangemSdkError(val code: Int, val messageReference: TextReference) : SendTransactionError()
data class UnknownError(val ex: Exception? = null) : SendTransactionError()
companion object {

View file

@ -8,19 +8,23 @@ import com.tangem.blockchain.common.TransactionData
import com.tangem.blockchain.extensions.SimpleResult
import com.tangem.blockchain.network.ResultChecker
import com.tangem.common.core.TangemSdkError
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.domain.card.repository.CardSdkConfigRepository
import com.tangem.domain.common.TapWorkarounds.isStart2Coin
import com.tangem.domain.demo.IsDemoCardUseCase
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.transaction.R
import com.tangem.domain.transaction.TransactionRepository
import com.tangem.domain.transaction.error.SendTransactionError
import com.tangem.domain.transaction.error.SendTransactionError.Companion.USER_CANCELLED_ERROR_CODE
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.sdk.extensions.localizedDescriptionRes
class SendTransactionUseCase(
private val isDemoCardUseCase: IsDemoCardUseCase,
private val cardSdkConfigRepository: CardSdkConfigRepository,
private val walletManagersFacade: WalletManagersFacade,
private val transactionRepository: TransactionRepository,
) {
suspend operator fun invoke(
txData: TransactionData,
@ -37,7 +41,7 @@ class SendTransactionUseCase(
if (isDemoCardUseCase(cardId = userWallet.cardId)) {
SendTransactionError.DemoCardError.left()
} else {
walletManagersFacade.sendTransaction(
transactionRepository.sendTransaction(
txData = txData,
signer = signer,
userWalletId = userWallet.walletId,
@ -64,29 +68,28 @@ class SendTransactionUseCase(
private fun handleError(result: SimpleResult.Failure): SendTransactionError {
if (ResultChecker.isNetworkError(result)) return SendTransactionError.NetworkError(result.error.message)
val error = result.error as? BlockchainSdkError ?: return SendTransactionError.UnknownError()
when (error) {
return when (error) {
is BlockchainSdkError.WrappedTangemError -> {
val errorByCode = mapErrorByCode(error)
if (errorByCode != null) {
return errorByCode
if (error.code == USER_CANCELLED_ERROR_CODE) {
SendTransactionError.UserCancelledError
} else {
val tangemError = error.tangemError
if (tangemError is TangemSdkError) {
val resource = tangemError.localizedDescriptionRes()
val resId = resource.resId ?: R.string.common_unknown_error
val resArgs = resource.args.map { it.value }
val textReference = resourceReference(resId, wrappedList(resArgs))
SendTransactionError.TangemSdkError(tangemError.code, textReference)
} else {
SendTransactionError.BlockchainSdkError(error.code, tangemError.customMessage)
}
}
val tangemSdkError = error.tangemError as? TangemSdkError ?: return SendTransactionError.UnknownError()
if (tangemSdkError is TangemSdkError.UserCancelled) return SendTransactionError.UserCancelledError
return SendTransactionError.TangemSdkError(tangemSdkError.code, tangemSdkError.cause)
}
else -> {
return SendTransactionError.TangemSdkError(error.code, error.cause)
}
}
}
private fun mapErrorByCode(error: BlockchainSdkError.WrappedTangemError): SendTransactionError? {
return when (error.code) {
USER_CANCELLED_ERROR_CODE -> {
return SendTransactionError.UserCancelledError
}
else -> {
null
SendTransactionError.BlockchainSdkError(
code = error.code,
message = error.customMessage,
)
}
}
}

View file

@ -23,6 +23,7 @@ dependencies {
implementation(deps.lifecycle.compose)
implementation(deps.jodatime)
implementation(deps.timber)
implementation(deps.reKotlin)
/** Compose */
implementation(deps.compose.accompanist.systemUiController)

View file

@ -0,0 +1,47 @@
package com.tangem.features.send.impl.presentation.state
import androidx.compose.runtime.Immutable
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.features.send.impl.R
@Immutable
internal sealed class SendAlertState {
abstract val title: TextReference?
abstract val message: TextReference
open val confirmButtonText: TextReference = resourceReference(id = R.string.common_ok)
open val onConfirmClick: (() -> Unit)? = null
data class GenericError(
override val title: TextReference? = resourceReference(id = R.string.send_alert_transaction_failed_title),
override val onConfirmClick: (() -> Unit),
) : SendAlertState() {
override val message: TextReference = resourceReference(R.string.common_unknown_error)
override val confirmButtonText: TextReference =
resourceReference(id = R.string.send_alert_button_request_support)
}
data class TransactionError(
val code: String,
val cause: String?,
val causeTextReference: TextReference? = null,
override val onConfirmClick: (() -> Unit),
) : SendAlertState() {
override val title: TextReference = resourceReference(id = R.string.send_alert_transaction_failed_title)
override val message: TextReference = resourceReference(
id = R.string.send_alert_transaction_failed_text,
formatArgs = wrappedList(causeTextReference ?: cause.orEmpty(), code),
)
override val confirmButtonText: TextReference =
resourceReference(id = R.string.send_alert_button_request_support)
}
data class DemoMode(
override val onConfirmClick: () -> Unit,
) : SendAlertState() {
override val title: TextReference = resourceReference(id = R.string.warning_demo_mode_title)
override val message: TextReference = resourceReference(id = R.string.warning_demo_mode_message)
}
}

View file

@ -0,0 +1,12 @@
package com.tangem.features.send.impl.presentation.state
import androidx.compose.runtime.Immutable
import com.tangem.core.ui.extensions.TextReference
@Immutable
internal sealed class SendEvent {
data class ShowSnackBar(val text: TextReference) : SendEvent()
data class ShowAlert(val alert: SendAlertState) : SendEvent()
}

View file

@ -0,0 +1,50 @@
package com.tangem.features.send.impl.presentation.state
import com.tangem.core.ui.event.consumedEvent
import com.tangem.core.ui.event.triggeredEvent
import com.tangem.domain.transaction.error.SendTransactionError
import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents
import com.tangem.utils.Provider
/**
* Factory to produce event state for [SendUiState]
*
* @param currentStateProvider [Provider] of [SendUiState]
* @param clickIntents [SendClickIntents]
*/
internal class SendEventStateFactory(
val currentStateProvider: Provider<SendUiState>,
val clickIntents: SendClickIntents,
) {
private val sendTransactionErrorConverter by lazy { SendTransactionAlertConverter(clickIntents) }
fun onConsumeEventState(): SendUiState {
return currentStateProvider().copy(event = consumedEvent())
}
fun getSendTransactionErrorState(error: SendTransactionError?, onConsume: () -> Unit): SendUiState {
val state = currentStateProvider()
val event = error?.let {
sendTransactionErrorConverter.convert(error)?.let {
triggeredEvent<SendEvent>(SendEvent.ShowAlert(it), onConsume)
}
}
return state.copy(
event = event ?: consumedEvent(),
)
}
fun getGenericErrorState(error: Throwable? = null, onConsume: () -> Unit): SendUiState {
val state = currentStateProvider()
return state.copy(
event = triggeredEvent(
data = SendEvent.ShowAlert(
SendAlertState.GenericError(
onConfirmClick = { clickIntents.onFailedTxEmailClick(error?.localizedMessage.orEmpty()) },
),
),
onConsume = onConsume,
),
)
}
}

View file

@ -5,6 +5,7 @@ import arrow.core.getOrElse
import com.tangem.blockchain.common.TransactionData
import com.tangem.blockchain.common.transaction.TransactionFee
import com.tangem.core.ui.components.currency.tokenicon.converter.CryptoCurrencyToIconStateConverter
import com.tangem.core.ui.event.consumedEvent
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.utils.BigDecimalFormatter
import com.tangem.domain.appcurrency.model.AppCurrency
@ -44,7 +45,6 @@ internal class SendStateFactory(
) {
private val iconStateConverter by lazy(::CryptoCurrencyToIconStateConverter)
private val amountFieldConverter by lazy { SendAmountFieldConverter(clickIntents) }
private val amountFieldChangeConverter by lazy { SendAmountFieldChangeConverter(currentStateProvider) }
private val customFeeFieldConverter by lazy {
@ -92,6 +92,7 @@ internal class SendStateFactory(
fun getInitialState(): SendUiState = SendUiState(
clickIntents = clickIntents,
currentState = MutableStateFlow(SendUiStateType.Amount),
event = consumedEvent(),
)
fun getReadyState(): SendUiState {

View file

@ -0,0 +1,44 @@
package com.tangem.features.send.impl.presentation.state
import com.tangem.domain.transaction.error.SendTransactionError
import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents
import com.tangem.utils.converter.Converter
internal class SendTransactionAlertConverter(
private val clickIntents: SendClickIntents,
) : Converter<SendTransactionError, SendAlertState?> {
override fun convert(value: SendTransactionError): SendAlertState? {
return when (value) {
SendTransactionError.DemoCardError -> SendAlertState.DemoMode(
onConfirmClick = { clickIntents.popBackStack() },
)
is SendTransactionError.TangemSdkError -> SendAlertState.TransactionError(
code = value.code.toString(),
cause = null,
causeTextReference = value.messageReference,
onConfirmClick = { clickIntents.onFailedTxEmailClick(value.code.toString()) },
)
is SendTransactionError.BlockchainSdkError -> SendAlertState.TransactionError(
code = value.code.toString(),
cause = value.message,
onConfirmClick = { clickIntents.onFailedTxEmailClick("${value.code}: ${value.message.orEmpty()}") },
)
is SendTransactionError.DataError -> SendAlertState.TransactionError(
code = "",
cause = value.message,
onConfirmClick = { clickIntents.onFailedTxEmailClick(value.message.orEmpty()) },
)
is SendTransactionError.NetworkError -> SendAlertState.TransactionError(
code = "",
cause = value.message,
onConfirmClick = { clickIntents.onFailedTxEmailClick(value.message.orEmpty()) },
)
is SendTransactionError.UnknownError -> SendAlertState.TransactionError(
code = "",
cause = value.ex?.localizedMessage,
onConfirmClick = { clickIntents.onFailedTxEmailClick(value.ex?.localizedMessage.orEmpty()) },
)
else -> null
}
}
}

View file

@ -5,6 +5,7 @@ import androidx.compose.runtime.Stable
import androidx.paging.PagingData
import com.tangem.blockchain.common.transaction.Fee
import com.tangem.core.ui.components.currency.tokenicon.TokenIconState
import com.tangem.core.ui.event.StateEvent
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.features.send.impl.presentation.domain.SendRecipientListContent
@ -31,6 +32,7 @@ internal data class SendUiState(
val sendState: SendStates.SendState = SendStates.SendState(),
val recipientList: MutableStateFlow<PagingData<SendRecipientListContent>> = MutableStateFlow(PagingData.empty()),
val currentState: MutableStateFlow<SendUiStateType>,
val event: StateEvent<SendEvent>,
)
@Stable

View file

@ -0,0 +1,73 @@
package com.tangem.features.send.impl.presentation.ui
import androidx.compose.material3.SnackbarHostState
import androidx.compose.runtime.*
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import com.tangem.core.ui.components.BasicDialog
import com.tangem.core.ui.components.DialogButton
import com.tangem.core.ui.event.EventEffect
import com.tangem.core.ui.event.StateEvent
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.features.send.impl.R
import com.tangem.features.send.impl.presentation.state.SendAlertState
import com.tangem.features.send.impl.presentation.state.SendEvent
@Composable
internal fun SendEventEffect(event: StateEvent<SendEvent>, snackbarHostState: SnackbarHostState) {
val resources = LocalContext.current.resources
var alertConfig by remember { mutableStateOf<SendAlertState?>(value = null) }
alertConfig?.let {
SendAlert(state = it, onDismiss = { alertConfig = null })
}
EventEffect(
event = event,
onTrigger = { value ->
when (value) {
is SendEvent.ShowSnackBar -> {
snackbarHostState.showSnackbar(message = value.text.resolveReference(resources))
}
is SendEvent.ShowAlert -> {
alertConfig = value.alert
}
}
},
)
}
@Composable
internal fun SendAlert(state: SendAlertState, onDismiss: () -> Unit) {
val confirmButton: DialogButton
val dismissButton: DialogButton?
val onActionClick = state.onConfirmClick
if (onActionClick != null) {
confirmButton = DialogButton(
title = state.confirmButtonText.resolveReference(),
onClick = {
onActionClick()
onDismiss()
},
)
dismissButton = DialogButton(
title = stringResource(id = R.string.common_cancel),
onClick = onDismiss,
)
} else {
confirmButton = DialogButton(
title = state.confirmButtonText.resolveReference(),
onClick = onDismiss,
)
dismissButton = null
}
BasicDialog(
message = state.message.resolveReference(),
confirmButton = confirmButton,
onDismissDialog = onDismiss,
title = state.title?.resolveReference(),
dismissButton = dismissButton,
)
}

View file

@ -7,8 +7,10 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.systemBarsPadding
import androidx.compose.material3.SnackbarHostState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
@ -28,6 +30,7 @@ import com.tangem.features.send.impl.presentation.ui.send.SendContent
internal fun SendScreen(uiState: SendUiState) {
val currentState = uiState.currentState.collectAsStateWithLifecycle()
val isSuccess = uiState.sendState.isSuccess
val snackbarHostState = remember { SnackbarHostState() }
BackHandler { uiState.clickIntents.onBackClick() }
Column(
modifier = Modifier
@ -65,6 +68,11 @@ internal fun SendScreen(uiState: SendUiState) {
)
SendNavigationButtons(uiState)
}
SendEventEffect(
event = uiState.event,
snackbarHostState = snackbarHostState,
)
}
@Composable

View file

@ -4,6 +4,7 @@ import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.features.send.impl.presentation.state.fee.FeeType
@Suppress("TooManyFunctions")
interface SendClickIntents {
fun popBackStack()
@ -16,6 +17,8 @@ interface SendClickIntents {
fun onQrCodeScanClick()
fun onFailedTxEmailClick(errorMessage: String)
fun onTokenDetailsClick(userWalletId: UserWalletId, currency: CryptoCurrency)
// region Amount

View file

@ -8,8 +8,11 @@ import androidx.paging.PagingData
import arrow.core.getOrElse
import com.tangem.blockchain.blockchains.xrp.XrpAddressService
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.TransactionData
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.redux.LegacyAction
import com.tangem.domain.redux.ReduxStateHolder
import com.tangem.domain.tokens.GetCryptoCurrenciesUseCase
import com.tangem.domain.tokens.GetCurrencyStatusUpdatesUseCase
import com.tangem.domain.tokens.GetNetworkCoinStatusUseCase
@ -31,10 +34,6 @@ import com.tangem.features.send.api.navigation.SendRouter
import com.tangem.features.send.impl.navigation.InnerSendRouter
import com.tangem.features.send.impl.presentation.domain.AvailableWallet
import com.tangem.features.send.impl.presentation.state.*
import com.tangem.features.send.impl.presentation.state.SendNotificationFactory
import com.tangem.features.send.impl.presentation.state.SendStateFactory
import com.tangem.features.send.impl.presentation.state.SendUiState
import com.tangem.features.send.impl.presentation.state.StateRouter
import com.tangem.features.send.impl.presentation.state.fee.FeeSelectorState
import com.tangem.features.send.impl.presentation.state.fee.FeeType
import com.tangem.features.send.impl.presentation.state.fee.getFee
@ -70,6 +69,7 @@ internal class SendViewModel @Inject constructor(
private val validateWalletAddressUseCase: ValidateWalletAddressUseCase,
private val parseSharedAddressUseCase: ParseSharedAddressUseCase,
private val walletManagersFacade: WalletManagersFacade,
private val reduxStateHolder: ReduxStateHolder,
getExplorerTransactionUrlUseCase: GetExplorerTransactionUrlUseCase,
validateWalletMemoUseCase: ValidateWalletMemoUseCase,
savedStateHandle: SavedStateHandle,
@ -98,6 +98,11 @@ internal class SendViewModel @Inject constructor(
getExplorerTransactionUrlUseCase = getExplorerTransactionUrlUseCase,
)
private val eventStateFactory = SendEventStateFactory(
clickIntents = this,
currentStateProvider = Provider { uiState },
)
private val sendNotificationFactory = SendNotificationFactory(
cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus },
coinCryptoCurrencyStatusProvider = Provider { coinCryptoCurrencyStatus },
@ -106,6 +111,7 @@ internal class SendViewModel @Inject constructor(
walletManagersFacade = walletManagersFacade,
)
// todo convert to StateFlow
var uiState: SendUiState by mutableStateOf(stateFactory.getInitialState())
private set
@ -139,7 +145,9 @@ internal class SendViewModel @Inject constructor(
getCurrenciesStatusUpdates(owner, wallet)
},
ifLeft = {
// todo add error handling [[REDACTED_JIRA]]
uiState = eventStateFactory.getGenericErrorState(
onConsume = { uiState = eventStateFactory.onConsumeEventState() },
)
return@launch
},
)
@ -331,6 +339,10 @@ internal class SendViewModel @Inject constructor(
override fun onQrCodeScanClick() = innerRouter.openQrCodeScanner(cryptoCurrency.network.name)
override fun onFailedTxEmailClick(errorMessage: String) {
reduxStateHolder.dispatch(LegacyAction.SendEmailTransactionFailed(errorMessage))
}
override fun onTokenDetailsClick(userWalletId: UserWalletId, currency: CryptoCurrency) =
innerRouter.openTokenDetails(userWalletId, currency)
// endregion
@ -433,8 +445,8 @@ internal class SendViewModel @Inject constructor(
val sendState = uiState.sendState
if (sendState.isSuccess) popBackStack()
uiState = stateFactory.getSendingStateUpdate(true)
viewModelScope.launch(dispatchers.io) { verifyAndSendTransaction() }
uiState = stateFactory.getSendingStateUpdate(isSending = true)
verifyAndSendTransaction()
}
override fun showAmount() = stateRouter.showAmount(isFromSend = true)
@ -445,7 +457,7 @@ internal class SendViewModel @Inject constructor(
override fun onExploreClick(txUrl: String) = innerRouter.openUrl(txUrl)
private suspend fun verifyAndSendTransaction() {
private fun verifyAndSendTransaction() {
val recipient = uiState.recipientState?.addressTextField?.value ?: return
val feeState = uiState.feeState ?: return
val feeSelectorState = feeState.feeSelectorState as? FeeSelectorState.Content ?: return
@ -454,41 +466,46 @@ internal class SendViewModel @Inject constructor(
val amountToSend = feeState.receivedAmountValue.convertToAmount(cryptoCurrency)
// todo add error handling [[REDACTED_JIRA]]
// val transactionErrors = walletManagersFacade.validateTransaction(
// amount = amountToSend,
// fee = fee.amount,
// userWalletId = userWalletId,
// network = cryptoCurrency.network,
// )
viewModelScope.launch(dispatchers.main) {
createTransactionUseCase(
amount = amountToSend,
fee = fee,
memo = memo,
destination = recipient,
userWalletId = userWalletId,
network = cryptoCurrency.network,
).fold(
ifLeft = {
Timber.e(it)
uiState = eventStateFactory.getGenericErrorState(
error = it,
onConsume = { uiState = eventStateFactory.onConsumeEventState() },
)
},
ifRight = { txData ->
sendTransaction(txData)
},
)
}
}
createTransactionUseCase(
amount = amountToSend,
fee = fee,
memo = memo,
destination = recipient,
userWalletId = userWalletId,
private suspend fun sendTransaction(txData: TransactionData) {
sendTransactionUseCase(
txData = txData,
userWallet = userWallet,
network = cryptoCurrency.network,
).fold(
ifLeft = {
Timber.e(it)
// todo add error handling [[REDACTED_JIRA]]
},
ifRight = { txData ->
sendTransactionUseCase(
txData = txData,
userWallet = userWallet,
network = cryptoCurrency.network,
).fold(
ifLeft = {
uiState = stateFactory.getSendingStateUpdate(false)
// todo add error handling [[REDACTED_JIRA]]
},
ifRight = {
uiState = stateFactory.getTransactionSendState(txData)
},
ifLeft = { error ->
uiState = stateFactory.getSendingStateUpdate(isSending = false)
uiState = eventStateFactory.getSendTransactionErrorState(
error = error,
onConsume = { uiState = eventStateFactory.onConsumeEventState() },
)
},
ifRight = {
uiState = stateFactory.getSendingStateUpdate(isSending = false)
uiState = stateFactory.getTransactionSendState(txData)
},
)
}
// endregion

View file

@ -8,6 +8,7 @@ import com.tangem.domain.tokens.GetCryptoCurrencyStatusesSyncUseCase
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.tokens.repository.NetworksRepository
import com.tangem.domain.tokens.repository.QuotesRepository
import com.tangem.domain.transaction.TransactionRepository
import com.tangem.domain.transaction.usecase.SendTransactionUseCase
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.legacy.WalletsStateHolder
@ -118,13 +119,13 @@ class SwapDomainModule {
@Singleton
fun provideSendTransactionUseCase(
@SwapScope isDemoCardUseCase: IsDemoCardUseCase,
walletManagersFacade: WalletManagersFacade,
cardSdkConfigRepository: CardSdkConfigRepository,
transactionRepository: TransactionRepository,
): SendTransactionUseCase {
return SendTransactionUseCase(
isDemoCardUseCase = isDemoCardUseCase,
cardSdkConfigRepository = cardSdkConfigRepository,
walletManagersFacade = walletManagersFacade,
transactionRepository = transactionRepository,
)
}

View file

@ -553,7 +553,7 @@ internal class StateBuilder(
resourceReference(R.string.warning_express_refresh_required_title)
},
subtitle = if (dataError is DataError.UnknownError) {
resourceReference(R.string.swapping_generic_error)
resourceReference(R.string.common_unknown_error)
} else {
resourceReference(R.string.generic_error_code, wrappedList(dataError.code.toString()))
},

View file

@ -109,7 +109,7 @@ internal fun SwapScreenContent(state: SwapStateHolder, modifier: Modifier = Modi
val message = if (state.alert.type == GenericWarningType.NETWORK) {
stringResource(id = R.string.disclaimer_error_loading)
} else {
state.alert.message?.resolveReference() ?: stringResource(id = R.string.swapping_generic_error)
state.alert.message?.resolveReference() ?: stringResource(id = R.string.common_unknown_error)
}
SimpleOkDialog(
message = message,
@ -338,7 +338,7 @@ private fun SwapWarnings(warnings: List<SwapWarning>) {
} else {
it.resolveReference()
}
} ?: stringResource(id = R.string.swapping_generic_error)
} ?: stringResource(id = R.string.common_unknown_error)
RefreshableWaringCard(
title = stringResource(id = R.string.common_warning),
description = message,

View file

@ -87,7 +87,7 @@ spr-client = "3.6.2"
# region Tangem
tangemBlockchainSdk = "develop-458"
#tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds
tangemCardSdk = "develop-318"
tangemCardSdk = "develop-319"
#tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^
# endregion Tangem