diff --git a/app/build.gradle b/app/build.gradle
index 7a6c354f62..56fa51dc9b 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -64,7 +64,7 @@ dependencies {
implementation 'com.google.android.material:material:1.2.1'
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.0.10'
- implementation 'com.tangem:blockchain:1.31.0'
+ implementation 'com.tangem:blockchain:1.35.0'
//lifecycle
implementation "androidx.lifecycle:lifecycle-runtime:2.2.0"
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 99243416f0..0def014c91 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -7,7 +7,6 @@
android:name="android.hardware.nfc"
android:required="true" />
-
diff --git a/app/src/main/java/com/tangem/tap/common/CurrencyConverter.kt b/app/src/main/java/com/tangem/tap/common/CurrencyConverter.kt
index 07ac39e6dc..0ca9b929bd 100644
--- a/app/src/main/java/com/tangem/tap/common/CurrencyConverter.kt
+++ b/app/src/main/java/com/tangem/tap/common/CurrencyConverter.kt
@@ -1,23 +1,36 @@
package com.tangem.tap.common
import com.tangem.common.extensions.isZero
+import com.tangem.tap.common.extensions.scaleToFiat
import java.math.BigDecimal
import java.math.RoundingMode
/**
[REDACTED_AUTHOR]
*/
-class CurrencyConverter(var rateValue: BigDecimal) {
+class CurrencyConverter(
+ private val rateValue: BigDecimal,
+ private val decimals: Int
+) {
+ private val roundingMode = RoundingMode.DOWN
- fun toFiat(crypto: BigDecimal, decimals: Int = 2): BigDecimal {
- return rateValue.multiply(crypto).setScale(decimals, RoundingMode.DOWN)
+ fun toFiat(crypto: BigDecimal, fiatDecimals: Int = 2): BigDecimal {
+ return toFiatUnscaled(crypto).setScale(fiatDecimals, roundingMode)
}
- fun toCrypto(fiat: BigDecimal, decimals: Int): BigDecimal {
- if (fiat.isZero() || rateValue.isZero()) return fiat
+ fun toFiatUnscaled(crypto: BigDecimal): BigDecimal {
+ return rateValue.multiply(crypto).setScale(decimals, roundingMode)
+ }
- val scaledRateValue = rateValue.setScale(decimals, RoundingMode.DOWN)
- val scaledFiat = fiat.setScale(decimals, RoundingMode.DOWN)
+ fun toFiatWithPrecision(crypto: BigDecimal): BigDecimal {
+ return toFiatUnscaled(crypto).scaleToFiat(true)
+ }
+
+ fun toCrypto(fiat: BigDecimal): BigDecimal {
+ if (fiat.isZero()) return fiat
+
+ val scaledRateValue = rateValue.setScale(decimals, roundingMode)
+ val scaledFiat = fiat.setScale(decimals, roundingMode)
return scaledFiat.divide(scaledRateValue, RoundingMode.UP)
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Specific.kt b/app/src/main/java/com/tangem/tap/common/extensions/Specific.kt
index 46072d5765..925969d32f 100644
--- a/app/src/main/java/com/tangem/tap/common/extensions/Specific.kt
+++ b/app/src/main/java/com/tangem/tap/common/extensions/Specific.kt
@@ -6,6 +6,7 @@ import com.google.zxing.BarcodeFormat
import com.google.zxing.EncodeHintType
import com.google.zxing.qrcode.QRCodeWriter
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel
+import com.tangem.common.extensions.isZero
import com.tangem.tap.common.redux.global.FiatCurrencyName
import com.tangem.tap.network.coinmarketcap.FiatCurrency
import java.math.BigDecimal
@@ -58,8 +59,28 @@ fun BigDecimal.toFiatString(rateValue: BigDecimal, fiatCurrencyName: FiatCurrenc
return "≈ ${fiatCurrencyName} $fiatValue"
}
+fun FiatCurrency.toFormattedString(): String = "${this.name} (${this.symbol}) - ${this.sign}"
+
fun BigDecimal.stripZeroPlainString(): String = this.stripTrailingZeros().toPlainString()
+// 0.00 -> 0.00
+// 0.00002345 -> 0.00002
+// 1.00002345 -> 1.00
+// 1.45002345 -> 1.45
+fun BigDecimal.scaleToFiat(applyPrecision: Boolean = false): BigDecimal {
+ if (this.isZero()) return this
+
+ val scaledFiat = this.setScale(2, RoundingMode.DOWN)
+ return if (scaledFiat.isZero() && applyPrecision) this.setPrecision(1)
+ else scaledFiat
+
+}
+
+fun BigDecimal.setPrecision(precision: Int, roundingMode: RoundingMode = RoundingMode.DOWN): BigDecimal {
+ if (precision == precision() || scale() <= precision) return this
+ return this.setScale(scale() - precision() + precision, roundingMode)
+}
+
fun BigDecimal.isPositive(): Boolean = this.compareTo(BigDecimal.ZERO) == 1
fun BigDecimal.isNegative(): Boolean = this.compareTo(BigDecimal.ZERO) == -1
fun BigDecimal.isGreaterThan(value: BigDecimal): Boolean = this.compareTo(value) == 1
@@ -73,6 +94,4 @@ fun BigDecimal.isGreaterThanOrEqual(value: BigDecimal): Boolean {
fun BigDecimal.isLessThanOrEqual(value: BigDecimal): Boolean {
val compareResult = this.compareTo(value)
return compareResult == -1 || compareResult == 0
-}
-
-fun FiatCurrency.toFormattedString(): String = "${this.name} (${this.symbol}) - ${this.sign}"
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/redux/NotificationsMiddleware.kt b/app/src/main/java/com/tangem/tap/common/redux/NotificationsMiddleware.kt
index 177089ffba..7e84963643 100644
--- a/app/src/main/java/com/tangem/tap/common/redux/NotificationsMiddleware.kt
+++ b/app/src/main/java/com/tangem/tap/common/redux/NotificationsMiddleware.kt
@@ -3,7 +3,9 @@ package com.tangem.tap.common.redux
import android.widget.Toast
import androidx.coordinatorlayout.widget.CoordinatorLayout
import com.google.android.material.snackbar.Snackbar
+import com.tangem.tap.domain.MultiMessageError
import com.tangem.tap.domain.TapError
+import com.tangem.tap.domain.assembleErrorIds
import com.tangem.tap.notificationsHandler
import org.rekotlin.Action
import org.rekotlin.Middleware
@@ -39,19 +41,30 @@ class NotificationsHandler(coordinatorLayout: CoordinatorLayout) {
Toast.makeText(it.context, it.context.getString(message), Toast.LENGTH_LONG).show()
}
}
+
+ fun showNotification(errorList: List, builder: (List) -> String) {
+ val context = baseLayout.get()?.context ?: return
+
+ val message = builder(errorList.map { context.getString(it) })
+ showNotification(message)
+ }
}
val notificationsMiddleware: Middleware = { dispatch, state ->
{ next ->
{ action ->
- if (action is NotificationAction) {
- notificationsHandler?.showNotification(action.messageResource)
- }
- if (action is ToastNotificationAction) {
- notificationsHandler?.showToastNotification(action.messageResource)
- }
- if (action is ErrorAction) {
- notificationsHandler?.showNotification(action.error.localizedMessage)
+ when (action) {
+ is NotificationAction -> notificationsHandler?.showNotification(action.messageResource)
+ is ToastNotificationAction -> notificationsHandler?.showToastNotification(action.messageResource)
+ is ErrorAction -> {
+ when (action.error) {
+ is MultiMessageError -> {
+ val multiError = action.error as MultiMessageError
+ notificationsHandler?.showNotification(multiError.assembleErrorIds(), multiError.builder)
+ }
+ else -> notificationsHandler?.showNotification(action.error.localizedMessage)
+ }
+ }
}
next(action)
}
diff --git a/app/src/main/java/com/tangem/tap/domain/TapErrors.kt b/app/src/main/java/com/tangem/tap/domain/TapErrors.kt
index 4f98257223..4bd45c181d 100644
--- a/app/src/main/java/com/tangem/tap/domain/TapErrors.kt
+++ b/app/src/main/java/com/tangem/tap/domain/TapErrors.kt
@@ -3,13 +3,39 @@ package com.tangem.tap.domain
import androidx.annotation.StringRes
import com.tangem.wallet.R
-sealed class TapError(@StringRes val localizedMessage: Int): Throwable() {
- object PayIdAlreadyCreated: TapError(R.string.error_payid_already_created)
- object PayIdCreatingError: TapError(R.string.error_creating_payid)
- object PayIdEmptyField: TapError(R.string.wallet_create_payid_empty)
- object UnknownBlockchain: TapError(R.string.wallet_unknown_blockchain)
- object NoInternetConnection: TapError(R.string.notification_no_internet)
- object InsufficientBalance: TapError(R.string.error_insufficient_balance)
- object BlockchainInternalError: TapError(R.string.error_blockchain_internal)
- object UnknownError: TapError(R.string.error_unknown)
+interface TapErrors
+interface MultiMessageError : TapErrors {
+ val errorList: List
+ val builder: (List) -> String
+}
+
+sealed class TapError(@StringRes val localizedMessage: Int) : Throwable(), TapErrors {
+ object UnknownError : TapError(R.string.error_unknown)
+ object PayIdAlreadyCreated : TapError(R.string.error_payid_already_created)
+ object PayIdCreatingError : TapError(R.string.error_creating_payid)
+ object PayIdEmptyField : TapError(R.string.wallet_create_payid_empty)
+ object UnknownBlockchain : TapError(R.string.wallet_unknown_blockchain)
+ object NoInternetConnection : TapError(R.string.notification_no_internet)
+ object InsufficientBalance : TapError(R.string.error_insufficient_balance)
+ object BlockchainInternalError : TapError(R.string.error_blockchain_internal)
+ object AmountExceedsBalance : TapError(R.string.amount_exceeds_balance)
+ object FeeExceedsBalance : TapError(R.string.fee_exceeds_balance)
+ object TotalExceedsBalance : TapError(R.string.total_exceeds_balance)
+ object InvalidAmountValue : TapError(R.string.invalid_amount_value)
+ object InvalidFeeValue : TapError(R.string.invalid_fee_value)
+ object DustAmount : TapError(R.string.dust_amount)
+ object DustChang : TapError(R.string.dust_change)
+ data class ValidateTransactionErrors(
+ override val errorList: List,
+ override val builder: (List) -> String
+ ) : TapError(-1), MultiMessageError
+}
+
+fun TapErrors.assembleErrorIds(): MutableList {
+ val idList = mutableListOf()
+ when (this) {
+ is MultiMessageError -> this.errorList.forEach { idList.addAll(it.assembleErrorIds()) }
+ is TapError -> idList.add(this.localizedMessage)
+ }
+ return idList
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt
index ba2a2b0387..9cc2fbb831 100644
--- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt
+++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt
@@ -41,7 +41,7 @@ private fun handleEraseWallet(action: DetailsAction.EraseWallet, state: DetailsS
return when (action) {
DetailsAction.EraseWallet.Check -> {
val notAllowedByCard = state.card?.settingsMask?.contains(Settings.ProhibitPurgeWallet) == true
- val notEmpty = state.wallet?.transactions?.isNullOrEmpty() != true ||
+ val notEmpty = state.wallet?.recentTransactions?.isNullOrEmpty() != true ||
state.wallet.amounts.toList().unzip().second.map { it.value?.isZero() }.contains(false)
val eraseWalletState = when {
notAllowedByCard -> EraseWalletState.NotAllowedByCard
diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/SendScreenAction.kt b/app/src/main/java/com/tangem/tap/features/send/redux/SendScreenAction.kt
index 2dba8c385f..bd93f890d5 100644
--- a/app/src/main/java/com/tangem/tap/features/send/redux/SendScreenAction.kt
+++ b/app/src/main/java/com/tangem/tap/features/send/redux/SendScreenAction.kt
@@ -26,14 +26,16 @@ data class PrepareSendScreen(
// Address or PayId
sealed class AddressPayIdActionUi : SendScreenActionUi {
- data class ChangeAddressOrPayId(val data: String) : AddressPayIdActionUi()
+ data class HandleUserInput(val data: String) : AddressPayIdActionUi()
+ data class PasteAddressPayId(val data: String) : AddressPayIdActionUi()
+ data class CheckClipboard(val data: String?) : AddressPayIdActionUi()
+ object CheckAddressPayId: AddressPayIdActionUi()
data class SetTruncateHandler(val handler: (String) -> String) : AddressPayIdActionUi()
data class TruncateOrRestore(val truncate: Boolean) : AddressPayIdActionUi()
}
sealed class AddressPayIdVerifyAction : SendScreenAction {
enum class Error {
- IS_NOT_PAY_ID,
PAY_ID_UNSUPPORTED_BY_BLOCKCHAIN,
PAY_ID_NOT_REGISTERED,
PAY_ID_REQUEST_FAILED,
@@ -41,38 +43,31 @@ sealed class AddressPayIdVerifyAction : SendScreenAction {
ADDRESS_SAME_AS_WALLET
}
- data class VerifyClipboard(val data: String?) : AddressPayIdVerifyAction()
data class ChangePasteBtnEnableState(val isEnabled: Boolean) : AddressPayIdVerifyAction()
sealed class PayIdVerification : AddressPayIdVerifyAction() {
- data class SetError(val payId: String, val error: Error) : PayIdVerification()
- data class SetPayIdWalletAddress(val payId: String, val payIdWalletAddress: String) : PayIdVerification()
+ data class SetPayIdError(val error: Error?) : PayIdVerification()
+ data class SetPayIdWalletAddress(val payId: String, val payIdWalletAddress: String, val isUserInput: Boolean) : PayIdVerification()
}
sealed class AddressVerification : AddressPayIdVerifyAction() {
- data class SetError(val address: String, val error: Error) : AddressVerification()
- data class SetWalletAddress(val address: String) : AddressVerification()
+ data class SetAddressError(val error: Error?) : AddressVerification()
+ data class SetWalletAddress(val address: String, val isUserInput: Boolean) : AddressVerification()
}
}
// Amount to send
sealed class AmountActionUi : SendScreenActionUi {
+ data class HandleUserInput(val data: String) : AmountActionUi()
+ object CheckAmountToSend : AmountActionUi()
object SetMaxAmount : AmountActionUi()
- data class CheckAmountToSend(val data: String? = null) : AmountActionUi()
data class SetMainCurrency(val mainCurrency: MainCurrencyType) : AmountActionUi()
object ToggleMainCurrency : AmountActionUi()
}
sealed class AmountAction : SendScreenAction {
- enum class Error {
- FEE_GREATER_THAN_AMOUNT,
- AMOUNT_WITH_FEE_GREATER_THAN_BALANCE
- }
-
- sealed class AmountVerification : AmountAction() {
- data class SetAmount(val amount: BigDecimal) : AmountVerification()
- data class SetError(val amount: BigDecimal, val error: Error) : AmountVerification()
- }
+ data class SetAmount(val amount: BigDecimal, val isUserInput: Boolean) : AmountAction()
+ data class SetAmountError(val error: TapError?) : AmountAction()
}
// Fee
diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/AddressPayIdMiddleware.kt b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/AddressPayIdMiddleware.kt
index b0169f3df7..f9e70c740f 100644
--- a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/AddressPayIdMiddleware.kt
+++ b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/AddressPayIdMiddleware.kt
@@ -1,52 +1,77 @@
package com.tangem.tap.features.send.redux.middlewares
import com.tangem.blockchain.common.Wallet
-import com.tangem.blockchain.common.WalletManager
import com.tangem.commands.common.network.Result
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.domain.PayIdManager
import com.tangem.tap.domain.isPayIdSupported
-import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction.*
+import com.tangem.tap.features.send.redux.AddressPayIdActionUi
+import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction
+import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction.AddressVerification.SetAddressError
+import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction.AddressVerification.SetWalletAddress
+import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction.Error
+import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction.PayIdVerification.SetPayIdError
+import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction.PayIdVerification.SetPayIdWalletAddress
import com.tangem.tap.features.send.redux.FeeAction
import com.tangem.tap.scope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
+import org.rekotlin.Action
import org.rekotlin.DispatchFunction
/**
[REDACTED_AUTHOR]
*/
internal class AddressPayIdMiddleware {
- fun handle(data: String?, appState: AppState?, dispatch: DispatchFunction) {
- if (data == null) {
- dispatch(AddressVerification.SetError("", Error.ADDRESS_INVALID_OR_UNSUPPORTED_BY_BLOCKCHAIN))
- return
- }
- val sendState = appState?.sendState ?: return
- val walletManager = sendState.walletManager ?: return
- if (data == sendState.addressPayIdState.etFieldValue) return
-
- if (PayIdManager.isPayId(data)) {
- verifyPayId(data, walletManager, dispatch)
- } else {
- val supposedAddress = extractAddressFromShareUri(data)
- dispatch(PayIdVerification.SetError(supposedAddress, Error.IS_NOT_PAY_ID))
-
- val failReason = isValidBlockchainAddressAndNotTheSameAsWallet(walletManager.wallet, supposedAddress)
- if (failReason == null) {
- dispatch(AddressVerification.SetWalletAddress(supposedAddress))
- } else {
- dispatch(AddressVerification.SetError(supposedAddress, failReason))
- }
+ fun handle(action: AddressPayIdActionUi, appState: AppState?, dispatch: (Action) -> Unit) {
+ when (action) {
+ is AddressPayIdActionUi.HandleUserInput -> handleUserInput(action.data, appState, dispatch)
+ is AddressPayIdActionUi.PasteAddressPayId -> pasteAddressPayId(action.data, dispatch)
+ is AddressPayIdActionUi.CheckClipboard -> verifyClipboard(action.data, appState, dispatch)
+ is AddressPayIdActionUi.CheckAddressPayId -> verifyAddressPayId(appState, dispatch)
+ else -> return
}
}
- private fun verifyPayId(payId: String, walletManager: WalletManager, dispatch: DispatchFunction) {
- val blockchain = walletManager.wallet.blockchain
+ private fun handleUserInput(input: String, appState: AppState?, dispatch: DispatchFunction) {
+ val sendState = appState?.sendState ?: return
+ if (input == sendState.addressPayIdState.viewFieldValue.value) return
+
+ setAddressAndCheck(input, true, dispatch)
+ }
+
+ private fun pasteAddressPayId(data: String, dispatch: (Action) -> Unit) {
+ setAddressAndCheck(data, false, dispatch)
+ }
+
+ private fun setAddressAndCheck(data: String, isUserInput: Boolean, dispatch: (Action) -> Unit) {
+ if (PayIdManager.isPayId(data)) {
+ dispatch(SetPayIdWalletAddress(data, "", isUserInput))
+ } else {
+ dispatch(SetWalletAddress(data, isUserInput))
+ }
+ dispatch(AddressPayIdActionUi.CheckAddressPayId)
+ }
+
+ private fun verifyAddressPayId(appState: AppState?, dispatch: (Action) -> Unit) {
+ val sendState = appState?.sendState ?: return
+ val wallet = sendState.walletManager?.wallet ?: return
+ val addressPayId = sendState.addressPayIdState.normalFieldValue ?: return
+ val isUserInput = sendState.addressPayIdState.viewFieldValue.isFromUserInput
+
+ if (PayIdManager.isPayId(addressPayId)) {
+ verifyPayId(addressPayId, wallet, isUserInput, dispatch)
+ } else {
+ verifyAddress(addressPayId, wallet, isUserInput, dispatch)
+ }
+ }
+
+ private fun verifyPayId(payId: String, wallet: Wallet, isUserInput: Boolean, dispatch: DispatchFunction) {
+ val blockchain = wallet.blockchain
if (!blockchain.isPayIdSupported()) {
- dispatch(PayIdVerification.SetError(payId, Error.PAY_ID_UNSUPPORTED_BY_BLOCKCHAIN))
+ dispatch(SetPayIdError(Error.PAY_ID_UNSUPPORTED_BY_BLOCKCHAIN))
return
}
@@ -57,27 +82,37 @@ internal class AddressPayIdMiddleware {
is Result.Success -> {
val address = result.data.getAddress()
if (address == null) {
- dispatch(PayIdVerification.SetError(payId, Error.PAY_ID_NOT_REGISTERED))
+ dispatch(SetPayIdError(Error.PAY_ID_NOT_REGISTERED))
return@withContext
}
- val failReason = isValidBlockchainAddressAndNotTheSameAsWallet(walletManager.wallet, address)
+ val failReason = isValidBlockchainAddressAndNotTheSameAsWallet(wallet, address)
if (failReason == null) {
- dispatch(PayIdVerification.SetPayIdWalletAddress(payId, address))
+ dispatch(SetPayIdWalletAddress(payId, address, isUserInput))
dispatch(FeeAction.RequestFee)
} else {
- dispatch(AddressVerification.SetError(payId, failReason))
+ dispatch(SetAddressError(failReason))
}
-
}
is Result.Failure -> {
- dispatch(PayIdVerification.SetError(payId, Error.PAY_ID_REQUEST_FAILED))
+ dispatch(SetPayIdError(Error.PAY_ID_REQUEST_FAILED))
}
}
}
}
}
+ private fun verifyAddress(address: String, wallet: Wallet, isUserInput: Boolean, dispatch: (Action) -> Unit) {
+ val supposedAddress = extractAddressFromShareUri(address)
+
+ val failReason = isValidBlockchainAddressAndNotTheSameAsWallet(wallet, supposedAddress)
+ if (failReason == null) {
+ dispatch(SetWalletAddress(supposedAddress, isUserInput))
+ } else {
+ dispatch(SetAddressError(failReason))
+ }
+ }
+
private fun isValidBlockchainAddressAndNotTheSameAsWallet(wallet: Wallet, address: String): Error? {
return if (wallet.blockchain.validateAddress(address)) {
if (wallet.address != address) {
@@ -97,4 +132,27 @@ internal class AddressPayIdMiddleware {
return if (prefixes.isEmpty()) shareUri
else shareUri.replace(prefixes[0], "")
}
+
+ private fun verifyClipboard(input: String?, appState: AppState?, dispatch: DispatchFunction) {
+ val addressPayId = input ?: return
+ val wallet = appState?.sendState?.walletManager?.wallet ?: return
+
+
+ val internalDispatcher: (Action) -> Unit = {
+ when (it) {
+ is SetWalletAddress, is SetPayIdWalletAddress -> {
+ dispatch(AddressPayIdVerifyAction.ChangePasteBtnEnableState(true))
+ }
+ is SetAddressError, is SetPayIdError -> {
+ dispatch(AddressPayIdVerifyAction.ChangePasteBtnEnableState(false))
+ }
+ }
+ }
+
+ if (PayIdManager.isPayId(addressPayId)) {
+ verifyPayId(addressPayId, wallet, false, internalDispatcher)
+ } else {
+ verifyAddress(addressPayId, wallet, false, internalDispatcher)
+ }
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/AmountMiddleware.kt b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/AmountMiddleware.kt
index 259051b8a4..3c001122bd 100644
--- a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/AmountMiddleware.kt
+++ b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/AmountMiddleware.kt
@@ -1,14 +1,16 @@
package com.tangem.tap.features.send.redux.middlewares
-import com.tangem.tap.common.CurrencyConverter
-import com.tangem.tap.common.extensions.isGreaterThan
-import com.tangem.tap.common.extensions.isGreaterThanOrEqual
+import com.tangem.blockchain.common.Amount
+import com.tangem.blockchain.common.TransactionError
+import com.tangem.common.extensions.isZero
+import com.tangem.tap.common.extensions.isNegative
import com.tangem.tap.common.redux.AppState
+import com.tangem.tap.domain.TapError
import com.tangem.tap.features.send.redux.AmountAction
+import com.tangem.tap.features.send.redux.AmountActionUi
import com.tangem.tap.features.send.redux.ReceiptAction
import com.tangem.tap.features.send.redux.SendAction
import com.tangem.tap.features.send.redux.states.MainCurrencyType
-import com.tangem.tap.store
import org.rekotlin.Action
import java.math.BigDecimal
@@ -17,83 +19,93 @@ import java.math.BigDecimal
*/
class AmountMiddleware {
- fun handle(rawData: String?, appState: AppState?, dispatch: (Action) -> Unit) {
+ fun handle(action: AmountActionUi, appState: AppState?, dispatch: (Action) -> Unit) {
+ when (action) {
+ is AmountActionUi.HandleUserInput -> handleUserInput(action.data, appState, dispatch)
+ is AmountActionUi.CheckAmountToSend -> checkAmountToSend(appState, dispatch)
+ is AmountActionUi.SetMaxAmount -> setMaxAmount(appState, dispatch)
+ is AmountActionUi.ToggleMainCurrency -> toggleMainCurrency(appState, dispatch)
+ is AmountActionUi.SetMainCurrency -> return
+ }
+ }
+
+ private fun handleUserInput(data: String, appState: AppState?, dispatch: (Action) -> Unit) {
val sendState = appState?.sendState ?: return
- val rawData = rawData ?: store.state.sendState.amountState.viewAmountValue
- val data = if (rawData == ".") "0.0" else rawData
- val proposedAmountValue = when {
+ val data = if (data == ".") "0.0" else data
+ val inputValue = when {
data.isEmpty() || data == "0" -> BigDecimal.ZERO
else -> BigDecimal(data)
}
- val balanceCrypto = sendState.amount?.value ?: BigDecimal.ZERO
- val amountChecker = when (sendState.amountState.mainCurrency.value) {
- MainCurrencyType.FIAT -> FiatAmountChecker(balanceCrypto, sendState.currencyConverter)
- MainCurrencyType.CRYPTO -> CryptoAmountChecker(balanceCrypto)
+ if (inputValue.isZero() && sendState.amountState.amountToSendCrypto.isZero()) return
+
+ dispatch(AmountAction.SetAmount(inputValue, true))
+ dispatch(AmountActionUi.CheckAmountToSend)
+ }
+
+ private fun checkAmountToSend(appState: AppState?, dispatch: (Action) -> Unit) {
+ val sendState = appState?.sendState ?: return
+ val walletManager = sendState.walletManager ?: return
+
+ val inputCrypto = sendState.amountState.amountToSendCrypto
+ if (sendState.amountState.viewAmountValue.value == "0" && inputCrypto.isZero()) {
+ dispatch(ReceiptAction.RefreshReceipt)
+ dispatch(SendAction.ChangeSendButtonState(sendState.getButtonState()))
+ return
}
- val checkResult = amountChecker.check(
- proposedAmountValue,
- sendState.feeState.getCurrentFee(),
- sendState.feeState.feeIsIncluded
- )
- if (checkResult.error == null) {
- dispatch(AmountAction.AmountVerification.SetAmount(checkResult.amount))
+ val feeCrypto = sendState.feeState.getCurrentFee()
+
+ val feeAmount = Amount(feeCrypto, walletManager.wallet.blockchain)
+ val totalAmount = Amount(sendState.getTotalAmountToSend(inputCrypto), walletManager.wallet.blockchain)
+
+ val transactionErrors = walletManager.validateTransaction(totalAmount, feeAmount)
+ if (transactionErrors.isEmpty()) {
+ dispatch(AmountAction.SetAmountError(null))
} else {
- dispatch(AmountAction.AmountVerification.SetError(checkResult.amount, checkResult.error))
+ val tapErrors = transactionErrors.map {
+ when (it) {
+ TransactionError.AmountExceedsBalance -> TapError.AmountExceedsBalance
+ TransactionError.FeeExceedsBalance -> TapError.FeeExceedsBalance
+ TransactionError.TotalExceedsBalance -> TapError.TotalExceedsBalance
+ TransactionError.InvalidAmountValue -> TapError.InvalidAmountValue
+ TransactionError.InvalidFeeValue -> TapError.InvalidFeeValue
+ TransactionError.DustAmount -> TapError.DustAmount
+ TransactionError.DustChange -> TapError.DustChang
+ else -> TapError.UnknownError
+ }
+ }
+ val error = TapError.ValidateTransactionErrors(tapErrors) { it.joinToString("\r\n") }
+ dispatch(AmountAction.SetAmountError(error))
}
dispatch(ReceiptAction.RefreshReceipt)
dispatch(SendAction.ChangeSendButtonState(sendState.getButtonState()))
}
-}
+ private fun setMaxAmount(appState: AppState?, dispatch: (Action) -> Unit) {
+ val sendState = appState?.sendState ?: return
+ val amountState = sendState.amountState
-interface AmountChecker {
- data class Result(val amount: BigDecimal, val error: AmountAction.Error? = null)
+ val maxAmount = if (sendState.feeState.feeIsIncluded) amountState.balanceCrypto
+ else amountState.balanceCrypto.minus(sendState.feeState.getCurrentFee())
- fun check(value: BigDecimal, feeCrypto: BigDecimal, feeIsIncluded: Boolean): Result
-}
-
-abstract class BaseAmountChecker(
- protected val balanceCrypto: BigDecimal
-) : AmountChecker {
-
- override fun check(value: BigDecimal, feeCrypto: BigDecimal, feeIsIncluded: Boolean): AmountChecker.Result {
- val convertedFee = convert(feeCrypto)
- val convertedBalance = convert(balanceCrypto)
- if (feeIsIncluded) {
- return if (value.isGreaterThan(convertedFee)) {
- if (convertedBalance.isGreaterThanOrEqual(value)) {
- AmountChecker.Result(value)
- } else {
- AmountChecker.Result(value, AmountAction.Error.FEE_GREATER_THAN_AMOUNT)
- }
- } else {
- AmountChecker.Result(value, AmountAction.Error.AMOUNT_WITH_FEE_GREATER_THAN_BALANCE)
- }
+ if (maxAmount.isNegative()) {
+ dispatch(AmountAction.SetAmountError(TapError.FeeExceedsBalance))
} else {
- val amountWithFee = value.plus(convertedFee)
- return if (convertedBalance.isGreaterThanOrEqual(amountWithFee)) {
- AmountChecker.Result(value)
- } else {
- AmountChecker.Result(value, AmountAction.Error.AMOUNT_WITH_FEE_GREATER_THAN_BALANCE)
- }
+ val currentCurrencyValue = if (amountState.mainCurrency.type == MainCurrencyType.CRYPTO) maxAmount
+ else sendState.convertToFiat(maxAmount)
+
+ dispatch(AmountAction.SetAmount(currentCurrencyValue, false))
}
}
- protected abstract fun convert(value: BigDecimal): BigDecimal
-}
+ private fun toggleMainCurrency(appState: AppState?, dispatch: (Action) -> Unit) {
+ val amountState = appState?.sendState?.amountState ?: return
-class CryptoAmountChecker(
- balanceCrypto: BigDecimal
-) : BaseAmountChecker(balanceCrypto) {
- override fun convert(value: BigDecimal): BigDecimal = value
-}
+ val type = if (amountState.mainCurrency.type == MainCurrencyType.FIAT) MainCurrencyType.CRYPTO
+ else MainCurrencyType.FIAT
-class FiatAmountChecker(
- balanceCrypto: BigDecimal,
- private val converter: CurrencyConverter
-) : BaseAmountChecker(balanceCrypto) {
- override fun convert(value: BigDecimal): BigDecimal = converter.toFiat(value)
+ dispatch(AmountActionUi.SetMainCurrency(type))
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/RequestFeeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/RequestFeeMiddleware.kt
index f1d202cf46..5ba0bc7bc2 100644
--- a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/RequestFeeMiddleware.kt
+++ b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/RequestFeeMiddleware.kt
@@ -28,7 +28,7 @@ class RequestFeeMiddleware {
if (!sendState.addressPayIdIsReady()) {
dispatch(FeeAction.FeeCalculation.SetFeeError(FeeAction.Error.ADDRESS_OR_AMOUNT_IS_EMPTY))
- dispatch(FeeAction.ChangeLayoutVisibility(main = false, controls = true, chipGroup = true))
+ dispatch(FeeAction.ChangeLayoutVisibility(main = false, chipGroup = true))
dispatch(ReceiptAction.RefreshReceipt)
dispatch(SendAction.ChangeSendButtonState(sendState.getButtonState()))
return
@@ -60,7 +60,7 @@ class RequestFeeMiddleware {
dispatch(FeeAction.ChangeLayoutVisibility(main = true, chipGroup = false))
}
} else {
- dispatch(FeeAction.ChangeLayoutVisibility(main = true, controls = true, chipGroup = true))
+ dispatch(FeeAction.ChangeLayoutVisibility(main = true, chipGroup = true))
}
}
is Result.Failure -> {
diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt
index 3f55dfddd5..cdf07f2690 100644
--- a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt
+++ b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt
@@ -7,9 +7,8 @@ import com.tangem.blockchain.extensions.SimpleResult
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.common.redux.navigation.NavigationAction
import com.tangem.tap.domain.TapError
-import com.tangem.tap.features.send.redux.AddressPayIdActionUi.ChangeAddressOrPayId
-import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction.*
-import com.tangem.tap.features.send.redux.AmountActionUi.CheckAmountToSend
+import com.tangem.tap.features.send.redux.AddressPayIdActionUi
+import com.tangem.tap.features.send.redux.AmountActionUi
import com.tangem.tap.features.send.redux.FeeAction.RequestFee
import com.tangem.tap.features.send.redux.SendAction
import com.tangem.tap.features.send.redux.SendActionUi
@@ -29,20 +28,8 @@ val sendMiddleware: Middleware = { dispatch, appState ->
{ nextDispatch ->
{ action ->
when (action) {
- is ChangeAddressOrPayId -> AddressPayIdMiddleware().handle(action.data, appState(), dispatch)
- is VerifyClipboard -> {
- AddressPayIdMiddleware().handle(action.data, appState()) {
- when (it) {
- is AddressVerification.SetWalletAddress, is PayIdVerification.SetPayIdWalletAddress -> {
- dispatch(ChangePasteBtnEnableState(true))
- }
- is AddressVerification.SetError, is PayIdVerification.SetError -> {
- dispatch(ChangePasteBtnEnableState(false))
- }
- }
- }
- }
- is CheckAmountToSend -> AmountMiddleware().handle(action.data, appState(), dispatch)
+ is AddressPayIdActionUi -> AddressPayIdMiddleware().handle(action, appState(), dispatch)
+ is AmountActionUi -> AmountMiddleware().handle(action, appState(), dispatch)
is RequestFee -> RequestFeeMiddleware().handle(appState(), dispatch)
is SendActionUi.SendAmountToRecipient -> verifyAndSendTransaction(appState(), dispatch)
}
@@ -59,7 +46,7 @@ private fun verifyAndSendTransaction(appState: AppState?, dispatch: (Action) ->
val recipientAddress = sendState.addressPayIdState.recipientWalletAddress!!
val feeAmount = Amount(sendState.feeState.getCurrentFee(), blockchain)
- val amountToSend = Amount(sendState.amountState.amountToSendCrypto, blockchain, recipientAddress)
+ val amountToSend = Amount(sendState.getTotalAmountToSend(), blockchain, recipientAddress)
val txSender = walletManager as TransactionSender
diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/AddressPayIdReducer.kt b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/AddressPayIdReducer.kt
index cc5fa3430f..c20db4385b 100644
--- a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/AddressPayIdReducer.kt
+++ b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/AddressPayIdReducer.kt
@@ -6,6 +6,7 @@ import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction.AddressVerifi
import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction.PayIdVerification
import com.tangem.tap.features.send.redux.SendScreenAction
import com.tangem.tap.features.send.redux.states.AddressPayIdState
+import com.tangem.tap.features.send.redux.states.InputViewValue
import com.tangem.tap.features.send.redux.states.SendState
/**
@@ -20,24 +21,43 @@ class AddressPayIdReducer : SendInternalReducer {
private fun handleUiAction(action: AddressPayIdActionUi, sendState: SendState, state: AddressPayIdState): SendState {
val result = when (action) {
- is AddressPayIdActionUi.ChangeAddressOrPayId -> state
+ is AddressPayIdActionUi.HandleUserInput -> state
is AddressPayIdActionUi.SetTruncateHandler -> state.copy(truncateHandler = action.handler)
is AddressPayIdActionUi.TruncateOrRestore -> {
- if (action.truncate) state.copy(etFieldValue = state.truncatedFieldValue)
- else state.copy(etFieldValue = state.normalFieldValue)
+ val value = if (action.truncate) state.truncatedFieldValue ?: ""
+ else state.normalFieldValue ?: ""
+ state.copy(viewFieldValue = state.viewFieldValue.copy(value = value))
}
+ is AddressPayIdActionUi.PasteAddressPayId -> return sendState
+ is AddressPayIdActionUi.CheckClipboard -> return sendState
+ is AddressPayIdActionUi.CheckAddressPayId -> return sendState
}
return updateLastState(sendState.copy(addressPayIdState = result), result)
}
private fun handleAction(action: AddressPayIdVerifyAction, sendState: SendState, state: AddressPayIdState): SendState {
val result = when (action) {
- is PayIdVerification.SetPayIdWalletAddress -> state.copyPayIdWalletAddress(action.payId, action.payIdWalletAddress)
- is PayIdVerification.SetError -> state.copyPayIdError(action.payId, action.error)
- is AddressVerification.SetWalletAddress -> state.copyWalletAddress(action.address)
- is AddressVerification.SetError -> state.copyError(action.address, action.error)
+ is PayIdVerification.SetPayIdWalletAddress -> {
+ state.copy(
+ viewFieldValue = InputViewValue(action.payId, action.isUserInput),
+ normalFieldValue = action.payId,
+ truncatedFieldValue = state.truncate(action.payId),
+ recipientWalletAddress = action.payIdWalletAddress,
+ error = null
+ )
+ }
+ is AddressVerification.SetWalletAddress -> {
+ state.copy(
+ viewFieldValue = InputViewValue(action.address, action.isUserInput),
+ normalFieldValue = action.address,
+ truncatedFieldValue = state.truncate(action.address),
+ recipientWalletAddress = action.address,
+ error = null
+ )
+ }
is AddressPayIdVerifyAction.ChangePasteBtnEnableState -> state.copy(pasteIsEnabled = action.isEnabled)
- is AddressPayIdVerifyAction.VerifyClipboard -> state
+ is AddressVerification.SetAddressError -> state.copy(error = action.error, recipientWalletAddress = null)
+ is PayIdVerification.SetPayIdError -> state.copy(error = action.error, recipientWalletAddress = null)
}
return updateLastState(sendState.copy(addressPayIdState = result), result)
}
diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/AmountReducer.kt b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/AmountReducer.kt
index 5129a95646..db8fe61e47 100644
--- a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/AmountReducer.kt
+++ b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/AmountReducer.kt
@@ -1,18 +1,16 @@
package com.tangem.tap.features.send.redux.reducers
import com.tangem.common.extensions.isZero
-import com.tangem.tap.common.CurrencyConverter
-import com.tangem.tap.common.extensions.isNegative
+import com.tangem.tap.common.extensions.scaleToFiat
import com.tangem.tap.common.extensions.stripZeroPlainString
import com.tangem.tap.features.send.redux.AmountAction
import com.tangem.tap.features.send.redux.AmountActionUi
-import com.tangem.tap.features.send.redux.AmountActionUi.*
+import com.tangem.tap.features.send.redux.AmountActionUi.SetMainCurrency
import com.tangem.tap.features.send.redux.SendScreenAction
import com.tangem.tap.features.send.redux.states.AmountState
+import com.tangem.tap.features.send.redux.states.InputViewValue
import com.tangem.tap.features.send.redux.states.MainCurrencyType
import com.tangem.tap.features.send.redux.states.SendState
-import com.tangem.tap.features.send.redux.states.Value
-import com.tangem.tap.store
import java.math.BigDecimal
/**
@@ -26,97 +24,65 @@ class AmountReducer : SendInternalReducer {
}
private fun handleUiAction(action: AmountActionUi, sendState: SendState, state: AmountState): SendState {
- val converter = sendState.currencyConverter
val result = when (action) {
- is ToggleMainCurrency -> {
- val type = if (state.mainCurrency.value == MainCurrencyType.FIAT) MainCurrencyType.CRYPTO
- else MainCurrencyType.FIAT
-
- return handleUiAction(SetMainCurrency(type), sendState, state)
- }
is SetMainCurrency -> {
when (action.mainCurrency) {
MainCurrencyType.FIAT -> {
val fiatToSend = if (state.amountToSendCrypto.isZero()) BigDecimal.ZERO
- else converter.toFiat(state.amountToSendCrypto)
+ else sendState.convertToFiat(state.amountToSendCrypto)
+ val rescaledBalance = sendState.convertToFiat(state.balanceCrypto, true)
state.copy(
- viewAmountValue = fiatToSend.stripZeroPlainString(),
- viewBalanceValue = converter.toFiat(state.balanceCrypto).stripZeroPlainString(),
- mainCurrency = Value(MainCurrencyType.FIAT, store.state.globalState.appCurrency),
+ viewAmountValue = InputViewValue(fiatToSend.stripZeroPlainString()),
+ viewBalanceValue = rescaledBalance.stripZeroPlainString(),
+ mainCurrency = state.createMainCurrency(action.mainCurrency),
maxLengthOfAmount = sendState.getDecimals(action.mainCurrency),
cursorAtTheSamePosition = false
)
}
MainCurrencyType.CRYPTO -> {
- val mainCurrency = Value(MainCurrencyType.CRYPTO, sendState.amount?.currencySymbol ?: "null")
state.copy(
- viewAmountValue = state.amountToSendCrypto.stripZeroPlainString(),
+ viewAmountValue = InputViewValue(state.amountToSendCrypto.stripZeroPlainString()),
viewBalanceValue = state.balanceCrypto.stripZeroPlainString(),
- mainCurrency = mainCurrency,
+ mainCurrency = state.createMainCurrency(action.mainCurrency),
maxLengthOfAmount = sendState.getDecimals(action.mainCurrency),
cursorAtTheSamePosition = false
)
}
}
}
- is SetMaxAmount -> {
- val maxAmount = if (sendState.feeState.feeIsIncluded) {
- state.balanceCrypto
- } else {
- val balanceExtractFee = state.balanceCrypto.minus(sendState.feeState.getCurrentFee())
- if (balanceExtractFee.isNegative()) BigDecimal.ZERO
- else balanceExtractFee
- }
-
- val etFieldValue = if (state.mainCurrency.value == MainCurrencyType.CRYPTO) maxAmount
- else converter.toFiat(maxAmount)
- state.copy(
- viewAmountValue = etFieldValue.stripZeroPlainString(),
- amountToSendCrypto = maxAmount,
- cursorAtTheSamePosition = false
- )
- }
- is CheckAmountToSend -> return sendState
+ else -> return sendState
}
return updateLastState(sendState.copy(amountState = result), result)
}
private fun handleAction(action: AmountAction, sendState: SendState, state: AmountState): SendState {
- val decimals = sendState.amount?.decimals ?: return sendState
-
val result = when (action) {
- is AmountAction.AmountVerification.SetAmount -> {
- setAmount(sendState.currencyConverter, decimals, action.amount, state)
+ is AmountAction.SetAmount -> {
+ when (state.mainCurrency.type) {
+ MainCurrencyType.FIAT -> {
+ val amountCrypto = sendState.convertToCrypto(action.amount)
+ state.copy(
+ viewAmountValue = InputViewValue(action.amount.scaleToFiat(false).stripZeroPlainString(), action.isUserInput),
+ amountToSendCrypto = amountCrypto,
+ cursorAtTheSamePosition = true,
+ error = null
+ )
+ }
+ MainCurrencyType.CRYPTO -> {
+ state.copy(
+ viewAmountValue = InputViewValue(action.amount.stripZeroPlainString(), action.isUserInput),
+ amountToSendCrypto = action.amount,
+ cursorAtTheSamePosition = true,
+ error = null
+ )
+ }
+ }
}
- is AmountAction.AmountVerification.SetError -> {
- setAmount(sendState.currencyConverter, decimals, action.amount, state).copy(error = action.error)
+ is AmountAction.SetAmountError -> {
+ state.copy(error = action.error)
}
}
-
return updateLastState(sendState.copy(amountState = result), result)
-
- }
-
- private fun setAmount(converter: CurrencyConverter, decimals: Int, amount: BigDecimal, state: AmountState): AmountState {
- return when (state.mainCurrency.value) {
- MainCurrencyType.FIAT -> {
- val amountCrypto = converter.toCrypto(amount, decimals).stripTrailingZeros()
- state.copy(
- viewAmountValue = amount.stripZeroPlainString(),
- amountToSendCrypto = amountCrypto,
- cursorAtTheSamePosition = true,
- error = null
- )
- }
- MainCurrencyType.CRYPTO -> {
- state.copy(
- viewAmountValue = amount.stripZeroPlainString(),
- amountToSendCrypto = amount,
- cursorAtTheSamePosition = true,
- error = null
- )
- }
- }
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/FeeReducer.kt b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/FeeReducer.kt
index 883ca9f708..f5da58ab54 100644
--- a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/FeeReducer.kt
+++ b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/FeeReducer.kt
@@ -2,14 +2,12 @@ package com.tangem.tap.features.send.redux.reducers
import com.tangem.blockchain.common.Amount
import com.tangem.blockchain.extensions.isAboveZero
-import com.tangem.tap.common.extensions.stripZeroPlainString
import com.tangem.tap.features.send.redux.FeeAction
import com.tangem.tap.features.send.redux.FeeActionUi
import com.tangem.tap.features.send.redux.SendScreenAction
import com.tangem.tap.features.send.redux.states.FeeState
import com.tangem.tap.features.send.redux.states.FeeType
import com.tangem.tap.features.send.redux.states.SendState
-import com.tangem.tap.features.send.redux.states.Value
/**
[REDACTED_AUTHOR]
@@ -86,9 +84,10 @@ class FeeReducer : SendInternalReducer {
return updateLastState(sendState.copy(feeState = result), result)
}
- private fun createValueOfFeeAmount(feeType: FeeType, list: List?): Value? {
+ private fun createValueOfFeeAmount(feeType: FeeType, list: List?): Amount? {
if (list == null || list.isEmpty()) return null
- val feeAmount = if (list.size == 1) {
+
+ return if (list.size == 1) {
if (!list[0].isAboveZero()) return null
list[0]
@@ -100,8 +99,6 @@ class FeeReducer : SendInternalReducer {
FeeType.PRIORITY -> list[2]
}
}
-
- return Value(feeAmount, feeAmount.value?.stripZeroPlainString() ?: "")
}
private fun getCurrentFeeType(state: FeeState): FeeType {
diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/ReceiptReducer.kt b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/ReceiptReducer.kt
index 91726cb199..0d92ab9a57 100644
--- a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/ReceiptReducer.kt
+++ b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/ReceiptReducer.kt
@@ -3,6 +3,7 @@ package com.tangem.tap.features.send.redux.reducers
import com.tangem.blockchain.common.AmountType
import com.tangem.blockchain.common.Wallet
import com.tangem.tap.common.CurrencyConverter
+import com.tangem.tap.common.extensions.scaleToFiat
import com.tangem.tap.common.extensions.stripZeroPlainString
import com.tangem.tap.features.send.redux.ReceiptAction.RefreshReceipt
import com.tangem.tap.features.send.redux.SendScreenAction
@@ -24,20 +25,21 @@ class ReceiptReducer : SendInternalReducer {
private fun handleRefresh(action: RefreshReceipt, sendState: SendState, state: ReceiptState): SendState {
val wallet = sendState.walletManager?.wallet ?: return sendState
- val converter = sendState.currencyConverter
+ val coinConverter = sendState.coinConverter
+ val tokenConverter = sendState.tokenConverter
val amountState = sendState.amountState
val feeState = sendState.feeState
- val layoutType = determineLayoutType(amountState.mainCurrency.value, amountState.typeOfAmount)
+ val layoutType = determineLayoutType(amountState.mainCurrency.type, sendState.amountState.typeOfAmount)
val symbols = determineSymbols(wallet)
val showBlank = !sendState.isReadyToSend()
val result = state.copy(
visibleTypeOfReceipt = layoutType,
- mainCurrencyType = sendState.amountState.mainCurrency,
- fiat = createFiatType(converter, amountState, feeState, symbols, showBlank),
- crypto = createCryptoType(converter, amountState, feeState, symbols, showBlank),
- tokenFiat = createTokenFiatType(converter, amountState, feeState, symbols, showBlank),
- tokenCrypto = createTokenCryptoType(converter, amountState, feeState, symbols, showBlank)
+ mainCurrency = sendState.amountState.mainCurrency,
+ fiat = createFiatType(coinConverter, amountState, feeState, symbols, showBlank),
+ crypto = createCryptoType(coinConverter, amountState, feeState, symbols, showBlank),
+ tokenFiat = createTokenFiatType(coinConverter, tokenConverter, amountState, feeState, symbols, showBlank),
+ tokenCrypto = createTokenCryptoType(coinConverter, tokenConverter, amountState, feeState, symbols, showBlank)
)
return updateLastState(sendState.copy(receiptState = result), result)
}
@@ -50,15 +52,15 @@ class ReceiptReducer : SendInternalReducer {
showBlank: Boolean
): ReceiptFiat {
val feeCrypto = feeState.getCurrentFee()
- val feeFiat = converter.toFiat(feeCrypto)
+ val feeFiat = converter.toFiatWithPrecision(feeCrypto)
if (showBlank) {
- return ReceiptFiat("0", feeFiat.stripZeroPlainString(), feeFiat.stripZeroPlainString(), "0", symbols)
+ return ReceiptFiat("0", feeFiat.stripZeroPlainString(), "0", "0", symbols)
}
return if (feeState.feeIsIncluded) {
- val amountFiat = converter.toFiat(amountState.amountToSendCrypto.minus(feeCrypto))
- val totalFiat = converter.toFiat(amountState.amountToSendCrypto)
+ val amountFiat = converter.toFiatWithPrecision(amountState.amountToSendCrypto.minus(feeCrypto))
+ val totalFiat = converter.toFiatWithPrecision(amountState.amountToSendCrypto)
ReceiptFiat(
amountFiat = amountFiat.stripZeroPlainString(),
feeFiat = feeFiat.stripZeroPlainString(),
@@ -68,8 +70,8 @@ class ReceiptReducer : SendInternalReducer {
)
} else {
val totalAmountCrypto = amountState.amountToSendCrypto.plus(feeCrypto)
- val amountFiat = converter.toFiat(amountState.amountToSendCrypto)
- val totalFiat = converter.toFiat(totalAmountCrypto)
+ val amountFiat = converter.toFiatWithPrecision(amountState.amountToSendCrypto)
+ val totalFiat = converter.toFiatWithPrecision(totalAmountCrypto)
ReceiptFiat(
amountFiat = amountFiat.stripZeroPlainString(),
feeFiat = feeFiat.stripZeroPlainString(),
@@ -88,9 +90,9 @@ class ReceiptReducer : SendInternalReducer {
showBlank: Boolean
): ReceiptCrypto {
val feeCrypto = feeState.getCurrentFee()
-
+ val feeFiat = converter.toFiatWithPrecision(feeCrypto)
if (showBlank) {
- return ReceiptCrypto("0", feeCrypto.stripZeroPlainString(), "0", converter.toFiat(feeCrypto).stripZeroPlainString(), "0", symbols)
+ return ReceiptCrypto("0", feeCrypto.stripZeroPlainString(), "0", "0", "0", symbols)
}
if (feeState.feeIsIncluded) {
@@ -98,8 +100,8 @@ class ReceiptReducer : SendInternalReducer {
amountCrypto = amountState.amountToSendCrypto.minus(feeCrypto).stripZeroPlainString(),
feeCrypto = feeCrypto.stripZeroPlainString(),
totalCrypto = amountState.amountToSendCrypto.stripZeroPlainString(),
- feeFiat = converter.toFiat(feeCrypto).stripZeroPlainString(),
- willSentFiat = converter.toFiat(amountState.amountToSendCrypto).stripZeroPlainString(),
+ feeFiat = feeFiat.stripZeroPlainString(),
+ willSentFiat = converter.toFiatWithPrecision(amountState.amountToSendCrypto).stripZeroPlainString(),
symbols = symbols
)
} else {
@@ -108,55 +110,63 @@ class ReceiptReducer : SendInternalReducer {
amountCrypto = amountState.amountToSendCrypto.stripZeroPlainString(),
feeCrypto = feeCrypto.stripZeroPlainString(),
totalCrypto = totalCrypto.stripZeroPlainString(),
- feeFiat = converter.toFiat(feeCrypto).stripZeroPlainString(),
- willSentFiat = converter.toFiat(totalCrypto).stripZeroPlainString(),
+ feeFiat = feeFiat.stripZeroPlainString(),
+ willSentFiat = converter.toFiatWithPrecision(totalCrypto).stripZeroPlainString(),
symbols = symbols
)
}
}
private fun createTokenFiatType(
- converter: CurrencyConverter,
+ coinConverter: CurrencyConverter,
+ tokenConverter: CurrencyConverter,
amountState: AmountState,
feeState: FeeState,
symbols: ReceiptSymbols,
showBlank: Boolean
): ReceiptTokenFiat {
- val feeCrypto = feeState.getCurrentFee()
- val amountFiat = converter.toFiat(amountState.amountToSendCrypto)
- val feeFiat = converter.toFiat(feeCrypto)
-
+ val feeCoin = feeState.getCurrentFee()
+ val feeFiat = coinConverter.toFiatWithPrecision(feeCoin)
if (showBlank) {
return ReceiptTokenFiat("0", feeFiat.stripZeroPlainString(), "0", "0", "0", symbols)
}
+ val tokensToSend = amountState.amountToSendCrypto
+ val amountFiat = tokenConverter.toFiatUnscaled(tokensToSend)
+ val totalFiat = amountFiat.plus(feeFiat)
+
return ReceiptTokenFiat(
- amountFiat = amountFiat.stripZeroPlainString(),
+ amountFiat = amountFiat.scaleToFiat().stripZeroPlainString(),
feeFiat = feeFiat.stripZeroPlainString(),
- totalFiat = amountFiat.plus(feeFiat).stripZeroPlainString(),
- willSentTokenCrypto = amountState.amountToSendCrypto.stripZeroPlainString(),
- willSentFeeCrypto = feeCrypto.stripZeroPlainString(),
+ totalFiat = totalFiat.scaleToFiat().stripZeroPlainString(),
+ willSentToken = tokensToSend.stripZeroPlainString(),
+ willSentFeeCoin = feeCoin.stripZeroPlainString(),
symbols = symbols
)
}
private fun createTokenCryptoType(
- converter: CurrencyConverter,
+ coinConverter: CurrencyConverter,
+ tokenConverter: CurrencyConverter,
amountState: AmountState,
feeState: FeeState,
symbols: ReceiptSymbols,
showBlank: Boolean
): ReceiptTokenCrypto {
- val feeCrypto = feeState.getCurrentFee()
- val totalFiat = converter.toFiat(amountState.amountToSendCrypto).plus(converter.toFiat(feeCrypto))
+ val feeCoin = feeState.getCurrentFee()
if (showBlank) {
- return ReceiptTokenCrypto("0", feeCrypto.stripZeroPlainString(), "0", symbols)
+ return ReceiptTokenCrypto("0", feeCoin.stripZeroPlainString(), "0", symbols)
}
+ val tokensToSend = amountState.amountToSendCrypto
+ val tokenFiat = tokenConverter.toFiatUnscaled(tokensToSend)
+ val feeFiat = coinConverter.toFiatUnscaled(feeCoin)
+ val totalFiat = tokenFiat.plus(feeFiat)
+
return ReceiptTokenCrypto(
- amountToken = amountState.amountToSendCrypto.stripZeroPlainString(),
- feeCrypto = feeCrypto.stripZeroPlainString(),
- totalFiat = totalFiat.stripZeroPlainString(),
+ amountToken = tokensToSend.stripZeroPlainString(),
+ feeCoin = feeCoin.stripZeroPlainString(),
+ totalFiat = totalFiat.scaleToFiat().stripZeroPlainString(),
symbols = symbols
)
}
diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/SendScreenReducer.kt b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/SendScreenReducer.kt
index 858b626b15..8a18db9d5e 100644
--- a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/SendScreenReducer.kt
+++ b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/SendScreenReducer.kt
@@ -1,6 +1,6 @@
package com.tangem.tap.features.send.redux.reducers
-import com.tangem.blockchain.common.WalletManager
+import com.tangem.blockchain.common.AmountType
import com.tangem.tap.common.CurrencyConverter
import com.tangem.tap.features.send.redux.*
import com.tangem.tap.features.send.redux.states.IdStateHolder
@@ -54,19 +54,34 @@ private class EmptyReducer : SendInternalReducer {
private class PrepareSendScreenStatesReducer : SendInternalReducer {
override fun handle(action: SendScreenAction, sendState: SendState): SendState {
- val amount = (action as PrepareSendScreen).tokenAmount ?: action.coinAmount
+ val prepareAction = action as PrepareSendScreen
val walletManager = store.state.globalState.scanNoteResponse!!.walletManager!!
+ val walletAmount = prepareAction.tokenAmount ?: prepareAction.coinAmount!!
+ val decimals = walletAmount.decimals
+
+ val coinConverter = createCurrencyConverter(walletManager.wallet.blockchain.currency, decimals)
+ val tokenConverter = createCurrencyConverter(prepareAction.tokenAmount?.currencySymbol ?: "", decimals)
+ if (coinConverter == null || (walletAmount.type == AmountType.Token && tokenConverter == null)) {
+ return sendState.copy(hasInitializationError = true)
+ }
return sendState.copy(
- amount = amount,
walletManager = walletManager,
- currencyConverter = createCurrencyConverter(walletManager),
- amountState = sendState.amountState.copy(balanceCrypto = amount?.value ?: BigDecimal.ZERO)
+ coinConverter = coinConverter,
+ tokenConverter = tokenConverter ?: CurrencyConverter(BigDecimal.ONE, decimals),
+ amountState = sendState.amountState.copy(
+ walletAmount = walletAmount,
+ typeOfAmount = walletAmount.type,
+ balanceCrypto = walletAmount.value ?: BigDecimal.ZERO
+ ),
+ feeState = sendState.feeState.copy(
+ includeFeeSwitcherIsEnabled = walletAmount.type == AmountType.Coin
+ )
)
}
- private fun createCurrencyConverter(walletManager: WalletManager): CurrencyConverter {
- val rate = store.state.globalState.conversionRates.getRate(walletManager.wallet.blockchain.currency)
- return if (rate == null) CurrencyConverter(BigDecimal.ONE) else CurrencyConverter(rate)
+ private fun createCurrencyConverter(currency: String, decimals: Int): CurrencyConverter? {
+ val rate = store.state.globalState.conversionRates.getRate(currency)
+ return if (rate == null) null else CurrencyConverter(rate, decimals)
}
}
diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/states/AddressPayIdState.kt b/app/src/main/java/com/tangem/tap/features/send/redux/states/AddressPayIdState.kt
index e7b081c183..dfcc6ca4c5 100644
--- a/app/src/main/java/com/tangem/tap/features/send/redux/states/AddressPayIdState.kt
+++ b/app/src/main/java/com/tangem/tap/features/send/redux/states/AddressPayIdState.kt
@@ -3,61 +3,20 @@ package com.tangem.tap.features.send.redux.states
import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction
data class AddressPayIdState(
- val etFieldValue: String? = null,
+ val viewFieldValue: InputViewValue = InputViewValue(""),
val normalFieldValue: String? = null,
val truncatedFieldValue: String? = null,
val recipientWalletAddress: String? = null,
val error: AddressPayIdVerifyAction.Error? = null,
val truncateHandler: ((String) -> String)? = null,
- val pasteIsEnabled: Boolean = false,
- override val stateId: StateId = StateId.ADDRESS_PAY_ID
+ val pasteIsEnabled: Boolean = false
) : SendScreenState {
+ override val stateId: StateId = StateId.ADDRESS_PAY_ID
+
+ fun truncate(value: String): String = truncateHandler?.invoke(value) ?: value
+
fun isReady(): Boolean = error == null && recipientWalletAddress?.isNotEmpty() ?: false
fun isPayIdState(): Boolean = recipientWalletAddress != null && recipientWalletAddress != normalFieldValue
-
- fun copyWalletAddress(address: String): AddressPayIdState {
- val truncated = truncateHandler?.invoke(address) ?: address
- return this.copy(
- etFieldValue = address,
- normalFieldValue = address,
- truncatedFieldValue = truncated,
- recipientWalletAddress = address,
- error = null
- )
- }
-
- fun copyError(address: String, error: AddressPayIdVerifyAction.Error): AddressPayIdState {
- val truncated = truncateHandler?.invoke(address) ?: address
- return this.copy(
- etFieldValue = address,
- normalFieldValue = address,
- truncatedFieldValue = truncated,
- error = error,
- recipientWalletAddress = null
- )
- }
-
- fun copyPayIdWalletAddress(payId: String, address: String): AddressPayIdState {
- val truncated = truncateHandler?.invoke(address) ?: address
- return this.copy(
- etFieldValue = payId,
- normalFieldValue = payId,
- truncatedFieldValue = truncated,
- recipientWalletAddress = address,
- error = null
- )
- }
-
- fun copyPayIdError(payId: String, error: AddressPayIdVerifyAction.Error): AddressPayIdState {
- val truncated = truncateHandler?.invoke(payId) ?: payId
- return this.copy(
- etFieldValue = payId,
- normalFieldValue = payId,
- truncatedFieldValue = truncated,
- error = error,
- recipientWalletAddress = null
- )
- }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/states/FeeState.kt b/app/src/main/java/com/tangem/tap/features/send/redux/states/FeeState.kt
index 2c95438ca3..72575e6a6b 100644
--- a/app/src/main/java/com/tangem/tap/features/send/redux/states/FeeState.kt
+++ b/app/src/main/java/com/tangem/tap/features/send/redux/states/FeeState.kt
@@ -14,15 +14,18 @@ enum class FeeType {
data class FeeState(
val selectedFeeType: FeeType = FeeType.NORMAL,
val feeList: List? = null,
- val currentFee: Value? = null,
+ val currentFee: Amount? = null,
val feeIsIncluded: Boolean = false,
val mainLayoutIsVisible: Boolean = false,
- val controlsLayoutIsVisible: Boolean = true,
+ val controlsLayoutIsVisible: Boolean = false,
val feeChipGroupIsVisible: Boolean = true,
+ val includeFeeSwitcherIsEnabled: Boolean = true,
val error: FeeAction.Error? = null,
- override val stateId: StateId = StateId.FEE
) : SendScreenState {
+
+ override val stateId: StateId = StateId.FEE
+
fun isReady(): Boolean = error == null && currentFee != null
- fun getCurrentFee(): BigDecimal = currentFee?.value?.value ?: BigDecimal.ZERO
+ fun getCurrentFee(): BigDecimal = currentFee?.value ?: BigDecimal.ZERO
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/states/ReceiptState.kt b/app/src/main/java/com/tangem/tap/features/send/redux/states/ReceiptState.kt
index 321cfe43b8..90e22a0e9e 100644
--- a/app/src/main/java/com/tangem/tap/features/send/redux/states/ReceiptState.kt
+++ b/app/src/main/java/com/tangem/tap/features/send/redux/states/ReceiptState.kt
@@ -11,9 +11,10 @@ data class ReceiptState(
val crypto: ReceiptCrypto? = null,
val tokenFiat: ReceiptTokenFiat? = null,
val tokenCrypto: ReceiptTokenCrypto? = null,
- val mainCurrencyType: Value? = null,
- override val stateId: StateId = StateId.RECEIPT
-) : SendScreenState
+ val mainCurrency: MainCurrency? = null
+) : SendScreenState {
+ override val stateId: StateId = StateId.RECEIPT
+}
data class ReceiptSymbols(
val fiat: String,
@@ -40,7 +41,7 @@ data class ReceiptCrypto(
data class ReceiptTokenCrypto(
val amountToken: String,
- val feeCrypto: String,
+ val feeCoin: String,
val totalFiat: String,
val symbols: ReceiptSymbols
)
@@ -49,7 +50,7 @@ data class ReceiptTokenFiat(
val amountFiat: String,
val feeFiat: String,
val totalFiat: String,
- val willSentTokenCrypto: String,
- val willSentFeeCrypto: String,
+ val willSentToken: String,
+ val willSentFeeCoin: String,
val symbols: ReceiptSymbols
)
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/states/SendState.kt b/app/src/main/java/com/tangem/tap/features/send/redux/states/SendState.kt
index 7d3df6c928..5742c5c60d 100644
--- a/app/src/main/java/com/tangem/tap/features/send/redux/states/SendState.kt
+++ b/app/src/main/java/com/tangem/tap/features/send/redux/states/SendState.kt
@@ -6,7 +6,7 @@ import com.tangem.blockchain.common.WalletManager
import com.tangem.common.extensions.isZero
import com.tangem.tap.common.CurrencyConverter
import com.tangem.tap.common.entities.TapCurrency
-import com.tangem.tap.features.send.redux.AmountAction
+import com.tangem.tap.domain.TapError
import com.tangem.tap.store
import org.rekotlin.StateType
import java.math.BigDecimal
@@ -26,18 +26,20 @@ enum class StateId {
interface SendScreenState : StateType, IdStateHolder
data class SendState(
- val amount: Amount? = null,
val walletManager: WalletManager? = null,
- val currencyConverter: CurrencyConverter = CurrencyConverter(BigDecimal.ONE),
+ val coinConverter: CurrencyConverter = CurrencyConverter(BigDecimal.ONE, 2),
+ val tokenConverter: CurrencyConverter = CurrencyConverter(BigDecimal.ONE, 2),
val lastChangedStates: LinkedHashSet = linkedSetOf(),
val addressPayIdState: AddressPayIdState = AddressPayIdState(),
val amountState: AmountState = AmountState(),
val feeState: FeeState = FeeState(),
val receiptState: ReceiptState = ReceiptState(),
val sendButtonState: SendButtonState = SendButtonState.DISABLED,
- override val stateId: StateId = StateId.SEND_SCREEN
+ val hasInitializationError: Boolean = false
) : SendScreenState {
+ override val stateId: StateId = StateId.SEND_SCREEN
+
fun isReadyToSend(): Boolean {
val sendState = store.state.sendState
return addressPayIdIsReady() && sendState.amountState.isReady() && sendState.feeState.isReady()
@@ -47,10 +49,29 @@ data class SendState(
fun getDecimals(type: MainCurrencyType): Int = when (type) {
MainCurrencyType.FIAT -> 2
- MainCurrencyType.CRYPTO -> amount?.decimals ?: 0
+ MainCurrencyType.CRYPTO -> amountState.walletAmount?.decimals ?: 0
+ }
+
+ fun getConverter(): CurrencyConverter {
+ return if (amountState.typeOfAmount == AmountType.Coin) coinConverter
+ else tokenConverter
+ }
+
+ fun convertToCrypto(value: BigDecimal): BigDecimal {
+ return getConverter().toCrypto(value)
+ }
+
+ fun convertToFiat(value: BigDecimal, scaleWithPrecision: Boolean = false): BigDecimal {
+ val converter = getConverter()
+ return if (!scaleWithPrecision) converter.toFiat(value) else converter.toFiatWithPrecision(value)
}
fun getButtonState(): SendButtonState = if (isReadyToSend()) SendButtonState.ENABLED else SendButtonState.DISABLED
+
+ fun getTotalAmountToSend(value: BigDecimal = amountState.amountToSendCrypto): BigDecimal {
+ val needToExtractFee = amountState.isCoinAmount() && feeState.feeIsIncluded
+ return if (needToExtractFee) value.minus(feeState.getCurrentFee()) else value
+ }
}
enum class SendButtonState {
@@ -58,25 +79,38 @@ enum class SendButtonState {
}
data class AmountState(
- val viewAmountValue: String = BigDecimal.ZERO.toPlainString(),
- val viewBalanceValue: String = BigDecimal.ZERO.toPlainString(),
- val mainCurrency: Value = Value(MainCurrencyType.FIAT, TapCurrency.DEFAULT_FIAT_CURRENCY),
+ val walletAmount: Amount? = null,
val typeOfAmount: AmountType = AmountType.Coin,
+ val viewAmountValue: InputViewValue = InputViewValue(BigDecimal.ZERO.toPlainString()),
+ val viewBalanceValue: String = BigDecimal.ZERO.toPlainString(),
+ val mainCurrency: MainCurrency = MainCurrency(MainCurrencyType.FIAT, TapCurrency.DEFAULT_FIAT_CURRENCY),
val amountToSendCrypto: BigDecimal = BigDecimal.ZERO,
val balanceCrypto: BigDecimal = BigDecimal.ZERO,
val cursorAtTheSamePosition: Boolean = true,
val maxLengthOfAmount: Int = 2,
- val error: AmountAction.Error? = null,
- override val stateId: StateId = StateId.AMOUNT
+ val error: TapError? = null
) : SendScreenState {
+
+ override val stateId: StateId = StateId.AMOUNT
+
fun isReady(): Boolean = error == null && !amountToSendCrypto.isZero()
+
+ fun isCoinAmount(): Boolean = typeOfAmount == AmountType.Coin
+
+ fun createMainCurrency(type: MainCurrencyType): MainCurrency {
+ return when (type) {
+ MainCurrencyType.FIAT -> MainCurrency(type, store.state.globalState.appCurrency)
+ MainCurrencyType.CRYPTO -> MainCurrency(type, walletAmount?.currencySymbol ?: "NONE")
+ }
+ }
}
+data class InputViewValue(val value: String, val isFromUserInput: Boolean = false)
enum class MainCurrencyType {
FIAT, CRYPTO
}
-data class Value(
- val value: T,
- val displayedValue: String
+data class MainCurrency(
+ val type: MainCurrencyType,
+ val currencySymbol: String
)
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt b/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt
index 8201b03f38..de7856d3e6 100644
--- a/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt
+++ b/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt
@@ -16,6 +16,7 @@ import com.tangem.tap.common.extensions.getDrawableCompat
import com.tangem.tap.common.extensions.getFromClipboard
import com.tangem.tap.common.extensions.setOnImeActionListener
import com.tangem.tap.common.qrCodeScan.ScanQrCodeActivity
+import com.tangem.tap.common.redux.navigation.NavigationAction
import com.tangem.tap.common.snackBar.MaxAmountSnackbar
import com.tangem.tap.common.text.truncateMiddleWith
import com.tangem.tap.common.toggleWidget.*
@@ -57,6 +58,11 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
+ if (store.state.sendState.hasInitializationError) {
+ store.dispatch(NavigationAction.PopBackTo())
+ return
+ }
+
initSendButtonStates()
setupAddressOrPayIdLayout()
setupAmountLayout()
@@ -69,22 +75,22 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
private fun setupAddressOrPayIdLayout() {
store.dispatch(SetTruncateHandler { etAddressOrPayId.truncateMiddleWith(it, "...") })
- store.dispatch(AddressPayIdVerifyAction.VerifyClipboard(requireContext().getFromClipboard()?.toString()))
+ store.dispatch(CheckClipboard(requireContext().getFromClipboard()?.toString()))
etAddressOrPayId.setOnFocusChangeListener { v, hasFocus ->
store.dispatch(TruncateOrRestore(!hasFocus))
}
etAddressOrPayId.inputtedTextAsFlow()
.debounce(400)
- .filter { store.state.sendState.addressPayIdState.etFieldValue != it }
+ .filter { store.state.sendState.addressPayIdState.viewFieldValue.value != it }
.onEach {
- store.dispatch(ChangeAddressOrPayId(it))
+ store.dispatch(AddressPayIdActionUi.HandleUserInput(it))
store.dispatch(FeeAction.RequestFee)
}
.launchIn(mainScope)
imvPaste.setOnClickListener {
- store.dispatch(ChangeAddressOrPayId(requireContext().getFromClipboard()?.toString() ?: ""))
+ store.dispatch(PasteAddressPayId(requireContext().getFromClipboard()?.toString() ?: ""))
store.dispatch(TruncateOrRestore(!etAddressOrPayId.isFocused))
store.dispatch(FeeAction.RequestFee)
}
@@ -102,7 +108,7 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
val scannedCode = data?.getStringExtra(ScanQrCodeActivity.SCAN_RESULT) ?: ""
if (scannedCode.isEmpty()) return
- store.dispatch(ChangeAddressOrPayId(scannedCode))
+ store.dispatch(PasteAddressPayId(scannedCode))
store.dispatch(TruncateOrRestore(!etAddressOrPayId.isFocused))
store.dispatch(FeeAction.RequestFee)
}
@@ -122,7 +128,7 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
etAmountToSend.clearFocus()
etAmountToSend.postDelayed(200) { etAmountToSend.hideSoftKeyboard() }
store.dispatch(SetMaxAmount)
- store.dispatch(CheckAmountToSend())
+ store.dispatch(CheckAmountToSend)
}
var snackbarControlledByChangingFocus = false
keyboardObserver = KeyboardObserver(requireActivity())
@@ -154,14 +160,14 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
val prevFocusChangeListener = etAmountToSend.onFocusChangeListener
etAmountToSend.setOnFocusChangeListener { v, hasFocus ->
prevFocusChangeListener.onFocusChange(v, hasFocus)
-// if (hasFocus && etAmountToSend.text?.toString() == "0") etAmountToSend.setText("")
+ if (hasFocus && etAmountToSend.text?.toString() == "0") etAmountToSend.setText("")
if (!hasFocus && etAmountToSend.text?.toString() == "") etAmountToSend.setText("0")
}
etAmountToSend.inputtedTextAsFlow()
.debounce(400)
- .filter { store.state.sendState.amountState.viewAmountValue != it && it.isNotEmpty() }
- .onEach { store.dispatch(CheckAmountToSend(it)) }
+ .filter { store.state.sendState.amountState.viewAmountValue.value != it && it.isNotEmpty() }
+ .onEach { store.dispatch(AmountActionUi.HandleUserInput(it)) }
.launchIn(mainScope)
etAmountToSend.setOnImeActionListener(EditorInfo.IME_ACTION_DONE) {
@@ -178,15 +184,17 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
if (checkedId == -1) return@setOnCheckedChangeListener
store.dispatch(ChangeSelectedFee(FeeUiHelper.idToFee(checkedId)))
- store.dispatch(CheckAmountToSend())
+ store.dispatch(CheckAmountToSend)
}
swIncludeFee.setOnCheckedChangeListener { btn, isChecked ->
store.dispatch(ChangeIncludeFee(isChecked))
- store.dispatch(CheckAmountToSend())
+ store.dispatch(CheckAmountToSend)
}
}
override fun subscribeToStore() {
+ if (store.state.sendState.hasInitializationError) return
+
store.subscribe(sendSubscriber) { appState ->
appState.skipRepeats { oldState, newState ->
oldState.sendState == newState.sendState
diff --git a/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/SendStateSubscriber.kt b/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/SendStateSubscriber.kt
index c4ee8d767a..278b8d62b4 100644
--- a/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/SendStateSubscriber.kt
+++ b/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/SendStateSubscriber.kt
@@ -10,9 +10,10 @@ import com.tangem.tap.common.extensions.show
import com.tangem.tap.common.extensions.update
import com.tangem.tap.common.text.DecimalDigitsInputFilter
import com.tangem.tap.common.toggleWidget.ProgressState
+import com.tangem.tap.domain.MultiMessageError
+import com.tangem.tap.domain.assembleErrorIds
import com.tangem.tap.features.send.BaseStoreFragment
import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction.Error
-import com.tangem.tap.features.send.redux.AmountAction
import com.tangem.tap.features.send.redux.FeeAction
import com.tangem.tap.features.send.redux.states.*
import com.tangem.tap.features.send.ui.FeeUiHelper
@@ -66,7 +67,6 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber
private fun handleAddressPayIdState(fg: BaseStoreFragment, state: AddressPayIdState) {
fun parseError(context: Context, error: Error?): String? {
val resId = when (error) {
- Error.IS_NOT_PAY_ID -> R.string.error_payid_verification_failed
Error.PAY_ID_UNSUPPORTED_BY_BLOCKCHAIN -> R.string.error_payid_unsupported_by_blockchain
Error.PAY_ID_NOT_REGISTERED -> R.string.error_payid_not_registere
Error.PAY_ID_REQUEST_FAILED -> R.string.error_payid_request_failed
@@ -89,31 +89,30 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber
til.helperText = state.recipientWalletAddress
til.isHelperTextEnabled = state.isPayIdState() && parsedError == null
- // prevent cycling
- if (state.etFieldValue == null) return
-
- et.update(state.etFieldValue)
+ if (!state.viewFieldValue.isFromUserInput) et.update(state.viewFieldValue.value)
}
private fun handleAmountState(fg: BaseStoreFragment, state: AmountState) {
- when (state.error) {
- AmountAction.Error.FEE_GREATER_THAN_AMOUNT -> {
- fg.amountContainer.parent?.beginDelayedTransition()
- fg.tilAmountToSend.enableError(true, fg.getString(R.string.error_fee_greater_than_amount))
- }
- AmountAction.Error.AMOUNT_WITH_FEE_GREATER_THAN_BALANCE -> {
- fg.amountContainer.parent?.beginDelayedTransition()
- fg.tilAmountToSend.enableError(true, fg.getString(R.string.error_amount_with_fee_greater_than_balance))
- }
- null -> {
- if (fg.tilAmountToSend.isErrorEnabled) fg.amountContainer.parent?.beginDelayedTransition()
- fg.tilAmountToSend.enableError(false)
+ if (state.error != null) {
+ val context = fg.requireContext()
+ val message = when (state.error) {
+ is MultiMessageError -> {
+ val multiError = state.error as MultiMessageError
+ val messageList = multiError.assembleErrorIds().map { context.getString(it) }
+ multiError.builder(messageList)
+ }
+ else -> context.getString(state.error.localizedMessage)
}
+ fg.amountContainer.parent?.beginDelayedTransition()
+ fg.tilAmountToSend.enableError(true, message)
+ } else {
+ if (fg.tilAmountToSend.isErrorEnabled) fg.amountContainer.parent?.beginDelayedTransition()
+ fg.tilAmountToSend.enableError(false)
}
fg.etAmountToSend.filters = arrayOf(DecimalDigitsInputFilter(12, state.maxLengthOfAmount))
val amountToSend = state.viewAmountValue
- fg.etAmountToSend.update(amountToSend)
+ if (!amountToSend.isFromUserInput) fg.etAmountToSend.update(amountToSend.value)
// fg.tvAmountToSendShadow.text = amountToSend
// if (amountToSend.length > 10) {
@@ -131,11 +130,11 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber
// if (!state.cursorAtTheSamePosition) fg.etAmountToSend.setSelection(amountToSend.length)
// }
- fg.tvAmountCurrency.update(state.mainCurrency.displayedValue)
- (fg as? SendFragment)?.saveMainCurrency(state.mainCurrency.value)
+ fg.tvAmountCurrency.update(state.mainCurrency.currencySymbol)
+ (fg as? SendFragment)?.saveMainCurrency(state.mainCurrency.type)
val balanceText = fg.getString(R.string.send_balance,
- state.mainCurrency.displayedValue,
+ state.mainCurrency.currencySymbol,
state.viewBalanceValue)
fg.tvBalance.update(balanceText)
}
@@ -162,6 +161,7 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber
}
}
+ fg.swIncludeFee.isEnabled = state.includeFeeSwitcherIsEnabled
if (fg.swIncludeFee.isChecked != state.feeIsIncluded) {
fg.swIncludeFee.isChecked = state.feeIsIncluded
}
@@ -182,6 +182,7 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber
val totalTokenLayout = fg.flTotalTokenCrypto as ViewGroup
fun getString(id: Int): String = mainLayout.context.getString(id)
+ val rough = getString(R.string.sign_rough)
when (state.visibleTypeOfReceipt) {
ReceiptLayoutType.FIAT -> {
val receipt = state.fiat ?: return
@@ -190,7 +191,7 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber
totalTokenLayout.show(false)
fg.tvReceiptAmountValue.update("${receipt.amountFiat} ${receipt.symbols.fiat}")
fg.tvReceiptFeeValue.update("${receipt.feeFiat} ${receipt.symbols.fiat}")
- totalLayout.tvTotalValue.update("${receipt.totalFiat} ${receipt.symbols.fiat}")
+ totalLayout.tvTotalValue.update("$rough ${receipt.totalFiat} ${receipt.symbols.fiat}")
val willSent = SpannableStringBuilder()
.bold { append(receipt.willSentCrypto) }.append(" ")
@@ -210,7 +211,7 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber
val willSent = SpannableStringBuilder()
.bold {
- append(getString(R.string.sign_rough)).append(" ")
+ append(rough).append(" ")
append(receipt.willSentFiat).append(" ")
append(receipt.symbols.fiat)
append(" (fee: ${receipt.feeFiat} ")
@@ -231,11 +232,11 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber
.bold {
append(receipt.symbols.token)
append(" ")
- append(receipt.willSentFeeCrypto)
+ append(receipt.willSentToken)
}.append(" ").append(getString(R.string.generic_and)).append(" ")
.bold {
append(receipt.symbols.crypto).append(" ")
- append(receipt.willSentFeeCrypto).append(" ")
+ append(receipt.willSentFeeCoin).append(" ")
}
.append(mainLayout.context.getString(R.string.send_total_will_be_sent))
totalLayout.tvWillBeSentValue.update(willSent.toString())
@@ -247,7 +248,7 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber
totalTokenLayout.show(true)
fg.tvReceiptAmountValue.update("${receipt.amountToken} ${receipt.symbols.token}")
- fg.tvReceiptFeeValue.update("${receipt.feeCrypto} ${receipt.symbols.crypto}")
+ fg.tvReceiptFeeValue.update("${receipt.feeCoin} ${receipt.symbols.crypto}")
val willSent = SpannableStringBuilder()
.bold {
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletReducer.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletReducer.kt
index 233a6c5500..c348a46df0 100644
--- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletReducer.kt
+++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletReducer.kt
@@ -104,7 +104,7 @@ private fun internalReduce(action: Action, state: AppState): WalletState {
val fiatRate = state.globalState.conversionRates.getRate(action.wallet.blockchain.currency)
val fiatAmount = fiatRate?.let { amount?.toFiatString(it, fiatCurrencySymbol) }
- val pendingTransactions = action.wallet.transactions
+ val pendingTransactions = action.wallet.recentTransactions
.toPendingTransactions(action.wallet.address)
val sendButtonEnabled = amount?.isZero() == false || token?.value?.isZero() == false
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 9373ea5d54..22a73a85d7 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -57,11 +57,15 @@
Address is invalid or unsupported by blockchain
Address is the same as wallet address
Network fee request is failed
- Fee greater than amount
- Amount with fee greater than balance
Insufficient balance
Blockchain internal error
-
+ amount_exceeds_balance
+ fee_exceeds_balance
+ total_exceeds_balance
+ invalid_amount_value
+ invalid_fee_value
+ dust_amount
+ dust_change
Send
Address or PayID