import QtQuick 2.15 import QtQuick.Layouts 1.15 import StatusQ.Core 0.1 import StatusQ.Core.Theme 0.1 import StatusQ.Controls 0.1 import StatusQ.Core.Utils 0.1 as SQUtils import StatusQ.Components 0.1 import StatusQ.Controls.Validators 0.1 import "../controls" import utils 1.0 ColumnLayout { id: root readonly property alias input: topAmountToSendInput readonly property bool inputNumberValid: !!input.text && !isNaN(d.parsedInput) && input.valid readonly property int minSendCryptoDecimals: !inputIsFiat ? LocaleUtils.fractionalPartLength(d.inputNumber) : 0 readonly property int minReceiveCryptoDecimals: !inputIsFiat ? minSendCryptoDecimals + 1 : 0 readonly property int minSendFiatDecimals: inputIsFiat ? LocaleUtils.fractionalPartLength(d.inputNumber) : 0 readonly property int minReceiveFiatDecimals: inputIsFiat ? minSendFiatDecimals + 1 : 0 property var selectedHolding // Crypto asset symbol like ETH property string currentCurrency // Fiat currency symbol like USD property int multiplierIndex // How divisible the token is, 18 for ETH property double maxInputBalance property bool isBridgeTx: false property bool interactive: false property bool inputIsFiat: false property string caption: isBridgeTx ? qsTr("Amount to bridge") : qsTr("Amount to send") property bool fiatInputInteractive: true // Crypto value to send expressed in base units (like wei for ETH), // as a string representing integer decimal readonly property alias cryptoValueToSend: d.cryptoValueRawToSend readonly property alias cryptoValueToSendFloat: d.cryptoValueToSend property var formatCurrencyAmount: (amount, symbol, options = null, locale = null) => {} property bool mainInputLoading property bool bottomTextLoading signal reCalculateSuggestedRoute() QtObject { id: d property double cryptoValueToSend property double fiatValueToSend Binding on cryptoValueToSend { value: { root.selectedHolding if(!root.selectedHolding || !root.selectedHolding.marketDetails || !root.selectedHolding.marketDetails.currencyPrice) { return 0 } return root.inputIsFiat ? d.fiatValueToSend/root.selectedHolding.marketDetails.currencyPrice.amount : d.inputNumber } delayed: true } Binding on fiatValueToSend { value: { root.selectedHolding if(!root.selectedHolding || !root.selectedHolding.marketDetails || !root.selectedHolding.marketDetails.currencyPrice) { return 0 } return root.inputIsFiat ? d.inputNumber : d.cryptoValueToSend * root.selectedHolding.marketDetails.currencyPrice.amount } delayed: true } readonly property string selectedSymbol: !!root.selectedHolding && !!root.selectedHolding.symbol ? root.selectedHolding.symbol: "" readonly property string cryptoValueRawToSend: { return SQUtils.AmountsArithmetic.fromNumber( d.cryptoValueToSend, root.multiplierIndex).toString() } // Crypto value should be represented by 0 and fiat with 0.00 readonly property string zeroString: { let decimals = root.inputIsFiat ? 2 : 0 LocaleUtils.numberToLocaleString(0, decimals, topAmountToSendInput.locale) } readonly property double parsedInput: LocaleUtils.numberFromLocaleString(topAmountToSendInput.text, topAmountToSendInput.locale) readonly property double inputNumber: // we should still calculate if value entered is greater than max safe value !!input.text && !isNaN(d.parsedInput) && d.parsedInput >= 0 ? d.parsedInput : 0 readonly property Timer waitTimer: Timer { interval: 1000 onTriggered: reCalculateSuggestedRoute() } } onMaxInputBalanceChanged: { input.validate() } onSelectedHoldingChanged: { input.validate() } StatusBaseText { text: root.caption font.pixelSize: 13 lineHeight: 18 lineHeightMode: Text.FixedHeight color: Theme.palette.directColor1 } RowLayout { Layout.fillWidth: true id: topItem AmountInputWithCursor { id: topAmountToSendInput Layout.fillWidth: true Layout.maximumWidth: 250 Layout.preferredWidth: !!text ? input.edit.paintedWidth + 2 : textMetrics.advanceWidth placeholderText: d.zeroString input.edit.color: input.valid ? Theme.palette.directColor1 : Theme.palette.dangerColor1 input.edit.readOnly: !root.interactive validationMode: StatusInput.ValidationMode.Always validators: [ StatusValidator { errorMessage: "" validate: (text) => { const num = LocaleUtils.numberFromLocaleString(topAmountToSendInput.text, topAmountToSendInput.locale) if (isNaN(num) || num <= 0 || num > root.maxInputBalance) { return false } if(!root.selectedHolding || !root.selectedHolding.marketDetails || !root.selectedHolding.marketDetails.currencyPrice) { return false } const cryptoValueToSend = root.inputIsFiat ? num / root.selectedHolding.marketDetails.currencyPrice.amount : num const cryptoValueToSendRaw = SQUtils.AmountsArithmetic.fromNumber(cryptoValueToSend, root.multiplierIndex).toString() return cryptoValueToSendRaw >= 1 } } ] TextMetrics { id: textMetrics text: topAmountToSendInput.placeholderText font: topAmountToSendInput.placeholderFont } Keys.onReleased: { const amount = LocaleUtils.numberFromLocaleString( topAmountToSendInput.text, locale) if (!isNaN(amount)) d.waitTimer.restart() } visible: !root.mainInputLoading } LoadingComponent { objectName: "topAmountToSendInputLoadingComponent" Layout.preferredWidth: topAmountToSendInput.width Layout.preferredHeight: topAmountToSendInput.height visible: root.mainInputLoading } } StatusBaseText { Layout.maximumWidth: parent.width id: bottomItem objectName: "bottomItemText" readonly property double bottomAmountToSend: inputIsFiat ? d.cryptoValueToSend : d.fiatValueToSend readonly property string bottomAmountSymbol: inputIsFiat ? d.selectedSymbol : root.currentCurrency elide: Text.ElideMiddle text: root.formatCurrencyAmount(bottomAmountToSend, bottomAmountSymbol) font.pixelSize: 13 color: Theme.palette.directColor5 MouseArea { anchors.fill: parent cursorShape: enabled ? Qt.PointingHandCursor : undefined enabled: root.fiatInputInteractive && !!root.selectedHolding onClicked: { topAmountToSendInput.validate() if(!!topAmountToSendInput.text) { topAmountToSendInput.text = root.formatCurrencyAmount( bottomItem.bottomAmountToSend, bottomItem.bottomAmountSymbol, { noSymbol: true, rawAmount: true }, topAmountToSendInput.locale) } root.inputIsFiat = !root.inputIsFiat d.waitTimer.restart() } } visible: !root.bottomTextLoading } LoadingComponent { objectName: "bottomItemTextLoadingComponent" Layout.preferredWidth: bottomItem.width Layout.preferredHeight: bottomItem.height visible: root.bottomTextLoading } }