SendModal: new AmountToSend initial integration

- new AmountToSend used in SendModal
- MaxSendButton simplified

Closes: #15207
This commit is contained in:
Michał Cieślak 2024-07-17 17:48:56 +02:00 committed by Michał
parent 613fa4d19f
commit 1e88756c13
4 changed files with 148 additions and 74 deletions

View File

@ -176,7 +176,7 @@ QObject* ObjectProxyModel::proxyObject(int index)
auto parentContext = creationContext auto parentContext = creationContext
? creationContext : m_delegate->engine()->rootContext(); ? creationContext : m_delegate->engine()->rootContext();
auto context = new QQmlContext(parentContext/*, submodelObj*/); auto context = new QQmlContext(parentContext);
auto rowData = new QQmlPropertyMap(context); auto rowData = new QQmlPropertyMap(context);
auto model = sourceModel(); auto model = sourceModel();

View File

@ -1,33 +1,19 @@
import QtQuick 2.15 import QtQuick 2.15
import QtQuick.Controls 2.15
import StatusQ.Core 0.1
import StatusQ.Controls 0.1 import StatusQ.Controls 0.1
import AppLayouts.Wallet 1.0
import utils 1.0
StatusButton { StatusButton {
id: root required property string formattedValue
property bool markAsInvalid: false
required property double value text: qsTr("Max. %1").arg(formattedValue)
required property string symbol type: markAsInvalid ? StatusBaseButton.Type.Danger
required property bool valid : StatusBaseButton.Type.Normal
property var formatCurrencyAmount: (amount, symbol) => { return "FIXME" }
readonly property double maxSafeValue: WalletUtils.calculateMaxSafeSendAmount(value, symbol)
readonly property string maxSafeValueAsString: maxSafeValue.toLocaleString(locale, 'f', -128)
locale: LocaleUtils.userInputLocale
implicitHeight: 22
type: valid ? StatusBaseButton.Type.Normal : StatusBaseButton.Type.Danger
text: qsTr("Max. %1").arg(value === 0 ? locale.zeroDigit : root.formatCurrencyAmount(maxSafeValue, root.symbol))
horizontalPadding: 8 horizontalPadding: 8
verticalPadding: 3 verticalPadding: 3
implicitHeight: 22
radius: 20 radius: 20
font.pixelSize: 12 font.pixelSize: 12
font.weight: Font.Normal font.weight: Font.Normal

View File

@ -10,6 +10,7 @@ import StatusQ.Core 0.1
import StatusQ.Core.Utils 0.1 as SQUtils import StatusQ.Core.Utils 0.1 as SQUtils
import StatusQ.Core.Theme 0.1 import StatusQ.Core.Theme 0.1
import AppLayouts.Wallet 1.0
import AppLayouts.Wallet.controls 1.0 import AppLayouts.Wallet.controls 1.0
import AppLayouts.Wallet.stores 1.0 import AppLayouts.Wallet.stores 1.0
import AppLayouts.Wallet.adaptors 1.0 import AppLayouts.Wallet.adaptors 1.0
@ -256,14 +257,24 @@ Control {
MaxSendButton { MaxSendButton {
id: maxSendButton id: maxSendButton
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
Layout.maximumWidth: parent.width Layout.maximumWidth: parent.width
objectName: "maxTagButton" objectName: "maxTagButton"
value: d.maxInputBalance readonly property double maxSafeValue: WalletUtils.calculateMaxSafeSendAmount(
symbol: d.inputSymbol d.maxInputBalance, d.inputSymbol)
valid: (amountToSendInput.input.valid || !amountToSendInput.input.text) && value > 0 readonly property string maxSafeValueAsString: maxSafeValue.toLocaleString(
formatCurrencyAmount: (amount, symbol) => root.currencyStore.formatCurrencyAmount(amount, symbol, {noSymbol: !amountToSendInput.inputIsFiat}) LocaleUtils.userInputLocale, 'f', -128)
markAsInvalid: (!amountToSendInput.input.valid && !!amountToSendInput.input.text)
|| d.maxInputBalance === 0
formattedValue:
d.maxInputBalance === 0 ? LocaleUtils.userInputLocale.zeroDigit
: root.currencyStore.formatCurrencyAmount(
maxSafeValue, d.inputSymbol,
{ noSymbol: !amountToSendInput.inputIsFiat })
visible: d.isSelectedHoldingValidAsset && root.swapSide === SwapInputPanel.SwapSide.Pay visible: d.isSelectedHoldingValidAsset && root.swapSide === SwapInputPanel.SwapSide.Pay

View File

@ -73,9 +73,9 @@ StatusDialog {
property var recalculateRoutesAndFees: Backpressure.debounce(popup, 600, function() { property var recalculateRoutesAndFees: Backpressure.debounce(popup, 600, function() {
if(!!popup.preSelectedAccount && !!holdingSelector.selectedItem if(!!popup.preSelectedAccount && !!holdingSelector.selectedItem
&& recipientInputLoader.ready && (amountToSendInput.inputNumberValid || d.isCollectiblesTransfer)) { && recipientInputLoader.ready && (amountToSend.ready || d.isCollectiblesTransfer)) {
popup.isLoading = true popup.isLoading = true
popup.store.suggestedRoutes(d.isCollectiblesTransfer ? "1" : amountToSendInput.cryptoValueToSend) popup.store.suggestedRoutes(d.isCollectiblesTransfer ? "1" : amountToSend.amount)
} }
}) })
@ -94,15 +94,34 @@ StatusDialog {
popup.preSelectedSendType === Constants.SendType.StickersBuy popup.preSelectedSendType === Constants.SendType.StickersBuy
readonly property var currencyStore: store.currencyStore readonly property var currencyStore: store.currencyStore
readonly property int errorType: !amountToSendInput.input.valid && (!isCollectiblesTransfer) ? Constants.SendAmountExceedsBalance :
(popup.bestRoutes && popup.bestRoutes.count === 0 && readonly property int errorType: {
!!amountToSendInput.input.text && recipientInputLoader.ready && !popup.isLoading) ? if (amountToSend.balanceExceeded && !isCollectiblesTransfer)
Constants.NoRoute : Constants.NoError return Constants.SendAmountExceedsBalance
if (popup.bestRoutes && popup.bestRoutes.count === 0
&& !amountToSend.empty && recipientInputLoader.ready
&& !popup.isLoading)
return Constants.NoRoute
return Constants.NoError
}
readonly property double maxFiatBalance: isSelectedHoldingValidAsset ? selectedHolding.currentCurrencyBalance : 0 readonly property double maxFiatBalance: isSelectedHoldingValidAsset ? selectedHolding.currentCurrencyBalance : 0
readonly property double maxCryptoBalance: isSelectedHoldingValidAsset ? selectedHolding.currentBalance : 0 readonly property double maxCryptoBalance: isSelectedHoldingValidAsset ? selectedHolding.currentBalance : 0
readonly property double maxInputBalance: amountToSendInput.inputIsFiat ? maxFiatBalance : maxCryptoBalance readonly property double maxInputBalance: amountToSend.fiatMode ? maxFiatBalance : maxCryptoBalance
readonly property string inputSymbol: amountToSendInput.inputIsFiat ? currencyStore.currentCurrency : !!d.selectedHolding && !!d.selectedHolding.symbol ? d.selectedHolding.symbol: ""
readonly property bool errorMode: popup.isLoading || !recipientInputLoader.ready ? false : errorType !== Constants.NoError || networkSelector.errorMode || !(amountToSendInput.inputNumberValid || d.isCollectiblesTransfer) readonly property string tokenSymbol: !!d.selectedHolding && !!d.selectedHolding.symbol ? d.selectedHolding.symbol: ""
readonly property string inputSymbol: amountToSend.fiatMode ? currencyStore.currentCurrency : tokenSymbol
readonly property bool errorMode: {
if (popup.isLoading || !recipientInputLoader.ready)
return false
return errorType !== Constants.NoError
|| networkSelector.errorMode
|| !(amountToSend.ready || d.isCollectiblesTransfer)
}
readonly property string uuid: Utils.uuid() readonly property string uuid: Utils.uuid()
property bool isPendingTx: false property bool isPendingTx: false
property string totalTimeEstimate property string totalTimeEstimate
@ -114,9 +133,6 @@ StatusDialog {
property var selectedHolding: null property var selectedHolding: null
property var selectedHoldingType: Constants.TokenType.Unknown property var selectedHoldingType: Constants.TokenType.Unknown
readonly property bool isSelectedHoldingValidAsset: !!selectedHolding && selectedHoldingType === Constants.TokenType.ERC20 readonly property bool isSelectedHoldingValidAsset: !!selectedHolding && selectedHoldingType === Constants.TokenType.ERC20
property var hoveredHolding: null
property var hoveredHoldingType: Constants.TokenType.Unknown
readonly property bool isHoveredHoldingValidAsset: !!hoveredHolding && hoveredHoldingType === Constants.TokenType.ERC20
onSelectedHoldingChanged: { onSelectedHoldingChanged: {
if (d.selectedHoldingType === Constants.TokenType.ERC20) { if (d.selectedHoldingType === Constants.TokenType.ERC20) {
@ -128,7 +144,7 @@ StatusDialog {
d.selectedHoldingType === Constants.TokenType.ERC1155) { d.selectedHoldingType === Constants.TokenType.ERC1155) {
let sendType = d.selectedHoldingType === Constants.TokenType.ERC721 ? Constants.SendType.ERC721Transfer : Constants.SendType.ERC1155Transfer let sendType = d.selectedHoldingType === Constants.TokenType.ERC721 ? Constants.SendType.ERC721Transfer : Constants.SendType.ERC1155Transfer
store.setSendType(sendType) store.setSendType(sendType)
amountToSendInput.input.text = 1 amountToSend.setValue("1")
store.setSelectedAssetKey(selectedHolding.contractAddress+":"+selectedHolding.tokenId) store.setSelectedAssetKey(selectedHolding.contractAddress+":"+selectedHolding.tokenId)
store.setRouteEnabledFromChains(selectedHolding.chainId) store.setRouteEnabledFromChains(selectedHolding.chainId)
store.updateRoutePreferredChains(selectedHolding.chainId) store.updateRoutePreferredChains(selectedHolding.chainId)
@ -168,7 +184,7 @@ StatusDialog {
} }
onOpened: { onOpened: {
amountToSendInput.input.input.edit.forceActiveFocus() amountToSend.forceActiveFocus()
// IMPORTANT: This step must be the first one since it's storing the send type // IMPORTANT: This step must be the first one since it's storing the send type
// into the backend at this stage so that, before this assignement, some properties // into the backend at this stage so that, before this assignement, some properties
@ -216,7 +232,14 @@ StatusDialog {
} }
if(!!popup.preDefinedAmountToSend) { if(!!popup.preDefinedAmountToSend) {
amountToSendInput.input.text = Number(popup.preDefinedAmountToSend).toLocaleString(Qt.locale(), 'f', -128) // TODO: At this stage the number should not be localized. However
// in many places when initializing popup the number is provided
// in localized version. It should be refactored to provide raw
// number consistently. Only the displaying component should apply
// final localized formatting.
const delocalized = popup.preDefinedAmountToSend.replace(",", ".")
amountToSend.setValue(delocalized)
} }
} }
@ -379,45 +402,98 @@ StatusDialog {
} }
MaxSendButton { MaxSendButton {
id: maxButton
readonly property double maxSafeValue: WalletUtils.calculateMaxSafeSendAmount(
d.maxInputBalance, d.inputSymbol)
readonly property double maxSafeCryptoValue: WalletUtils.calculateMaxSafeSendAmount(
d.maxCryptoBalance, d.tokenSymbol)
formattedValue: d.currencyStore.formatCurrencyAmount(
maxSafeValue, d.inputSymbol,
{ noSymbol: !amountToSend.fiatMode })
markAsInvalid: amountToSend.markAsInvalid
Layout.maximumWidth: 300 Layout.maximumWidth: 300
Layout.alignment: Qt.AlignVCenter | Qt.AlignRight Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
visible: d.isSelectedHoldingValidAsset || d.isHoveredHoldingValidAsset && !d.isCollectiblesTransfer
value: d.maxInputBalance visible: d.isSelectedHoldingValidAsset && !d.isCollectiblesTransfer
symbol: d.inputSymbol
valid: amountToSendInput.input.valid || !amountToSendInput.input.text
formatCurrencyAmount: (amount, symbol) => d.currencyStore.formatCurrencyAmount(amount, symbol, {noSymbol: !amountToSendInput.inputIsFiat})
onClicked: { onClicked: {
if (maxSafeValue > 0) if (maxSafeValue > 0) {
amountToSendInput.input.text = maxSafeValueAsString amountToSend.setValue(SQUtils.AmountsArithmetic.fromNumber(maxSafeValue).toString())
else }else {
amountToSendInput.input.input.edit.clear() amountToSend.clear()
amountToSendInput.input.forceActiveFocus() }
amountToSend.forceActiveFocus()
} }
} }
} }
RowLayout { RowLayout {
visible: d.isSelectedHoldingValidAsset && !d.isCollectiblesTransfer visible: d.isSelectedHoldingValidAsset && !d.isCollectiblesTransfer
AmountToSend {
id: amountToSendInput
Layout.fillWidth: true AmountToSendNew {
isBridgeTx: d.isBridgeTx id: amountToSend
caption: d.isBridgeTx ? qsTr("Amount to bridge")
: qsTr("Amount to send")
interactive: popup.interactive interactive: popup.interactive
selectedHolding: d.selectedHolding
maxInputBalance: d.maxInputBalance readonly property bool balanceExceeded:
currentCurrency: d.currencyStore.currentCurrency SQUtils.AmountsArithmetic.cmp(
SQUtils.AmountsArithmetic.fromNumber(maxButton.maxSafeCryptoValue, multiplierIndex),
SQUtils.AmountsArithmetic.fromString(amount)) === -1
readonly property bool ready: valid && !empty && !balanceExceeded
&& amount !== "0"
readonly property string selectedSymbol:
!!d.selectedHolding && !!d.selectedHolding.symbol
? d.selectedHolding.symbol : ""
// For backward compatibility. To be removed when
// dependent components (NetworkSelector, AmountToReceive)
// are refactored.
readonly property double asNumber: {
if (!valid)
return 0
return parseFloat(text.replace(",", "."))
}
readonly property int minSendCryptoDecimals:
!fiatMode ? LocaleUtils.fractionalPartLength(asNumber) : 0
readonly property int minReceiveCryptoDecimals:
!fiatMode ? minSendCryptoDecimals + 1 : 0
readonly property int minSendFiatDecimals:
fiatMode ? LocaleUtils.fractionalPartLength(asNumber) : 0
readonly property int minReceiveFiatDecimals:
fiatMode ? minSendFiatDecimals + 1 : 0
// End of to-be-removed part
decimalPoint: LocaleUtils.userInputLocale.decimalPoint
markAsInvalid: balanceExceeded
// Collectibles do not have decimals // Collectibles do not have decimals
multiplierIndex: d.isSelectedHoldingValidAsset && !!holdingSelector.selectedItem && !!holdingSelector.selectedItem.decimals multiplierIndex:
? holdingSelector.selectedItem.decimals d.isSelectedHoldingValidAsset
: 0 && !!holdingSelector.selectedItem
&& !!holdingSelector.selectedItem.decimals
? holdingSelector.selectedItem.decimals : 0
formatCurrencyAmount: d.currencyStore.formatCurrencyAmount price: d.isSelectedHoldingValidAsset
onReCalculateSuggestedRoute: popup.recalculateRoutesAndFees() ? (d.selectedHolding ?
input.input.tabNavItem: recipientInputLoader.item d.selectedHolding.marketDetails.currencyPrice.amount : 1)
Keys.onTabPressed: event.accepted = true : 1
formatFiat: amount => d.currencyStore.formatCurrencyAmount(
amount, d.currencyStore.currentCurrency)
formatBalance: amount => d.currencyStore.formatCurrencyAmount(
amount, selectedSymbol)
onAmountChanged: popup.recalculateRoutesAndFees()
} }
// Horizontal spacer // Horizontal spacer
@ -428,14 +504,14 @@ StatusDialog {
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
Layout.fillWidth:true Layout.fillWidth:true
visible: !!popup.bestRoutes && popup.bestRoutes !== undefined && visible: !!popup.bestRoutes && popup.bestRoutes !== undefined &&
popup.bestRoutes.count > 0 && amountToSendInput.inputNumberValid popup.bestRoutes.count > 0 && amountToSend.ready
isLoading: popup.isLoading isLoading: popup.isLoading
selectedHolding: d.selectedHolding selectedHolding: d.selectedHolding
isBridgeTx: d.isBridgeTx isBridgeTx: d.isBridgeTx
cryptoValueToReceive: d.totalAmountToReceive cryptoValueToReceive: d.totalAmountToReceive
inputIsFiat: amountToSendInput.inputIsFiat inputIsFiat: amountToSend.fiatMode
minCryptoDecimals: amountToSendInput.minReceiveCryptoDecimals minCryptoDecimals: amountToSend.minReceiveCryptoDecimals
minFiatDecimals: amountToSendInput.minReceiveFiatDecimals minFiatDecimals: amountToSend.minReceiveFiatDecimals
currentCurrency: d.currencyStore.currentCurrency currentCurrency: d.currencyStore.currentCurrency
formatCurrencyAmount: d.currencyStore.formatCurrencyAmount formatCurrencyAmount: d.currencyStore.formatCurrencyAmount
} }
@ -520,7 +596,7 @@ StatusDialog {
contentWidth: availableWidth contentWidth: availableWidth
visible: recipientInputLoader.ready && visible: recipientInputLoader.ready &&
(amountToSendInput.inputNumberValid || d.isCollectiblesTransfer) (amountToSend.ready || d.isCollectiblesTransfer)
objectName: "sendModalScroll" objectName: "sendModalScroll"
@ -537,9 +613,9 @@ StatusDialog {
interactive: popup.interactive interactive: popup.interactive
selectedRecipient: popup.preSelectedRecipient selectedRecipient: popup.preSelectedRecipient
ensAddressOrEmpty: recipientInputLoader.resolvedENSAddress ensAddressOrEmpty: recipientInputLoader.resolvedENSAddress
amountToSend: amountToSendInput.cryptoValueToSendFloat amountToSend: amountToSend.cryptoValueToSendFloat
minSendCryptoDecimals: amountToSendInput.minSendCryptoDecimals minSendCryptoDecimals: amountToSend.minSendCryptoDecimals
minReceiveCryptoDecimals: amountToSendInput.minReceiveCryptoDecimals minReceiveCryptoDecimals: amountToSend.minReceiveCryptoDecimals
selectedAsset: d.selectedHolding selectedAsset: d.selectedHolding
onReCalculateSuggestedRoute: popup.recalculateRoutesAndFees() onReCalculateSuggestedRoute: popup.recalculateRoutesAndFees()
errorType: d.errorType errorType: d.errorType
@ -562,7 +638,8 @@ StatusDialog {
maxFiatFees: popup.isLoading ? "..." : d.currencyStore.formatCurrencyAmount(d.totalFeesInFiat, d.currencyStore.currentCurrency) maxFiatFees: popup.isLoading ? "..." : d.currencyStore.formatCurrencyAmount(d.totalFeesInFiat, d.currencyStore.currentCurrency)
totalTimeEstimate: popup.isLoading? "..." : d.totalTimeEstimate totalTimeEstimate: popup.isLoading? "..." : d.totalTimeEstimate
pending: d.isPendingTx || popup.isLoading pending: d.isPendingTx || popup.isLoading
visible: recipientInputLoader.ready && (amountToSendInput.inputNumberValid || d.isCollectiblesTransfer) && !d.errorMode visible: recipientInputLoader.ready && (amountToSend.ready || d.isCollectiblesTransfer) && !d.errorMode
onNextButtonClicked: popup.sendTransaction() onNextButtonClicked: popup.sendTransaction()
} }