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..456bf93702 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
@@ -7,7 +7,6 @@ import com.tangem.domain.transaction.usecase.CreateTransactionUseCase
import com.tangem.domain.transaction.usecase.GetFeeUseCase
import com.tangem.domain.transaction.usecase.SendTransactionUseCase
import com.tangem.domain.walletmanager.WalletManagersFacade
-import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
@@ -20,24 +19,21 @@ internal object TransactionDomainModule {
@Provides
@ViewModelScoped
- fun provideGetFeeUseCase(
- walletManagersFacade: WalletManagersFacade,
- dispatchers: CoroutineDispatcherProvider,
- ): GetFeeUseCase {
- return GetFeeUseCase(walletManagersFacade, dispatchers)
+ fun provideGetFeeUseCase(walletManagersFacade: WalletManagersFacade): GetFeeUseCase {
+ return GetFeeUseCase(walletManagersFacade)
}
@Provides
@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..efdc1b3d26 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
Адрес
Код назначения
@@ -477,8 +479,10 @@
Недостаточно средств
Размер комиссии превышает баланс сети. Для продолжения необходимо пополнить баланс сети.
Комиссия превышает баланс
- Комиссия при переводе всего баланса выше. Для того, чтобы снизить комиссию Вы можете оставить 0.01.
Увеличение комиссии
+ Комиссия при переводе всего баланса выше. Для того, чтобы снизить комиссию Вы можете оставить 0.01.
+ Оставить %s XTZ
+ Отправить все
Установлена высокая комиссия
Сумма комиссии в %s раз превышает рекомендованную. Убедитесь, что указанная комиссия верна.
Включенная комиссия превышает сумму перевода, что приводит к отрицательному значению
@@ -528,7 +532,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..125fb77911 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
@@ -482,8 +484,10 @@
Total exceeds balance
The commission fee exceeds the network balance. To continue, it is necessary to replenish the network balance.
Fee exceeds balance
- The fee for transferring the entire balance is higher. To reduce the commission, you can leave 0.01.
Fee is increased
+ The fee for transferring the entire balance is higher. To reduce the commission, you can leave 0.01.
+ Reduce by %s XTZ
+ No, send all
Custom fee is high
The commission amount is %s times the recommended amount. Make sure that the custom settings are correct.
The included commission exceeds the transfer amount, leading to a negative value
@@ -519,6 +523,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 +550,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/GetFeeUseCase.kt b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/GetFeeUseCase.kt
index e27a7d2625..ae697ceb08 100644
--- a/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/GetFeeUseCase.kt
+++ b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/GetFeeUseCase.kt
@@ -1,21 +1,15 @@
package com.tangem.domain.transaction.usecase
-import arrow.core.Either
-import arrow.core.left
-import arrow.core.right
+import arrow.core.raise.catch
+import arrow.core.raise.either
import com.tangem.blockchain.common.Amount
import com.tangem.blockchain.common.AmountType
import com.tangem.blockchain.common.Token
-import com.tangem.blockchain.common.transaction.TransactionFee
import com.tangem.blockchain.extensions.Result
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.transaction.error.GetFeeError
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.models.UserWalletId
-import com.tangem.utils.coroutines.CoroutineDispatcherProvider
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.flow
-import kotlinx.coroutines.flow.flowOn
import java.math.BigDecimal
/**
@@ -23,16 +17,15 @@ import java.math.BigDecimal
*/
class GetFeeUseCase(
private val walletManagersFacade: WalletManagersFacade,
- private val dispatcher: CoroutineDispatcherProvider,
) {
suspend operator fun invoke(
amount: BigDecimal,
destination: String,
userWalletId: UserWalletId,
cryptoCurrency: CryptoCurrency,
- ): Flow> {
- return flow {
- try {
+ ) = either {
+ catch(
+ block = {
val result = requireNotNull(
walletManagersFacade.getFee(
amount = convertCryptoCurrencyToAmount(cryptoCurrency, amount),
@@ -43,14 +36,15 @@ class GetFeeUseCase(
) { "Fee is null" }
val maybeFee = when (result) {
- is Result.Success -> result.data.right()
- is Result.Failure -> GetFeeError.DataError(result.error).left()
+ is Result.Success -> result.data
+ is Result.Failure -> raise(GetFeeError.DataError(result.error))
}
- emit(maybeFee)
- } catch (e: Exception) {
- emit(GetFeeError.DataError(e.cause).left())
- }
- }.flowOn(dispatcher.io)
+ maybeFee
+ },
+ catch = {
+ raise(GetFeeError.DataError(it))
+ },
+ )
}
private fun convertCryptoCurrencyToAmount(cryptoCurrency: CryptoCurrency, amount: BigDecimal) = Amount(
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/SendNotification.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendNotification.kt
index 6d2975319a..cdbd005127 100644
--- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendNotification.kt
+++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendNotification.kt
@@ -57,16 +57,28 @@ internal sealed class SendNotification(val config: NotificationConfig) {
sealed class Warning(
title: TextReference,
subtitle: TextReference,
+ buttonsState: NotificationConfig.ButtonsState? = null,
) : SendNotification(
config = NotificationConfig(
title = title,
subtitle = subtitle,
iconResId = R.drawable.img_attention_20,
+ buttonsState = buttonsState,
),
) {
- data class HighFeeError(val amount: String) : Warning(
+ data class HighFeeError(
+ val amount: String,
+ val onConfirmClick: () -> Unit,
+ val onDismissClick: () -> Unit,
+ ) : Warning(
title = resourceReference(R.string.send_notification_high_fee_title),
subtitle = resourceReference(R.string.send_notification_high_fee_text, wrappedList(amount)),
+ buttonsState = NotificationConfig.ButtonsState.PairButtonsConfig(
+ primaryText = resourceReference(R.string.send_notification_fee_too_high_accept, wrappedList(amount)),
+ onPrimaryClick = onConfirmClick,
+ secondaryText = resourceReference(R.string.send_notification_fee_too_high_ignore),
+ onSecondaryClick = onDismissClick,
+ ),
)
data class ExistentialDeposit(val deposit: String) : Warning(
diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendNotificationFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendNotificationFactory.kt
index ce29bd2c60..18e0b4f34c 100644
--- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendNotificationFactory.kt
+++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/SendNotificationFactory.kt
@@ -6,6 +6,7 @@ import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.models.UserWallet
+import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents
import com.tangem.utils.Provider
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
@@ -21,6 +22,7 @@ internal class SendNotificationFactory(
private val currentStateProvider: Provider,
private val userWalletProvider: Provider,
private val walletManagersFacade: WalletManagersFacade,
+ private val clickIntents: SendClickIntents,
) {
fun create(): Flow> = currentStateProvider().currentState
@@ -30,19 +32,33 @@ internal class SendNotificationFactory(
val feeState = state.feeState ?: return@map persistentListOf()
val recipientState = state.recipientState ?: return@map persistentListOf()
val feeAmount = feeState.fee?.amount?.value ?: BigDecimal.ZERO
+ val amountValue = state.amountState?.amountTextField?.value?.toBigDecimalOrNull() ?: BigDecimal.ZERO
+ val sendAmount = if (feeState.isSubtract) feeState.receivedAmountValue else amountValue
buildList {
// errors
- addExceedBalanceNotification(feeAmount, feeState.receivedAmountValue)
- addInvalidAmountNotification(feeState.isSubtract, feeState.receivedAmountValue)
- addMinimumAmountErrorNotification(feeAmount, feeState.receivedAmountValue)
+ addExceedBalanceNotification(feeAmount, sendAmount)
+ addInvalidAmountNotification(feeState.isSubtract, sendAmount)
+ addMinimumAmountErrorNotification(feeAmount, sendAmount)
addReserveAmountErrorNotification(recipientState.addressTextField.value)
- addTransactionLimitErrorNotification(feeAmount, feeState.receivedAmountValue)
+ addTransactionLimitErrorNotification(feeAmount, sendAmount)
// warnings
- addExistentialWarningNotification(feeAmount, feeState.receivedAmountValue)
- addHighFeeWarningNotification()
+ addExistentialWarningNotification(feeAmount, sendAmount)
+ addHighFeeWarningNotification(amountValue, state.sendState.ignoreAmountReduce)
}.toImmutableList()
}
+ fun dismissHighFeeWarningState(): SendUiState {
+ val state = currentStateProvider()
+ val sendState = state.sendState
+ val updatedNotifications = sendState.notifications.filterNot { it is SendNotification.Warning.HighFeeError }
+ return state.copy(
+ sendState = sendState.copy(
+ ignoreAmountReduce = true,
+ notifications = updatedNotifications.toImmutableList(),
+ ),
+ )
+ }
+
private fun MutableList.addExceedBalanceNotification(
feeAmount: BigDecimal,
receivedAmount: BigDecimal,
@@ -173,16 +189,30 @@ internal class SendNotificationFactory(
}
}
- private fun MutableList.addHighFeeWarningNotification() {
- // TODO Move Blockchain check elsewhere
- if (cryptoCurrencyStatusProvider().currency.network.id.value == Blockchain.Tezos.id) {
- add(SendNotification.Warning.HighFeeError(TEZOS_FEE_THRESHOLD))
+ private fun MutableList.addHighFeeWarningNotification(
+ sendAmount: BigDecimal,
+ ignoreAmountReduce: Boolean,
+ ) {
+ val cryptoCurrencyStatus = cryptoCurrencyStatusProvider()
+ val balance = cryptoCurrencyStatus.value.amount ?: BigDecimal.ZERO
+ val isTezos = cryptoCurrencyStatus.currency.network.id.value == Blockchain.Tezos.id
+ if (!ignoreAmountReduce && sendAmount == balance && isTezos) {
+ add(
+ SendNotification.Warning.HighFeeError(
+ amount = TEZOS_FEE_THRESHOLD.toPlainString(),
+ onConfirmClick = {
+ val reduceTo = sendAmount.minus(TEZOS_FEE_THRESHOLD).toPlainString()
+ clickIntents.onAmountReduceClick(reduceTo)
+ },
+ onDismissClick = clickIntents::onAmountReduceIgnoreClick,
+ ),
+ )
}
}
companion object {
private const val CARDANO_MINIMUM = "1"
private const val DOGECOIN_MINIMUM = "0.01"
- private const val TEZOS_FEE_THRESHOLD = "0.01"
+ private val TEZOS_FEE_THRESHOLD = BigDecimal("0.01")
}
}
\ 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..185a62b42a 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
@@ -3,10 +3,9 @@ package com.tangem.features.send.impl.presentation.state
import androidx.paging.PagingData
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
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.txhistory.models.TxHistoryItem
@@ -16,22 +15,19 @@ import com.tangem.domain.wallets.usecase.ValidateWalletMemoUseCase
import com.tangem.features.send.impl.R
import com.tangem.features.send.impl.presentation.domain.AvailableWallet
import com.tangem.features.send.impl.presentation.state.amount.SendAmountStateConverter
-import com.tangem.features.send.impl.presentation.state.fee.*
+import com.tangem.features.send.impl.presentation.state.fee.SendFeeStateConverter
import com.tangem.features.send.impl.presentation.state.fields.SendAmountFieldChangeConverter
import com.tangem.features.send.impl.presentation.state.fields.SendAmountFieldConverter
import com.tangem.features.send.impl.presentation.state.recipient.SendRecipientListConverter
import com.tangem.features.send.impl.presentation.state.recipient.SendRecipientStateConverter
import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents
import com.tangem.utils.Provider
-import com.tangem.utils.isNullOrZero
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
-import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.flow.MutableStateFlow
import timber.log.Timber
-import java.math.BigDecimal
-@Suppress("LongParameterList", "LargeClass")
+@Suppress("LongParameterList")
internal class SendStateFactory(
private val clickIntents: SendClickIntents,
private val currentStateProvider: Provider,
@@ -40,25 +36,11 @@ internal class SendStateFactory(
private val cryptoCurrencyStatusProvider: Provider,
private val validateWalletMemoUseCase: ValidateWalletMemoUseCase,
private val getExplorerTransactionUrlUseCase: GetExplorerTransactionUrlUseCase,
- coinCryptoCurrencyStatusProvider: Provider,
) {
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 {
- SendFeeCustomFieldConverter(
- clickIntents = clickIntents,
- appCurrencyProvider = appCurrencyProvider,
- )
- }
-
- private val feeNotificationFactory = FeeNotificationFactory(
- coinCryptoCurrencyStatusProvider = coinCryptoCurrencyStatusProvider,
- userWalletProvider = userWalletProvider,
- clickIntents = clickIntents,
- )
private val amountStateConverter by lazy {
SendAmountStateConverter(
@@ -92,6 +74,7 @@ internal class SendStateFactory(
fun getInitialState(): SendUiState = SendUiState(
clickIntents = clickIntents,
currentState = MutableStateFlow(SendUiStateType.Amount),
+ event = consumedEvent(),
)
fun getReadyState(): SendUiState {
@@ -218,144 +201,6 @@ internal class SendStateFactory(
}
//endregion
- //region fee
- fun onFeeOnLoadingState(): SendUiState {
- val state = currentStateProvider()
- val feeState = state.feeState ?: return state
- val feeSelectorState = FeeSelectorState.Loading
- return state.copy(
- feeState = feeState.copy(
- feeSelectorState = feeSelectorState,
- notifications = persistentListOf(),
- isPrimaryButtonEnabled = feeSelectorState.isPrimaryButtonEnabled(),
- ),
- )
- }
-
- fun onFeeOnLoadedState(fees: TransactionFee): SendUiState {
- val state = currentStateProvider()
- val feeState = state.feeState ?: return state
- val feeSelectorState = FeeSelectorState.Content(
- fees = fees,
- customValues = customFeeFieldConverter.convert(fees.normal),
- )
-
- val fee = feeSelectorState.getFee()
- val receivedAmount = calculateReceiveAmount(state, fee, feeState.isSubtract)
- val updatedState = feeState.copy(
- feeSelectorState = feeSelectorState,
- fee = fee,
- receivedAmountValue = receivedAmount,
- receivedAmount = getFormattedValue(receivedAmount),
- )
- return state.copy(
- feeState = updatedState.copy(
- notifications = feeNotificationFactory(feeState = updatedState),
- isPrimaryButtonEnabled = feeSelectorState.isPrimaryButtonEnabled(),
- ),
- )
- }
-
- fun onFeeSelectedState(feeType: FeeType): SendUiState {
- val state = currentStateProvider()
- val feeState = state.feeState ?: return state
- val feeSelectorState = feeState.feeSelectorState as? FeeSelectorState.Content ?: return state
-
- val updatedFeeSelectorState = feeSelectorState.copy(selectedFee = feeType)
- val fee = updatedFeeSelectorState.getFee()
- val receivedAmount = calculateReceiveAmount(state, fee, feeState.isSubtract)
-
- val updatedState = feeState.copy(
- fee = fee,
- receivedAmountValue = receivedAmount,
- receivedAmount = getFormattedValue(receivedAmount),
- feeSelectorState = updatedFeeSelectorState,
- isPrimaryButtonEnabled = updatedFeeSelectorState.isPrimaryButtonEnabled(),
- )
-
- return state.copy(
- feeState = updatedState.copy(
- notifications = feeNotificationFactory(feeState = updatedState),
- ),
- )
- }
-
- fun onCustomFeeValueChange(index: Int, value: String): SendUiState {
- val state = currentStateProvider()
- val feeState = state.feeState ?: return state
- val feeSelectorState = feeState.feeSelectorState as? FeeSelectorState.Content ?: return state
- val updatedFeeSelectorState = feeSelectorState.copy(
- customValues = feeSelectorState.customValues.toMutableList().apply {
- set(index, feeSelectorState.customValues[index].copy(value = value))
- }.toImmutableList(),
- )
-
- val fee = updatedFeeSelectorState.getFee()
- val receivedAmount = calculateReceiveAmount(state, fee, feeState.isSubtract)
-
- val updatedState = feeState.copy(
- feeSelectorState = updatedFeeSelectorState,
- fee = fee,
- receivedAmountValue = receivedAmount,
- receivedAmount = getFormattedValue(receivedAmount),
- isPrimaryButtonEnabled = updatedFeeSelectorState.isPrimaryButtonEnabled(),
- )
- return state.copy(
- feeState = updatedState.copy(
- notifications = feeNotificationFactory(feeState = updatedState),
- ),
- )
- }
-
- fun onSubtractSelect(value: Boolean): SendUiState {
- val state = currentStateProvider()
- val feeState = state.feeState ?: return state
- val feeSelectorState = feeState.feeSelectorState as? FeeSelectorState.Content ?: return state
-
- val fee = feeSelectorState.getFee()
- val receivedAmount = calculateReceiveAmount(state, fee, value)
- val updatedState = feeState.copy(
- isSubtract = value,
- fee = fee,
- receivedAmountValue = receivedAmount,
- receivedAmount = if (value) {
- getFormattedValue(receivedAmount)
- } else {
- feeState.receivedAmount
- },
- )
- return state.copy(
- feeState = updatedState.copy(
- notifications = feeNotificationFactory(feeState = updatedState),
- ),
- )
- }
-
- private fun FeeSelectorState.isPrimaryButtonEnabled(): Boolean {
- return when (this) {
- is FeeSelectorState.Loading -> false
- is FeeSelectorState.Content -> {
- val customValue = customValues.firstOrNull()?.value?.toBigDecimalOrNull()
- val balance = cryptoCurrencyStatusProvider().value.amount ?: BigDecimal.ZERO
- val fee = getFee().amount.value ?: BigDecimal.ZERO
-
- val isNotEmptyCustom = !customValue.isNullOrZero() && selectedFee == FeeType.CUSTOM
- val isNotCustom = selectedFee != FeeType.CUSTOM
- fee < balance && (isNotEmptyCustom || isNotCustom)
- }
- }
- }
-
- private fun getFormattedValue(value: BigDecimal): String {
- val cryptoCurrency = cryptoCurrencyStatusProvider().currency
- return BigDecimalFormatter.formatCryptoAmount(
- cryptoAmount = value,
- cryptoCurrency = cryptoCurrency.symbol,
- decimals = cryptoCurrency.decimals,
- )
- }
- //endregion
-
//region send
fun getSendingStateUpdate(isSending: Boolean): SendUiState {
val state = currentStateProvider()
@@ -375,6 +220,7 @@ internal class SendStateFactory(
transactionDate = txData.date?.timeInMillis ?: System.currentTimeMillis(),
isSuccess = true,
txUrl = txUrl,
+ notifications = persistentListOf(),
),
)
}
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..f02b6ddf13 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
@@ -52,6 +54,7 @@ internal sealed class SendStates {
val isFiatValue: Boolean,
val segmentedButtonConfig: PersistentList,
val amountTextField: SendTextField.Amount,
+ val amountValue: BigDecimal,
) : SendStates()
/** Recipient state */
@@ -72,6 +75,7 @@ internal sealed class SendStates {
val cryptoCurrencyStatus: CryptoCurrencyStatus,
val feeSelectorState: FeeSelectorState = FeeSelectorState.Loading,
val isSubtract: Boolean = false,
+ val isUserSubtracted: Boolean = false,
val fee: Fee? = null,
val receivedAmountValue: BigDecimal = BigDecimal.ZERO,
val receivedAmount: String = "",
@@ -86,6 +90,7 @@ internal sealed class SendStates {
val isSuccess: Boolean = false,
val transactionDate: Long = 0L,
val txUrl: String = "",
+ val ignoreAmountReduce: Boolean = false,
val notifications: ImmutableList = persistentListOf(),
) : SendStates()
}
diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountStateConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountStateConverter.kt
index c80815c345..3420a483f4 100644
--- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountStateConverter.kt
+++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/amount/SendAmountStateConverter.kt
@@ -12,6 +12,7 @@ import com.tangem.features.send.impl.presentation.state.fields.SendAmountFieldCo
import com.tangem.utils.Provider
import com.tangem.utils.converter.Converter
import kotlinx.collections.immutable.persistentListOf
+import java.math.BigDecimal
internal class SendAmountStateConverter(
private val appCurrencyProvider: Provider,
@@ -37,6 +38,7 @@ internal class SendAmountStateConverter(
amountTextField = sendAmountFieldConverter.convert(Unit),
isFiatValue = false,
isPrimaryButtonEnabled = false,
+ amountValue = BigDecimal.ZERO,
segmentedButtonConfig = persistentListOf(
SendAmountSegmentedButtonsConfig(
title = stringReference(status.currency.symbol),
diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeCalculation.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeCalculation.kt
index 1805ce3f6a..de65f8a987 100644
--- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeCalculation.kt
+++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeCalculation.kt
@@ -8,14 +8,10 @@ import java.math.BigDecimal
/**
* Calculate receiving amount when fee is subtracted from sending amount
*/
-internal fun calculateReceiveAmount(uiState: SendUiState, feeAmount: Fee, isSubtract: Boolean): BigDecimal {
- val amount = uiState.amountState?.amountTextField?.value ?: return BigDecimal.ZERO
+internal fun calculateReceiveAmount(state: SendUiState, feeAmount: Fee): BigDecimal {
+ val amountValue = state.amountState?.amountValue ?: BigDecimal.ZERO
val fee = feeAmount.amount.value ?: return BigDecimal.ZERO
- return if (isSubtract) {
- amount.toBigDecimal().minus(fee)
- } else {
- amount.toBigDecimal()
- }
+ return amountValue.minus(fee)
}
/**
diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeNotificationFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeNotificationFactory.kt
index fa63899356..8f51e6164a 100644
--- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeNotificationFactory.kt
+++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeNotificationFactory.kt
@@ -1,5 +1,6 @@
package com.tangem.features.send.impl.presentation.state.fee
+import com.tangem.blockchain.common.transaction.Fee
import com.tangem.blockchain.common.transaction.TransactionFee
import com.tangem.core.ui.extensions.networkIconResId
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
@@ -9,7 +10,6 @@ import com.tangem.features.send.impl.presentation.state.fields.SendTextField
import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents
import com.tangem.utils.Provider
import kotlinx.collections.immutable.ImmutableList
-import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
import java.math.BigDecimal
@@ -19,17 +19,28 @@ internal class FeeNotificationFactory(
private val clickIntents: SendClickIntents,
) {
- operator fun invoke(feeState: SendStates.FeeState): ImmutableList {
- val feeSelectorState = feeState.feeSelectorState as? FeeSelectorState.Content ?: return persistentListOf()
- val customFee = feeSelectorState.customValues
- val selectedFee = feeSelectorState.selectedFee
-
- return buildList {
- addTooLowNotification(feeSelectorState.fees, selectedFee, customFee)
- addTooHighNotification(feeSelectorState.fees, selectedFee, customFee)
- addFeeCoverageNotification()
- addExceedsBalanceNotification(feeSelectorState)
+ operator fun invoke(feeState: SendStates.FeeState, amountValue: BigDecimal?): ImmutableList =
+ buildList {
+ when (val feeSelectorState = feeState.feeSelectorState) {
+ FeeSelectorState.Loading -> Unit
+ FeeSelectorState.Error -> {
+ addFeeUnreachableNotification(feeSelectorState)
+ }
+ is FeeSelectorState.Content -> {
+ val customFee = feeSelectorState.customValues
+ val selectedFee = feeSelectorState.selectedFee
+ addTooLowNotification(feeSelectorState.fees, selectedFee, customFee)
+ addTooHighNotification(feeSelectorState.fees, selectedFee, customFee)
+ addFeeCoverageNotification(feeState, amountValue)
+ addExceedsBalanceNotification(feeState.fee)
+ }
+ }
}.toImmutableList()
+
+ private fun MutableList.addFeeUnreachableNotification(feeSelectorState: FeeSelectorState) {
+ if (feeSelectorState is FeeSelectorState.Error) {
+ add(SendFeeNotification.Warning.NetworkFeeUnreachable(clickIntents::feeReload))
+ }
}
private fun MutableList.addTooLowNotification(
@@ -59,25 +70,24 @@ internal class FeeNotificationFactory(
}
}
- private fun MutableList.addFeeCoverageNotification() {
- // TODO add fee coverage condition [REDACTED_JIRA]
- add(SendFeeNotification.Warning.NetworkCoverage)
+ private fun MutableList.addFeeCoverageNotification(
+ feeState: SendStates.FeeState,
+ amountValue: BigDecimal?,
+ ) {
+ val cryptoAmount = coinCryptoCurrencyStatusProvider().value.amount ?: BigDecimal.ZERO
+ val feeValue = feeState.fee?.amount?.value ?: BigDecimal.ZERO
+ val value = amountValue ?: BigDecimal.ZERO
+ if (cryptoAmount <= value + feeValue && feeState.isSubtract && !feeState.isUserSubtracted) {
+ add(SendFeeNotification.Warning.NetworkCoverage)
+ }
}
- private fun MutableList.addExceedsBalanceNotification(
- feeSelectorState: FeeSelectorState.Content,
- ) {
+ private fun MutableList.addExceedsBalanceNotification(fee: Fee?) {
val coinCryptoCurrency = coinCryptoCurrencyStatusProvider()
val cryptoAmount = coinCryptoCurrency.value.amount ?: BigDecimal.ZERO
- val choosableFee = feeSelectorState.fees as? TransactionFee.Choosable
- val fee = when (feeSelectorState.selectedFee) {
- FeeType.SLOW -> choosableFee?.minimum?.amount?.value
- FeeType.MARKET -> feeSelectorState.fees.normal.amount.value
- FeeType.FAST -> choosableFee?.priority?.amount?.value
- FeeType.CUSTOM -> feeSelectorState.customValues.firstOrNull()?.value?.toBigDecimalOrNull()
- } ?: return
+ val feeValue = fee?.amount?.value ?: BigDecimal.ZERO
- if (fee > cryptoAmount) {
+ if (feeValue > cryptoAmount) {
add(
SendFeeNotification.Error.ExceedsBalance(
coinCryptoCurrency.currency.networkIconResId,
diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeSelectorState.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeSelectorState.kt
index c5a392c4a9..9751e61eae 100644
--- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeSelectorState.kt
+++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeSelectorState.kt
@@ -16,6 +16,8 @@ internal sealed class FeeSelectorState {
val selectedFee: FeeType = FeeType.MARKET,
val customValues: ImmutableList = persistentListOf(),
) : FeeSelectorState()
+
+ object Error : FeeSelectorState()
}
enum class FeeType {
diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeStateFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeStateFactory.kt
new file mode 100644
index 0000000000..abeea708ac
--- /dev/null
+++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeStateFactory.kt
@@ -0,0 +1,217 @@
+package com.tangem.features.send.impl.presentation.state.fee
+
+import com.tangem.blockchain.common.transaction.Fee
+import com.tangem.blockchain.common.transaction.TransactionFee
+import com.tangem.core.ui.utils.BigDecimalFormatter
+import com.tangem.domain.appcurrency.model.AppCurrency
+import com.tangem.domain.tokens.model.CryptoCurrencyStatus
+import com.tangem.domain.wallets.models.UserWallet
+import com.tangem.features.send.impl.presentation.state.SendStates
+import com.tangem.features.send.impl.presentation.state.SendUiState
+import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents
+import com.tangem.utils.Provider
+import com.tangem.utils.isNullOrZero
+import kotlinx.collections.immutable.persistentListOf
+import kotlinx.collections.immutable.toImmutableList
+import java.math.BigDecimal
+
+/**
+ * Factory to produce fee state for [SendUiState]
+ */
+internal class FeeStateFactory(
+ private val clickIntents: SendClickIntents,
+ private val currentStateProvider: Provider,
+ private val coinCryptoCurrencyStatusProvider: Provider,
+ private val cryptoCurrencyStatusProvider: Provider,
+ private val appCurrencyProvider: Provider,
+ userWalletProvider: Provider,
+) {
+ private val customFeeFieldConverter by lazy {
+ SendFeeCustomFieldConverter(
+ clickIntents = clickIntents,
+ appCurrencyProvider = appCurrencyProvider,
+ )
+ }
+
+ private val feeNotificationFactory = FeeNotificationFactory(
+ coinCryptoCurrencyStatusProvider = coinCryptoCurrencyStatusProvider,
+ userWalletProvider = userWalletProvider,
+ clickIntents = clickIntents,
+ )
+
+ fun onFeeOnLoadingState(): SendUiState {
+ val state = currentStateProvider()
+ val feeState = state.feeState ?: return state
+ return state.copy(
+ feeState = feeState.copy(
+ feeSelectorState = FeeSelectorState.Loading,
+ notifications = persistentListOf(),
+ isPrimaryButtonEnabled = feeState.isPrimaryButtonEnabled(),
+ ),
+ )
+ }
+
+ fun onFeeOnLoadedState(fees: TransactionFee): SendUiState {
+ val state = currentStateProvider()
+ val balance = coinCryptoCurrencyStatusProvider().value.amount ?: BigDecimal.ZERO
+ val feeState = state.feeState ?: return state
+ val feeSelectorState = FeeSelectorState.Content(
+ fees = fees,
+ customValues = customFeeFieldConverter.convert(fees.normal),
+ )
+
+ val fee = feeSelectorState.getFee()
+ val receivedAmount = calculateReceiveAmount(state, fee)
+ val updatedState = feeState.copy(
+ feeSelectorState = feeSelectorState,
+ fee = fee,
+ receivedAmountValue = receivedAmount,
+ receivedAmount = getFormattedValue(receivedAmount),
+ isSubtract = checkAutoSubtract(state, fee, balance),
+ )
+ return state.copy(
+ feeState = updatedState.copy(
+ notifications = feeNotificationFactory(
+ feeState = updatedState,
+ amountValue = state.amountState?.amountValue,
+ ),
+ isPrimaryButtonEnabled = updatedState.isPrimaryButtonEnabled(),
+ ),
+ )
+ }
+
+ fun onFeeOnErrorState(): SendUiState {
+ val state = currentStateProvider()
+ val updatedState = state.feeState?.copy(
+ feeSelectorState = FeeSelectorState.Error,
+ )
+ return state.copy(
+ feeState = updatedState?.copy(
+ notifications = feeNotificationFactory(
+ feeState = updatedState,
+ amountValue = state.amountState?.amountValue,
+ ),
+ ),
+ )
+ }
+
+ fun onFeeSelectedState(feeType: FeeType): SendUiState {
+ val state = currentStateProvider()
+ val feeState = state.feeState ?: return state
+ val feeSelectorState = feeState.feeSelectorState as? FeeSelectorState.Content ?: return state
+ val balance = coinCryptoCurrencyStatusProvider().value.amount ?: BigDecimal.ZERO
+
+ val updatedFeeSelectorState = feeSelectorState.copy(selectedFee = feeType)
+ val fee = updatedFeeSelectorState.getFee()
+ val receivedAmount = calculateReceiveAmount(state, fee)
+ val updatedState = feeState.copy(
+ fee = fee,
+ feeSelectorState = updatedFeeSelectorState,
+ receivedAmountValue = receivedAmount,
+ receivedAmount = getFormattedValue(receivedAmount),
+ isSubtract = checkAutoSubtract(state, fee, balance),
+ )
+
+ return state.copy(
+ feeState = updatedState.copy(
+ notifications = feeNotificationFactory(
+ feeState = updatedState,
+ amountValue = state.amountState?.amountValue,
+ ),
+ isPrimaryButtonEnabled = updatedState.isPrimaryButtonEnabled(),
+ ),
+ )
+ }
+
+ fun onCustomFeeValueChange(index: Int, value: String): SendUiState {
+ val state = currentStateProvider()
+ val feeState = state.feeState ?: return state
+ val feeSelectorState = feeState.feeSelectorState as? FeeSelectorState.Content ?: return state
+ val updatedFeeSelectorState = feeSelectorState.copy(
+ customValues = feeSelectorState.customValues.toMutableList().apply {
+ set(index, feeSelectorState.customValues[index].copy(value = value))
+ }.toImmutableList(),
+ )
+ val balance = coinCryptoCurrencyStatusProvider().value.amount ?: BigDecimal.ZERO
+
+ val fee = updatedFeeSelectorState.getFee()
+ val receivedAmount = calculateReceiveAmount(state, fee)
+ val updatedState = feeState.copy(
+ feeSelectorState = updatedFeeSelectorState,
+ fee = fee,
+ receivedAmountValue = receivedAmount,
+ receivedAmount = getFormattedValue(receivedAmount),
+ isSubtract = checkAutoSubtract(state, fee, balance),
+ )
+ return state.copy(
+ feeState = updatedState.copy(
+ notifications = feeNotificationFactory(
+ feeState = updatedState,
+ amountValue = state.amountState?.amountValue,
+ ),
+ isPrimaryButtonEnabled = updatedState.isPrimaryButtonEnabled(),
+ ),
+ )
+ }
+
+ fun onSubtractSelect(value: Boolean): SendUiState {
+ val state = currentStateProvider()
+ val feeState = state.feeState ?: return state
+ val feeSelectorState = feeState.feeSelectorState as? FeeSelectorState.Content ?: return state
+ val fee = feeSelectorState.getFee()
+ val receivedAmount = calculateReceiveAmount(state, fee)
+ val updatedState = feeState.copy(
+ isSubtract = value,
+ isUserSubtracted = true,
+ receivedAmountValue = receivedAmount,
+ receivedAmount = getFormattedValue(receivedAmount),
+ fee = fee,
+ )
+ return state.copy(
+ feeState = updatedState.copy(
+ notifications = feeNotificationFactory(
+ feeState = updatedState,
+ amountValue = state.amountState?.amountValue,
+ ),
+ isPrimaryButtonEnabled = updatedState.isPrimaryButtonEnabled(),
+ ),
+ )
+ }
+
+ private fun SendStates.FeeState.isPrimaryButtonEnabled(): Boolean {
+ return when (feeSelectorState) {
+ is FeeSelectorState.Content -> {
+ val customValue = feeSelectorState.customValues.firstOrNull()?.value?.toBigDecimalOrNull()
+ val balance = cryptoCurrencyStatusProvider().value.amount ?: BigDecimal.ZERO
+ val fee = feeSelectorState.getFee().amount.value ?: BigDecimal.ZERO
+
+ val isNotCustom = feeSelectorState.selectedFee != FeeType.CUSTOM
+ val isNotEmptyCustom = !customValue.isNullOrZero() && !isNotCustom
+ val isSubtractRequired = if (fee + receivedAmountValue >= balance) isSubtract else true
+ val isBalanceEnough = fee + receivedAmountValue <= balance
+ isBalanceEnough && isSubtractRequired && (isNotEmptyCustom || isNotCustom)
+ }
+ else -> false
+ }
+ }
+
+ private fun checkAutoSubtract(state: SendUiState, fee: Fee, balance: BigDecimal): Boolean {
+ val feeState = state.feeState ?: return false
+ val amountValue = state.amountState?.amountValue ?: BigDecimal.ZERO
+ val feeAmount = fee.amount.value ?: BigDecimal.ZERO
+ return if (feeState.isUserSubtracted) {
+ feeState.isSubtract
+ } else {
+ amountValue + feeAmount >= balance
+ }
+ }
+
+ private fun getFormattedValue(value: BigDecimal): String {
+ val cryptoCurrency = cryptoCurrencyStatusProvider().currency
+ return BigDecimalFormatter.formatCryptoAmount(
+ cryptoAmount = value,
+ cryptoCurrency = cryptoCurrency.symbol,
+ decimals = cryptoCurrency.decimals,
+ )
+ }
+}
\ No newline at end of file
diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/SendFeeNotification.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/SendFeeNotification.kt
index 8763cf5cc2..fbf72931af 100644
--- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/SendFeeNotification.kt
+++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/SendFeeNotification.kt
@@ -1,13 +1,11 @@
package com.tangem.features.send.impl.presentation.state.fee
-import androidx.compose.runtime.Immutable
import com.tangem.core.ui.components.notifications.NotificationConfig
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
sealed class SendFeeNotification(val config: NotificationConfig) {
sealed class Informational(
@@ -29,11 +27,13 @@ sealed class SendFeeNotification(val config: NotificationConfig) {
sealed class Warning(
val title: TextReference,
val subtitle: TextReference,
+ val buttonsState: NotificationConfig.ButtonsState? = null,
) : SendFeeNotification(
config = NotificationConfig(
title = title,
subtitle = subtitle,
iconResId = R.drawable.img_attention_20,
+ buttonsState = buttonsState,
),
) {
data class TooHigh(
@@ -47,6 +47,15 @@ sealed class SendFeeNotification(val config: NotificationConfig) {
title = resourceReference(id = R.string.send_network_fee_warning_title),
subtitle = resourceReference(id = R.string.send_network_fee_warning_content),
)
+
+ data class NetworkFeeUnreachable(val onRefresh: () -> Unit) : Warning(
+ title = resourceReference(R.string.send_fee_unreachable_error_title),
+ subtitle = resourceReference(R.string.send_fee_unreachable_error_text),
+ buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig(
+ text = resourceReference(R.string.warning_button_refresh),
+ onClick = onRefresh,
+ ),
+ )
}
sealed class Error(
diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldChangeConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldChangeConverter.kt
index 66f31d9233..b21b9540ac 100644
--- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldChangeConverter.kt
+++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldChangeConverter.kt
@@ -1,5 +1,6 @@
package com.tangem.features.send.impl.presentation.state.fields
+import com.tangem.blockchain.extensions.toBigDecimalOrDefault
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.features.send.impl.presentation.state.SendStates
import com.tangem.features.send.impl.presentation.state.SendUiState
@@ -41,8 +42,9 @@ internal class SendAmountFieldChangeConverter(
trimmedValue
}
- val isExceedBalance = cryptoValue.checkExceedBalance(amountState)
- val isMaxAmount = cryptoValue.checkMaxAmount(amountState)
+ val checkValue = if (amountState.isFiatValue) fiatValue else cryptoValue
+ val isExceedBalance = checkValue.checkExceedBalance(amountState)
+ val isMaxAmount = checkValue.checkMaxAmount(amountState)
return state.copy(
amountState = amountState.copy(
isPrimaryButtonEnabled = !isExceedBalance,
@@ -51,6 +53,7 @@ internal class SendAmountFieldChangeConverter(
fiatValue = fiatValue,
isError = isExceedBalance,
),
+ amountValue = cryptoValue.toBigDecimalOrDefault(),
),
feeState = feeState.copy(
isSubtract = isMaxAmount,
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/ui/fee/SendSpeedAndFeeContent.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedAndFeeContent.kt
index 4c8c06116d..2bd84ea7ba 100644
--- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedAndFeeContent.kt
+++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedAndFeeContent.kt
@@ -3,7 +3,6 @@ package com.tangem.features.send.impl.presentation.ui.fee
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
-import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
@@ -16,7 +15,6 @@ import com.tangem.core.ui.components.notifications.Notification
import com.tangem.core.ui.res.TangemTheme
import com.tangem.features.send.impl.presentation.state.SendStates
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.SendFeeNotification
import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents
import kotlinx.collections.immutable.ImmutableList
@@ -36,7 +34,6 @@ internal fun SendSpeedAndFeeContent(state: SendStates.FeeState?, clickIntents: S
.padding(
horizontal = TangemTheme.dimens.spacing16,
),
- verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
) {
item(
key = FEE_SELECTOR_KEY,
@@ -46,13 +43,12 @@ internal fun SendSpeedAndFeeContent(state: SendStates.FeeState?, clickIntents: S
clickIntents = clickIntents,
)
}
- notifications(notifications)
customFee(
feeSendState = feeSendState,
cryptoCurrencySymbol = state.cryptoCurrencyStatus.currency.symbol,
)
+ notifications(notifications)
subtractButton(
- feeSendState = feeSendState,
receivedAmount = state.receivedAmount,
isSubtract = state.isSubtract,
clickIntents = clickIntents,
@@ -69,8 +65,15 @@ internal fun LazyListScope.notifications(configs: ImmutableList TangemTheme.colors.background.primary
+ else -> TangemTheme.colors.button.disabled
+ },
iconTint = when (it) {
is SendFeeNotification.Informational -> TangemTheme.colors.icon.accent
else -> null
@@ -102,6 +105,7 @@ internal fun LazyListScope.customFee(
customValues = customValues,
selectedFee = fee.selectedFee,
symbol = cryptoCurrencySymbol,
+ modifier = Modifier.padding(top = TangemTheme.dimens.spacing12),
)
}
}
@@ -110,32 +114,19 @@ internal fun LazyListScope.customFee(
@OptIn(ExperimentalFoundationApi::class)
internal fun LazyListScope.subtractButton(
- feeSendState: FeeSelectorState,
receivedAmount: String,
isSubtract: Boolean,
clickIntents: SendClickIntents,
modifier: Modifier = Modifier,
) {
- (feeSendState as? FeeSelectorState.Content)?.let { state ->
- item {
- val selectedFeeValue = state.selectedFee
- val topPadding = if (selectedFeeValue != FeeType.CUSTOM) {
- TangemTheme.dimens.spacing8
- } else {
- TangemTheme.dimens.spacing0
- }
-
- SendSpeedSubtract(
- receivingAmount = receivedAmount,
- isSubtract = isSubtract,
- onSelectClick = clickIntents::onSubtractSelect,
- modifier = modifier
- .animateItemPlacement()
- .padding(
- top = topPadding,
- bottom = TangemTheme.dimens.spacing12,
- ),
- )
- }
+ item {
+ SendSpeedSubtract(
+ receivingAmount = receivedAmount,
+ isSubtract = isSubtract,
+ onSelectClick = clickIntents::onSubtractSelect,
+ modifier = modifier
+ .padding(vertical = TangemTheme.dimens.spacing12)
+ .animateItemPlacement(),
+ )
}
}
\ No newline at end of file
diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedSelector.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedSelector.kt
index f221817a1d..db6be28780 100644
--- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedSelector.kt
+++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendSpeedSelector.kt
@@ -2,6 +2,7 @@ package com.tangem.features.send.impl.presentation.ui.fee
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
+import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.animateColorAsState
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
@@ -13,6 +14,7 @@ import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextStyle
@@ -26,12 +28,19 @@ import com.tangem.core.ui.components.SpacerWMax
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.TangemTheme
+import com.tangem.core.ui.utils.BigDecimalFormatter
import com.tangem.features.send.impl.R
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.ui.common.FooterContainer
import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents
+private val DEFAULT_FEE_OPTIONS = listOf(
+ R.string.common_fee_selector_option_slow to R.drawable.ic_tortoise_24,
+ R.string.common_fee_selector_option_market to R.drawable.ic_bird_24,
+ R.string.common_fee_selector_option_fast to R.drawable.ic_hare_24,
+)
+
@Suppress("LongMethod")
@Composable
internal fun SendSpeedSelector(
@@ -50,10 +59,11 @@ internal fun SendSpeedSelector(
.background(TangemTheme.colors.background.action),
) {
when (state) {
+ FeeSelectorState.Error -> {
+ SendSpeedSelectorItemError()
+ }
FeeSelectorState.Loading -> {
SendSpeedSelectorItemLoading()
- SendSpeedSelectorItemLoading()
- SendSpeedSelectorItemLoading()
}
is FeeSelectorState.Content -> {
when (state.fees) {
@@ -84,7 +94,10 @@ internal fun SendSpeedSelector(
onSelect = { clickIntents.onFeeSelectorClick(FeeType.FAST) },
showDivider = state.fees.normal is Fee.Ethereum,
)
- if (state.fees.normal is Fee.Ethereum) {
+ AnimatedVisibility(
+ visible = state.fees.normal is Fee.Ethereum,
+ label = "Custom fee appearance animation",
+ ) {
SendSpeedSelectorItem(
titleRes = R.string.common_fee_selector_option_custom,
iconRes = R.drawable.ic_edit_24,
@@ -114,34 +127,52 @@ internal fun SendSpeedSelector(
@Composable
private fun SendSpeedSelectorItemLoading() {
- Row(modifier = Modifier.fillMaxWidth()) {
- RectangleShimmer(
- radius = TangemTheme.dimens.radius3,
- modifier = Modifier
- .padding(
- top = TangemTheme.dimens.spacing18,
- bottom = TangemTheme.dimens.spacing18,
- start = TangemTheme.dimens.spacing12,
- )
- .size(
- width = TangemTheme.dimens.size50,
- height = TangemTheme.dimens.size12,
- ),
- )
- SpacerWMax()
- RectangleShimmer(
- radius = TangemTheme.dimens.radius3,
- modifier = Modifier
- .padding(
- top = TangemTheme.dimens.spacing18,
- bottom = TangemTheme.dimens.spacing18,
- end = TangemTheme.dimens.spacing12,
- )
- .size(
- width = TangemTheme.dimens.size90,
- height = TangemTheme.dimens.size12,
- ),
- )
+ repeat(DEFAULT_FEE_OPTIONS.size) {
+ val (text, iconRes) = DEFAULT_FEE_OPTIONS[it]
+ Row(modifier = Modifier.fillMaxWidth()) {
+ SelectorTitleContent(
+ titleRes = text,
+ iconRes = iconRes,
+ )
+ SpacerWMax()
+ RectangleShimmer(
+ radius = TangemTheme.dimens.radius3,
+ modifier = Modifier
+ .padding(
+ top = TangemTheme.dimens.spacing18,
+ bottom = TangemTheme.dimens.spacing18,
+ end = TangemTheme.dimens.spacing12,
+ )
+ .size(
+ width = TangemTheme.dimens.size90,
+ height = TangemTheme.dimens.size12,
+ ),
+ )
+ }
+ }
+}
+
+@Composable
+private fun SendSpeedSelectorItemError() {
+ repeat(DEFAULT_FEE_OPTIONS.size) {
+ val (text, iconRes) = DEFAULT_FEE_OPTIONS[it]
+ Row(modifier = Modifier.fillMaxWidth()) {
+ SelectorTitleContent(
+ titleRes = text,
+ iconRes = iconRes,
+ )
+ SpacerWMax()
+ Text(
+ text = BigDecimalFormatter.EMPTY_BALANCE_SIGN,
+ style = TangemTheme.typography.body2,
+ color = TangemTheme.colors.text.primary1,
+ modifier = Modifier
+ .padding(
+ vertical = TangemTheme.dimens.spacing14,
+ horizontal = TangemTheme.dimens.spacing12,
+ ),
+ )
+ }
}
}
@@ -177,27 +208,11 @@ private fun SendSpeedSelectorItem(
.clickable { onSelect() },
) {
Row(modifier = Modifier.fillMaxWidth()) {
- Icon(
- painter = painterResource(iconRes),
- tint = iconTint,
- contentDescription = null,
- modifier = Modifier
- .padding(
- start = TangemTheme.dimens.spacing12,
- top = TangemTheme.dimens.spacing12,
- bottom = TangemTheme.dimens.spacing12,
- ),
- )
- Text(
- text = stringResource(titleRes),
- style = textStyle,
- color = TangemTheme.colors.text.primary1,
- modifier = Modifier
- .padding(
- start = TangemTheme.dimens.spacing8,
- top = TangemTheme.dimens.spacing14,
- bottom = TangemTheme.dimens.spacing14,
- ),
+ SelectorTitleContent(
+ titleRes = titleRes,
+ iconRes = iconRes,
+ iconTint = iconTint,
+ textStyle = textStyle,
)
if (amount != null && symbol != null) {
SelectorValueContent(
@@ -220,6 +235,37 @@ private fun SendSpeedSelectorItem(
}
}
+@Composable
+private fun SelectorTitleContent(
+ @StringRes titleRes: Int,
+ @DrawableRes iconRes: Int,
+ iconTint: Color = TangemTheme.colors.icon.informative,
+ textStyle: TextStyle = TangemTheme.typography.body2,
+) {
+ Icon(
+ painter = painterResource(iconRes),
+ tint = iconTint,
+ contentDescription = null,
+ modifier = Modifier
+ .padding(
+ start = TangemTheme.dimens.spacing12,
+ top = TangemTheme.dimens.spacing12,
+ bottom = TangemTheme.dimens.spacing12,
+ ),
+ )
+ Text(
+ text = stringResource(titleRes),
+ style = textStyle,
+ color = TangemTheme.colors.text.primary1,
+ modifier = Modifier
+ .padding(
+ start = TangemTheme.dimens.spacing8,
+ top = TangemTheme.dimens.spacing14,
+ bottom = TangemTheme.dimens.spacing14,
+ ),
+ )
+}
+
@Composable
private fun RowScope.SelectorValueContent(amount: TextReference, symbol: TextReference, textStyle: TextStyle) {
Text(
diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/SendContent.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/SendContent.kt
index b9f84d7264..ccff5dd66b 100644
--- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/SendContent.kt
+++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/SendContent.kt
@@ -197,7 +197,10 @@ internal fun LazyListScope.notifications(configs: ImmutableList TangemTheme.colors.background.action
+ else -> TangemTheme.colors.button.disabled
+ },
iconTint = when (it) {
is SendNotification.Error -> TangemTheme.colors.icon.warning
is SendNotification.Warning -> null
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..cd3c448c3f 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
@@ -33,6 +36,8 @@ interface SendClickIntents {
// endregion
// region Fee
+ fun feeReload()
+
fun onFeeSelectorClick(feeType: FeeType)
fun onCustomFeeValueChange(index: Int, value: String)
@@ -50,5 +55,9 @@ interface SendClickIntents {
fun showFee()
fun onExploreClick(txUrl: String)
+
+ fun onAmountReduceClick(reducedAmount: String)
+
+ fun onAmountReduceIgnoreClick()
// endregion
}
\ No newline at end of file
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..7b406ca2d1 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,11 +34,8 @@ 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.FeeStateFactory
import com.tangem.features.send.impl.presentation.state.fee.FeeType
import com.tangem.features.send.impl.presentation.state.fee.getFee
import com.tangem.utils.Provider
@@ -70,6 +70,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,
@@ -93,19 +94,36 @@ internal class SendViewModel @Inject constructor(
userWalletProvider = Provider { userWallet },
appCurrencyProvider = Provider(selectedAppCurrencyFlow::value),
cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus },
- coinCryptoCurrencyStatusProvider = Provider { coinCryptoCurrencyStatus },
validateWalletMemoUseCase = validateWalletMemoUseCase,
getExplorerTransactionUrlUseCase = getExplorerTransactionUrlUseCase,
)
+ private val feeStateFactory by lazy {
+ FeeStateFactory(
+ clickIntents = this,
+ currentStateProvider = Provider { uiState },
+ coinCryptoCurrencyStatusProvider = Provider { coinCryptoCurrencyStatus },
+ cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus },
+ appCurrencyProvider = Provider(selectedAppCurrencyFlow::value),
+ userWalletProvider = Provider { userWallet },
+ )
+ }
+
+ private val eventStateFactory = SendEventStateFactory(
+ clickIntents = this,
+ currentStateProvider = Provider { uiState },
+ )
+
private val sendNotificationFactory = SendNotificationFactory(
cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus },
coinCryptoCurrencyStatusProvider = Provider { coinCryptoCurrencyStatus },
currentStateProvider = Provider { uiState },
userWalletProvider = Provider { userWallet },
walletManagersFacade = walletManagersFacade,
+ clickIntents = this,
)
+ // todo convert to StateFlow
var uiState: SendUiState by mutableStateOf(stateFactory.getInitialState())
private set
@@ -122,7 +140,7 @@ internal class SendViewModel @Inject constructor(
override fun onCreate(owner: LifecycleOwner) {
subscribeOnCurrencyStatusUpdates(owner)
- getFee()
+ onFeeStateActive()
}
fun setRouter(router: InnerSendRouter, stateRouter: StateRouter) {
@@ -139,7 +157,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
},
)
@@ -280,37 +300,11 @@ internal class SendViewModel @Inject constructor(
}
}
- private fun getFee() {
- viewModelScope.launch(dispatchers.main) {
- uiState.currentState
- .filter { it == SendUiStateType.Fee }
- .onEach {
- val amountState = uiState.amountState ?: return@onEach
- val recipientState = uiState.recipientState ?: return@onEach
- val amount = amountState.amountTextField.value.toBigDecimal()
-
- uiState = stateFactory.onFeeOnLoadingState()
- getFeeUseCase.invoke(
- amount = amount,
- destination = recipientState.addressTextField.value,
- userWalletId = userWalletId,
- cryptoCurrency = cryptoCurrency,
- )
- .conflate()
- .distinctUntilChanged()
- .onEach { maybeFee ->
- maybeFee.fold(
- ifRight = {
- uiState = stateFactory.onFeeOnLoadedState(it)
- },
- ifLeft = {
- // todo add error handling [[REDACTED_JIRA]]
- },
- )
- }
- .launchIn(viewModelScope)
- }.launchIn(viewModelScope)
- }.saveIn(feeJobHolder)
+ private fun onFeeStateActive() {
+ uiState.currentState
+ .filter { it == SendUiStateType.Fee }
+ .onEach { loadFee() }
+ .launchIn(viewModelScope)
}
private fun updateNotifications() {
@@ -331,6 +325,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
@@ -415,16 +413,41 @@ internal class SendViewModel @Inject constructor(
// endregion
// region fee
+ override fun feeReload() = loadFee()
+
override fun onFeeSelectorClick(feeType: FeeType) {
- uiState = stateFactory.onFeeSelectedState(feeType)
+ uiState = feeStateFactory.onFeeSelectedState(feeType)
}
override fun onCustomFeeValueChange(index: Int, value: String) {
- uiState = stateFactory.onCustomFeeValueChange(index, value)
+ uiState = feeStateFactory.onCustomFeeValueChange(index, value)
}
override fun onSubtractSelect(value: Boolean) {
- uiState = stateFactory.onSubtractSelect(value)
+ uiState = feeStateFactory.onSubtractSelect(value)
+ }
+
+ private fun loadFee() {
+ viewModelScope.launch(dispatchers.main) {
+ val amountState = uiState.amountState ?: return@launch
+ val recipientState = uiState.recipientState ?: return@launch
+ val amount = amountState.amountTextField.value.toBigDecimal()
+
+ uiState = feeStateFactory.onFeeOnLoadingState()
+ getFeeUseCase.invoke(
+ amount = amount,
+ destination = recipientState.addressTextField.value,
+ userWalletId = userWalletId,
+ cryptoCurrency = cryptoCurrency,
+ ).fold(
+ ifRight = {
+ uiState = feeStateFactory.onFeeOnLoadedState(it)
+ },
+ ifLeft = {
+ uiState = feeStateFactory.onFeeOnErrorState()
+ },
+ )
+ }.saveIn(feeJobHolder)
}
// endregion
@@ -433,8 +456,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,50 +468,66 @@ internal class SendViewModel @Inject constructor(
override fun onExploreClick(txUrl: String) = innerRouter.openUrl(txUrl)
- private suspend fun verifyAndSendTransaction() {
+ override fun onAmountReduceClick(reducedAmount: String) {
+ uiState = stateFactory.getOnAmountValueChange(reducedAmount)
+ uiState = sendNotificationFactory.dismissHighFeeWarningState()
+ loadFee()
+ }
+
+ override fun onAmountReduceIgnoreClick() {
+ uiState = sendNotificationFactory.dismissHighFeeWarningState()
+ }
+
+ private fun verifyAndSendTransaction() {
val recipient = uiState.recipientState?.addressTextField?.value ?: return
val feeState = uiState.feeState ?: return
val feeSelectorState = feeState.feeSelectorState as? FeeSelectorState.Content ?: return
val memo = uiState.recipientState?.memoTextField?.value
val fee = feeSelectorState.getFee()
+ val amountValue = uiState.amountState?.amountValue ?: return
- val amountToSend = feeState.receivedAmountValue.convertToAmount(cryptoCurrency)
+ val amountToSend = if (feeState.isSubtract) feeState.receivedAmountValue else amountValue
- // 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.convertToAmount(cryptoCurrency),
+ 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 75863084e4..90d9bdb59f 100644
--- a/gradle/dependencies.toml
+++ b/gradle/dependencies.toml
@@ -85,9 +85,9 @@ spr-client = "3.6.2"
# endregion Other libraries
# region Tangem
-tangemBlockchainSdk = "release-app_5.5-462"
+tangemBlockchainSdk = "develop-463"
#tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds
-tangemCardSdk = "release-app_5.5-321"
+tangemCardSdk = "develop-319"
#tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^
# endregion Tangem