Updated on 2026-08-14

This commit is contained in:
Tangem 2020-09-21 19:04:30 +03:00
parent b2d8f0bb76
commit b63e52d5b7
6 changed files with 146 additions and 118 deletions

View file

@ -1,6 +1,7 @@
package com.tangem.tap.features.send.redux.middlewares
import com.tangem.blockchain.common.Amount
import com.tangem.blockchain.common.AmountType
import com.tangem.blockchain.common.TransactionError
import com.tangem.common.extensions.isZero
import com.tangem.tap.common.redux.AppState
@ -40,7 +41,11 @@ class AmountMiddleware {
if (inputValue.isZero() && amountState.amountToSendCrypto.isZero()) return
val inputValueCrypto = if (amountState.mainCurrency.type == MainCurrencyType.CRYPTO) inputValue
else sendState.convertToCrypto(inputValue)
else when (amountState.typeOfAmount) {
AmountType.Coin -> sendState.convertFiatToCoin(inputValue)
AmountType.Token -> sendState.convertFiatToToken(inputValue)
else -> inputValue
}
dispatch(AmountAction.SetAmount(inputValueCrypto, true))
dispatch(AmountActionUi.CheckAmountToSend)
@ -93,6 +98,7 @@ class AmountMiddleware {
private fun toggleMainCurrency(appState: AppState?, dispatch: (Action) -> Unit) {
val amountState = appState?.sendState?.amountState ?: return
if (!appState.sendState.coinIsConvertible()) return
val type = if (amountState.mainCurrency.type == MainCurrencyType.FIAT) MainCurrencyType.CRYPTO
else MainCurrencyType.FIAT

View file

@ -25,16 +25,18 @@ class AmountReducer : SendInternalReducer {
private fun handleUiAction(action: AmountActionUi, sendState: SendState, state: AmountState): SendState {
val result = when (action) {
is SetMainCurrency -> {
when (action.mainCurrency) {
val currencyCanBeSwitched = sendState.mainCurrencyCanBeSwitched()
when (val currency = if (currencyCanBeSwitched) action.mainCurrency else MainCurrencyType.CRYPTO) {
MainCurrencyType.FIAT -> {
val fiatToSend = if (state.amountToSendCrypto.isZero()) BigDecimal.ZERO
else sendState.convertToFiat(state.amountToSendCrypto)
val rescaledBalance = sendState.convertToFiat(state.balanceCrypto, true)
else sendState.convertCoinToFiat(state.amountToSendCrypto)
val rescaledBalance = sendState.convertCoinToFiat(state.balanceCrypto, true)
state.copy(
viewAmountValue = InputViewValue(fiatToSend.stripZeroPlainString()),
viewBalanceValue = rescaledBalance.stripZeroPlainString(),
mainCurrency = state.createMainCurrency(action.mainCurrency),
maxLengthOfAmount = sendState.getDecimals(action.mainCurrency),
mainCurrency = state.createMainCurrency(currency, currencyCanBeSwitched),
maxLengthOfAmount = sendState.getDecimals(currency),
cursorAtTheSamePosition = false
)
}
@ -42,8 +44,8 @@ class AmountReducer : SendInternalReducer {
state.copy(
viewAmountValue = InputViewValue(state.amountToSendCrypto.stripZeroPlainString()),
viewBalanceValue = state.balanceCrypto.stripZeroPlainString(),
mainCurrency = state.createMainCurrency(action.mainCurrency),
maxLengthOfAmount = sendState.getDecimals(action.mainCurrency),
mainCurrency = state.createMainCurrency(currency, currencyCanBeSwitched),
maxLengthOfAmount = sendState.getDecimals(currency),
cursorAtTheSamePosition = false
)
}
@ -59,7 +61,7 @@ class AmountReducer : SendInternalReducer {
val result = when (action) {
is AmountAction.SetAmount -> {
val amount = if (state.mainCurrency.type == MainCurrencyType.CRYPTO) action.amountCrypto
else sendState.convertToFiat(action.amountCrypto, true)
else sendState.convertCoinToFiat(action.amountCrypto, true)
state.copy(
viewAmountValue = InputViewValue(amount.stripZeroPlainString(), action.isUserInput),
amountToSendCrypto = action.amountCrypto,

View file

@ -2,95 +2,88 @@ 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
import com.tangem.tap.features.send.redux.states.*
import com.tangem.tap.store
import java.math.BigDecimal
/**
[REDACTED_AUTHOR]
*/
class ReceiptReducer : SendInternalReducer {
private lateinit var sendState: SendState
private lateinit var amountState: AmountState
private lateinit var feeState: FeeState
private val EMPTY = "-"
override fun handle(action: SendScreenAction, sendState: SendState): SendState {
this.sendState = sendState
this.amountState = sendState.amountState
this.feeState = sendState.feeState
return when (action) {
is RefreshReceipt -> handleRefresh(action, sendState, sendState.receiptState)
is RefreshReceipt -> handleRefresh(action, sendState.receiptState)
else -> sendState
}
}
private fun handleRefresh(action: RefreshReceipt, sendState: SendState, state: ReceiptState): SendState {
private fun handleRefresh(action: RefreshReceipt, state: ReceiptState): SendState {
val wallet = sendState.walletManager?.wallet ?: return sendState
val coinConverter = sendState.coinConverter
val tokenConverter = sendState.tokenConverter
val amountState = sendState.amountState
val feeState = sendState.feeState
val layoutType = determineLayoutType(amountState.mainCurrency.type, sendState.amountState.typeOfAmount)
val layoutType = determineLayoutType(amountState.mainCurrency.type, amountState.typeOfAmount)
val symbols = determineSymbols(wallet)
val showBlank = !sendState.isReadyToSend()
val result = state.copy(
visibleTypeOfReceipt = layoutType,
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)
mainCurrency = amountState.mainCurrency,
fiat = createFiatType(symbols, showBlank),
crypto = createCryptoType(symbols, showBlank),
tokenFiat = createTokenFiatType(symbols, showBlank),
tokenCrypto = createTokenCryptoType(symbols, showBlank)
)
return updateLastState(sendState.copy(receiptState = result), result)
}
private fun createFiatType(
converter: CurrencyConverter,
amountState: AmountState,
feeState: FeeState,
symbols: ReceiptSymbols,
showBlank: Boolean
): ReceiptFiat {
private fun createFiatType(symbols: ReceiptSymbols, showBlank: Boolean): ReceiptFiat {
val feeCrypto = feeState.getCurrentFee()
val feeFiat = converter.toFiatWithPrecision(feeCrypto)
val feeFiat = convertToFiatPrecision(feeCrypto)
if (showBlank) {
return ReceiptFiat("0", feeFiat.stripZeroPlainString(), "0", "0", symbols)
return ReceiptFiat("0", feeFiat, "0", "0", symbols)
}
return if (feeState.feeIsIncluded) {
val amountFiat = converter.toFiatWithPrecision(amountState.amountToSendCrypto.minus(feeCrypto))
val totalFiat = converter.toFiatWithPrecision(amountState.amountToSendCrypto)
val amountFiat = convertToFiatPrecision(amountState.amountToSendCrypto.minus(feeCrypto))
val totalFiat = convertToFiatPrecision(amountState.amountToSendCrypto)
ReceiptFiat(
amountFiat = amountFiat.stripZeroPlainString(),
feeFiat = feeFiat.stripZeroPlainString(),
totalFiat = totalFiat.stripZeroPlainString(),
amountFiat = amountFiat,
feeFiat = feeFiat,
totalFiat = totalFiat,
willSentCrypto = amountState.amountToSendCrypto.stripZeroPlainString(),
symbols = symbols
)
} else {
val totalAmountCrypto = amountState.amountToSendCrypto.plus(feeCrypto)
val amountFiat = converter.toFiatWithPrecision(amountState.amountToSendCrypto)
val totalFiat = converter.toFiatWithPrecision(totalAmountCrypto)
val amountFiat = convertToFiatPrecision(amountState.amountToSendCrypto)
val totalFiat = convertToFiatPrecision(totalAmountCrypto)
ReceiptFiat(
amountFiat = amountFiat.stripZeroPlainString(),
feeFiat = feeFiat.stripZeroPlainString(),
totalFiat = totalFiat.stripZeroPlainString(),
amountFiat = amountFiat,
feeFiat = feeFiat,
totalFiat = totalFiat,
willSentCrypto = totalAmountCrypto.stripZeroPlainString(),
symbols = symbols
)
}
}
private fun createCryptoType(
converter: CurrencyConverter,
amountState: AmountState,
feeState: FeeState,
symbols: ReceiptSymbols,
showBlank: Boolean
): ReceiptCrypto {
private fun createCryptoType(symbols: ReceiptSymbols, showBlank: Boolean): ReceiptCrypto {
val feeCrypto = feeState.getCurrentFee()
val feeFiat = converter.toFiatWithPrecision(feeCrypto)
val feeFiat = convertToFiatPrecision(feeCrypto)
if (showBlank) {
return ReceiptCrypto("0", feeCrypto.stripZeroPlainString(), "0", "0", "0", symbols)
}
@ -100,8 +93,8 @@ class ReceiptReducer : SendInternalReducer {
amountCrypto = amountState.amountToSendCrypto.minus(feeCrypto).stripZeroPlainString(),
feeCrypto = feeCrypto.stripZeroPlainString(),
totalCrypto = amountState.amountToSendCrypto.stripZeroPlainString(),
feeFiat = feeFiat.stripZeroPlainString(),
willSentFiat = converter.toFiatWithPrecision(amountState.amountToSendCrypto).stripZeroPlainString(),
feeFiat = feeFiat,
willSentFiat = convertToFiatPrecision(amountState.amountToSendCrypto),
symbols = symbols
)
} else {
@ -110,65 +103,73 @@ class ReceiptReducer : SendInternalReducer {
amountCrypto = amountState.amountToSendCrypto.stripZeroPlainString(),
feeCrypto = feeCrypto.stripZeroPlainString(),
totalCrypto = totalCrypto.stripZeroPlainString(),
feeFiat = feeFiat.stripZeroPlainString(),
willSentFiat = converter.toFiatWithPrecision(totalCrypto).stripZeroPlainString(),
feeFiat = feeFiat,
willSentFiat = convertToFiatPrecision(totalCrypto),
symbols = symbols
)
}
}
private fun createTokenFiatType(
coinConverter: CurrencyConverter,
tokenConverter: CurrencyConverter,
amountState: AmountState,
feeState: FeeState,
symbols: ReceiptSymbols,
showBlank: Boolean
): ReceiptTokenFiat {
private fun createTokenFiatType(symbols: ReceiptSymbols, showBlank: Boolean): ReceiptTokenFiat {
val feeCoin = feeState.getCurrentFee()
val feeFiat = coinConverter.toFiatWithPrecision(feeCoin)
if (showBlank) {
return ReceiptTokenFiat("0", feeFiat.stripZeroPlainString(), "0", "0", "0", symbols)
val feeFiat = convertToFiatPrecision(feeCoin)
return ReceiptTokenFiat("0", feeFiat, "0", "0", "0", symbols)
}
val tokensToSend = amountState.amountToSendCrypto
val amountFiat = tokenConverter.toFiatUnscaled(tokensToSend)
val totalFiat = amountFiat.plus(feeFiat)
return ReceiptTokenFiat(
amountFiat = amountFiat.scaleToFiat().stripZeroPlainString(),
feeFiat = feeFiat.stripZeroPlainString(),
totalFiat = totalFiat.scaleToFiat().stripZeroPlainString(),
willSentToken = tokensToSend.stripZeroPlainString(),
willSentFeeCoin = feeCoin.stripZeroPlainString(),
symbols = symbols
)
return if (sendState.coinIsConvertible() && sendState.tokenIsConvertible()) {
val feeFiat = sendState.coinConverter!!.toFiatUnscaled(feeCoin)
val amountFiat = sendState.tokenConverter!!.toFiatUnscaled(tokensToSend)
val totalFiat = amountFiat.plus(feeFiat)
ReceiptTokenFiat(
amountFiat = amountFiat.scaleToFiat().stripZeroPlainString(),
feeFiat = feeFiat.stripZeroPlainString(),
totalFiat = totalFiat.scaleToFiat().stripZeroPlainString(),
willSentToken = tokensToSend.stripZeroPlainString(),
willSentFeeCoin = feeCoin.stripZeroPlainString(),
symbols = symbols
)
} else {
ReceiptTokenFiat(
amountFiat = EMPTY,
feeFiat = EMPTY,
totalFiat = EMPTY,
willSentToken = tokensToSend.stripZeroPlainString(),
willSentFeeCoin = feeCoin.stripZeroPlainString(),
symbols = symbols
)
}
}
private fun createTokenCryptoType(
coinConverter: CurrencyConverter,
tokenConverter: CurrencyConverter,
amountState: AmountState,
feeState: FeeState,
symbols: ReceiptSymbols,
showBlank: Boolean
): ReceiptTokenCrypto {
private fun createTokenCryptoType(symbols: ReceiptSymbols, showBlank: Boolean): ReceiptTokenCrypto {
val feeCoin = feeState.getCurrentFee()
if (showBlank) {
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 = tokensToSend.stripZeroPlainString(),
feeCoin = feeCoin.stripZeroPlainString(),
totalFiat = totalFiat.scaleToFiat().stripZeroPlainString(),
symbols = symbols
)
return if (sendState.coinIsConvertible() && sendState.tokenIsConvertible()) {
val tokenFiat = sendState.tokenConverter!!.toFiatUnscaled(tokensToSend)
val feeFiat = sendState.coinConverter!!.toFiatUnscaled(feeCoin)
val totalFiat = tokenFiat.plus(feeFiat)
ReceiptTokenCrypto(
amountToken = tokensToSend.stripZeroPlainString(),
feeCoin = feeCoin.stripZeroPlainString(),
totalFiat = totalFiat.scaleToFiat().stripZeroPlainString(),
symbols = symbols
)
} else {
ReceiptTokenCrypto(
amountToken = tokensToSend.stripZeroPlainString(),
feeCoin = feeCoin.stripZeroPlainString(),
totalFiat = EMPTY,
symbols = symbols
)
}
}
private fun determineSymbols(wallet: Wallet): ReceiptSymbols {
@ -193,4 +194,12 @@ class ReceiptReducer : SendInternalReducer {
}
}
}
private fun convertToFiatPrecision(value: BigDecimal, isToken: Boolean = false): String {
return when {
!isToken && sendState.coinIsConvertible() -> sendState.coinConverter!!.toFiatWithPrecision(value).stripZeroPlainString()
isToken && sendState.tokenIsConvertible() -> sendState.tokenConverter!!.toFiatWithPrecision(value).stripZeroPlainString()
else -> EMPTY
}
}
}

View file

@ -61,13 +61,10 @@ private class PrepareSendScreenStatesReducer : SendInternalReducer {
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(
walletManager = walletManager,
coinConverter = coinConverter,
tokenConverter = tokenConverter ?: CurrencyConverter(BigDecimal.ONE, decimals),
tokenConverter = tokenConverter,
amountState = sendState.amountState.copy(
walletAmount = walletAmount,
typeOfAmount = walletAmount.type,

View file

@ -27,15 +27,14 @@ interface SendScreenState : StateType, IdStateHolder
data class SendState(
val walletManager: WalletManager? = null,
val coinConverter: CurrencyConverter = CurrencyConverter(BigDecimal.ONE, 2),
val tokenConverter: CurrencyConverter = CurrencyConverter(BigDecimal.ONE, 2),
val coinConverter: CurrencyConverter? = null,
val tokenConverter: CurrencyConverter? = null,
val lastChangedStates: LinkedHashSet<StateId> = linkedSetOf(),
val addressPayIdState: AddressPayIdState = AddressPayIdState(),
val amountState: AmountState = AmountState(),
val feeState: FeeState = FeeState(),
val receiptState: ReceiptState = ReceiptState(),
val sendButtonState: SendButtonState = SendButtonState.DISABLED,
val hasInitializationError: Boolean = false
val sendButtonState: SendButtonState = SendButtonState.DISABLED
) : SendScreenState {
override val stateId: StateId = StateId.SEND_SCREEN
@ -52,17 +51,27 @@ data class SendState(
MainCurrencyType.CRYPTO -> amountState.walletAmount?.decimals ?: 0
}
fun getConverter(): CurrencyConverter {
return if (amountState.typeOfAmount == AmountType.Coin) coinConverter
else tokenConverter
fun convertFiatToCoin(value: BigDecimal): BigDecimal {
return if (!this.coinIsConvertible()) value
else coinConverter!!.toCrypto(value)
}
fun convertToCrypto(value: BigDecimal): BigDecimal {
return getConverter().toCrypto(value)
fun convertCoinToFiat(value: BigDecimal, scaleWithPrecision: Boolean = false): BigDecimal {
if (!this.coinIsConvertible()) return value
val converter = coinConverter!!
return if (!scaleWithPrecision) converter.toFiat(value) else converter.toFiatWithPrecision(value)
}
fun convertToFiat(value: BigDecimal, scaleWithPrecision: Boolean = false): BigDecimal {
val converter = getConverter()
fun convertFiatToToken(value: BigDecimal): BigDecimal {
return if (!this.tokenIsConvertible()) value
else tokenConverter!!.toCrypto(value)
}
fun convertTokenToFiat(value: BigDecimal, scaleWithPrecision: Boolean = false): BigDecimal {
if (!this.tokenIsConvertible()) return value
val converter = tokenConverter!!
return if (!scaleWithPrecision) converter.toFiat(value) else converter.toFiatWithPrecision(value)
}
@ -72,6 +81,17 @@ data class SendState(
val needToExtractFee = amountState.isCoinAmount() && feeState.feeIsIncluded
return if (needToExtractFee) value.minus(feeState.getCurrentFee()) else value
}
fun coinIsConvertible(): Boolean = coinConverter != null
fun tokenIsConvertible(): Boolean = tokenConverter != null
fun mainCurrencyCanBeSwitched(): Boolean {
return when (amountState.typeOfAmount) {
AmountType.Coin -> coinIsConvertible()
AmountType.Token -> tokenIsConvertible()
AmountType.Reserve -> false
}
}
}
enum class SendButtonState {
@ -97,8 +117,9 @@ data class AmountState(
fun isCoinAmount(): Boolean = typeOfAmount == AmountType.Coin
fun createMainCurrency(type: MainCurrencyType): MainCurrency {
return when (type) {
fun createMainCurrency(type: MainCurrencyType, canSwitched: Boolean): MainCurrency {
return if (!canSwitched) MainCurrency(type, walletAmount?.currencySymbol ?: "NONE", false)
else when (type) {
MainCurrencyType.FIAT -> MainCurrency(type, store.state.globalState.appCurrency)
MainCurrencyType.CRYPTO -> MainCurrency(type, walletAmount?.currencySymbol ?: "NONE")
}
@ -112,5 +133,6 @@ enum class MainCurrencyType {
data class MainCurrency(
val type: MainCurrencyType,
val currencySymbol: String
val currencySymbol: String,
val isEnabled: Boolean = true
)

View file

@ -16,7 +16,6 @@ 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.*
@ -58,11 +57,6 @@ 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()
@ -193,8 +187,6 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
}
override fun subscribeToStore() {
if (store.state.sendState.hasInitializationError) return
store.subscribe(sendSubscriber) { appState ->
appState.skipRepeats { oldState, newState ->
oldState.sendState == newState.sendState