diff --git a/app/src/main/java/com/tangem/tap/common/redux/legacy/LegacyMiddleware.kt b/app/src/main/java/com/tangem/tap/common/redux/legacy/LegacyMiddleware.kt
index 81d40b5307..9ad974cdcd 100644
--- a/app/src/main/java/com/tangem/tap/common/redux/legacy/LegacyMiddleware.kt
+++ b/app/src/main/java/com/tangem/tap/common/redux/legacy/LegacyMiddleware.kt
@@ -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)
}
diff --git a/app/src/main/java/com/tangem/tap/di/domain/TransactionDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/TransactionDomainModule.kt
index 5429ab3030..fdd81bd7d9 100644
--- a/app/src/main/java/com/tangem/tap/di/domain/TransactionDomainModule.kt
+++ b/app/src/main/java/com/tangem/tap/di/domain/TransactionDomainModule.kt
@@ -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,
)
}
diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml
index 12777954f8..dc64c4dac6 100644
--- a/core/res/src/main/res/values-ru/strings.xml
+++ b/core/res/src/main/res/values-ru/strings.xml
@@ -136,6 +136,7 @@
Перевод
Я понял
Недоступно
+ Произошла ошибка. Пожалуйста, попробуйте снова.
Да
Адрес контракта скопирован!
Доступные сети
@@ -448,8 +449,9 @@
Сумма
Вычесть из суммы отправки
Сумма к получению %s
+ Поддержка
Транзакция не выполнена
- Причина: %1$s\Код:%2$s
+ Причина: %1$s\nКод: %2$s
%1$s в %2$s
Адрес
Код назначения
@@ -528,7 +530,6 @@
Подтвердить
Ошибка: %s
Вы отправляете
- Произошла ошибка. Пожалуйста, попробуйте еще раз.
Дать разрешение
Обмен этой суммы выбранных токенов может вызвать значительные колебания цены и уменьшить получаемую сумму.
Недостаточно средств
diff --git a/core/res/src/main/res/values-zh-rTW/strings.xml b/core/res/src/main/res/values-zh-rTW/strings.xml
index c7ef19cf31..a1d2ee341a 100644
--- a/core/res/src/main/res/values-zh-rTW/strings.xml
+++ b/core/res/src/main/res/values-zh-rTW/strings.xml
@@ -327,7 +327,6 @@
批准被視為所有去中心化交易所的行業標準,並保護您的錢包在未經您許可的情況下不被智能合約訪問。按照設計,智能合約無法訪問您的代幣,除非您從您的終端批准訪問。通過“解鎖”您的代幣,您將獲得 1inch 智能合約使用您的資產的權限。網絡的礦工將獲得Gas Fee(由您支付)作為補償,以在區塊鏈上記錄此操作。一旦獲得許可,您就可以交易您的代幣。
批准
錯誤: %s
- 有錯誤。請再試一遍
賦予權限
在此代幣交換的數量將對價格產生重大影響,並降低您收到的數量
餘額不足
diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml
index 585432c7dd..b0bd11e551 100644
--- a/core/res/src/main/res/values/strings.xml
+++ b/core/res/src/main/res/values/strings.xml
@@ -135,6 +135,7 @@
Transfer
I understand
Unreachable
+ There was an error. Please try again.
Yes
Contract address copied!
Available networks
@@ -447,8 +448,9 @@
Amount
Subtract from send amount
The recipient will receive %s
+ Support
The transaction is not completed
- Reason: %1$s\nCode:%2$s
+ Reason: %1$s\nCode: %2$s
Confirm
%1$s at %2$s
Address
@@ -519,6 +521,7 @@
Transaction has been successfully signed and sent to the blockchain node. Wallet balance will be updated in a while
Invalid address
Transaction sent
+ %s (%s)
Buy now
I have a promo code…
Tangem Wallet
@@ -545,7 +548,6 @@
Approve
Error: %s
You swap
- There was an error. Please try again.
Give Permission
Swapping this amount of selected tokens will cause a significant price impact and reduce your outcome.
Insufficient funds
diff --git a/data/transaction/src/main/java/com/tangem/data/transaction/DefaultTransactionRepository.kt b/data/transaction/src/main/java/com/tangem/data/transaction/DefaultTransactionRepository.kt
index e5cf1cec6c..7c515253c2 100644
--- a/data/transaction/src/main/java/com/tangem/data/transaction/DefaultTransactionRepository.kt
+++ b/data/transaction/src/main/java/com/tangem/data/transaction/DefaultTransactionRepository.kt
@@ -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
diff --git a/domain/legacy/src/main/java/com/tangem/domain/redux/LegacyAction.kt b/domain/legacy/src/main/java/com/tangem/domain/redux/LegacyAction.kt
index f715061ac7..826ba5a966 100644
--- a/domain/legacy/src/main/java/com/tangem/domain/redux/LegacyAction.kt
+++ b/domain/legacy/src/main/java/com/tangem/domain/redux/LegacyAction.kt
@@ -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
}
\ No newline at end of file
diff --git a/domain/transaction/build.gradle.kts b/domain/transaction/build.gradle.kts
index 571b6c0f50..f0345df888 100644
--- a/domain/transaction/build.gradle.kts
+++ b/domain/transaction/build.gradle.kts
@@ -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)
diff --git a/domain/transaction/src/main/java/com/tangem/domain/transaction/TransactionRepository.kt b/domain/transaction/src/main/java/com/tangem/domain/transaction/TransactionRepository.kt
index 6c38bc0e7d..70c8b7d0ae 100644
--- a/domain/transaction/src/main/java/com/tangem/domain/transaction/TransactionRepository.kt
+++ b/domain/transaction/src/main/java/com/tangem/domain/transaction/TransactionRepository.kt
@@ -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
}
\ No newline at end of file
diff --git a/domain/transaction/src/main/java/com/tangem/domain/transaction/error/SendTransactionError.kt b/domain/transaction/src/main/java/com/tangem/domain/transaction/error/SendTransactionError.kt
index 8ed9705902..2d5536cefb 100644
--- a/domain/transaction/src/main/java/com/tangem/domain/transaction/error/SendTransactionError.kt
+++ b/domain/transaction/src/main/java/com/tangem/domain/transaction/error/SendTransactionError.kt
@@ -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 {
diff --git a/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/SendTransactionUseCase.kt b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/SendTransactionUseCase.kt
index 1e710033ae..c8ed1fe905 100644
--- a/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/SendTransactionUseCase.kt
+++ b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/SendTransactionUseCase.kt
@@ -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,
+ )
}
}
}
diff --git a/features/send/impl/build.gradle.kts b/features/send/impl/build.gradle.kts
index 53a6d8a573..46295abadb 100644
--- a/features/send/impl/build.gradle.kts
+++ b/features/send/impl/build.gradle.kts
@@ -23,6 +23,7 @@ dependencies {
implementation(deps.lifecycle.compose)
implementation(deps.jodatime)
implementation(deps.timber)
+ implementation(deps.reKotlin)
/** Compose */
implementation(deps.compose.accompanist.systemUiController)
diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendAlertState.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendAlertState.kt
new file mode 100644
index 0000000000..ff2aac684f
--- /dev/null
+++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendAlertState.kt
@@ -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)
+ }
+}
\ No newline at end of file
diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendEvent.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendEvent.kt
new file mode 100644
index 0000000000..2fdb687d96
--- /dev/null
+++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendEvent.kt
@@ -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()
+}
\ No newline at end of file
diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendEventStateFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendEventStateFactory.kt
new file mode 100644
index 0000000000..8bd869cb7c
--- /dev/null
+++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendEventStateFactory.kt
@@ -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,
+ 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.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,
+ ),
+ )
+ }
+}
\ No newline at end of file
diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendStateFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendStateFactory.kt
index 70fac6ad0d..4fea850423 100644
--- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendStateFactory.kt
+++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendStateFactory.kt
@@ -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 {
diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendTransactionAlertConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendTransactionAlertConverter.kt
new file mode 100644
index 0000000000..14f9cc265d
--- /dev/null
+++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendTransactionAlertConverter.kt
@@ -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 {
+ 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
+ }
+ }
+}
\ No newline at end of file
diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendUiState.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendUiState.kt
index fb2918c11d..3db22025d6 100644
--- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendUiState.kt
+++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendUiState.kt
@@ -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> = MutableStateFlow(PagingData.empty()),
val currentState: MutableStateFlow,
+ val event: StateEvent,
)
@Stable
diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendEventEffect.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendEventEffect.kt
new file mode 100644
index 0000000000..84e638eb8a
--- /dev/null
+++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendEventEffect.kt
@@ -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, snackbarHostState: SnackbarHostState) {
+ val resources = LocalContext.current.resources
+ var alertConfig by remember { mutableStateOf(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,
+ )
+}
\ No newline at end of file
diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendScreen.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendScreen.kt
index eae5388921..8fe5c5f53d 100644
--- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendScreen.kt
+++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/SendScreen.kt
@@ -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
diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendClickIntents.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendClickIntents.kt
index f79bf242b5..43a116ee65 100644
--- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendClickIntents.kt
+++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendClickIntents.kt
@@ -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
diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt
index 133e865e26..d4861a2e74 100644
--- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt
+++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt
@@ -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
diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt
index d299f7824e..bfa910c669 100644
--- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt
+++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt
@@ -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,
)
}
diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt
index d0d611881d..90e2f34c73 100644
--- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt
+++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt
@@ -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()))
},
diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt
index bb85650ace..3b42cf78a6 100644
--- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt
+++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt
@@ -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) {
} 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,
diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml
index 2fcde13f2c..3d15ad3489 100644
--- a/gradle/dependencies.toml
+++ b/gradle/dependencies.toml
@@ -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