From 7c10b16b674cbf34a750307004578ad38b0199ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Tinkl?= Date: Wed, 14 Aug 2024 23:56:48 +0200 Subject: [PATCH] chore(AmountToSend): AmountToSendNew is dead, long live AmountToSend --- storybook/pages/AmountToSendNewPage.qml | 159 ------- storybook/pages/AmountToSendPage.qml | 188 +++++--- ...ountToSendNew.qml => tst_AmountToSend.qml} | 6 +- .../Wallet/panels/SwapInputPanel.qml | 2 +- ui/imports/shared/popups/send/SendModal.qml | 2 +- .../shared/popups/send/views/AmountToSend.qml | 410 ++++++++++-------- .../popups/send/views/AmountToSendNew.qml | 284 ------------ ui/imports/shared/popups/send/views/qmldir | 1 - 8 files changed, 359 insertions(+), 693 deletions(-) delete mode 100644 storybook/pages/AmountToSendNewPage.qml rename storybook/qmlTests/tests/{tst_AmountToSendNew.qml => tst_AmountToSend.qml} (98%) delete mode 100644 ui/imports/shared/popups/send/views/AmountToSendNew.qml diff --git a/storybook/pages/AmountToSendNewPage.qml b/storybook/pages/AmountToSendNewPage.qml deleted file mode 100644 index 3f57b21bd9..0000000000 --- a/storybook/pages/AmountToSendNewPage.qml +++ /dev/null @@ -1,159 +0,0 @@ -import QtQuick 2.15 -import QtQuick.Controls 2.15 -import QtQuick.Layouts 1.15 - -import Qt.labs.settings 1.0 - -import shared.popups.send.views 1.0 - -SplitView { - orientation: Qt.Vertical - SplitView.fillWidth: true - - Item { - SplitView.fillWidth: true - SplitView.fillHeight: true - - AmountToSendNew { - id: amountToSend - - anchors.centerIn: parent - - interactive: interactiveCheckBox.checked - fiatInputInteractive: fiatInteractiveCheckBox.checked - markAsInvalid: markAsInvalidCheckBox.checked - - mainInputLoading: ctrlMainInputLoading.checked - bottomTextLoading: ctrlBottomTextLoading.checked - - caption: "Amount to send" - - decimalPoint: decimalPointRadioButton.checked ? "." : "," - price: parseFloat(priceTextField.text) - - multiplierIndex: multiplierIndexSpinBox.value - - formatFiat: balance => `${balance.toLocaleString(Qt.locale())} USD` - formatBalance: balance => `${balance.toLocaleString(Qt.locale())} ETH` - } - } - - Pane { - id: logsAndControlsPanel - - SplitView.minimumHeight: 350 - - ColumnLayout { - spacing: 15 - - RowLayout { - Label { - text: "Price" - } - - TextField { - id: priceTextField - - text: "812.323" - } - } - - RowLayout { - Label { - text: "Decimal point" - } - - RadioButton { - id: decimalPointRadioButton - - text: "." - } - - RadioButton { - text: "," - checked: true - } - } - - RowLayout { - Label { - text: "Multiplier index" - } - - SpinBox { - id: multiplierIndexSpinBox - - editable: true - value: 18 - to: 30 - } - } - - RowLayout { - CheckBox { - id: interactiveCheckBox - - text: "Interactive" - checked: true - } - - CheckBox { - id: fiatInteractiveCheckBox - - text: "Fiat mode interactive" - checked: true - } - - CheckBox { - id: markAsInvalidCheckBox - - text: "Mark as invalid" - } - - CheckBox { - id: ctrlMainInputLoading - text: "Input loading" - } - - CheckBox { - id: ctrlBottomTextLoading - text: "Bottom text loading" - } - } - - Label { - font.bold: true - text: `fiat mode: ${amountToSend.fiatMode}, ` + - `valid: ${amountToSend.valid}, ` + - `empty: ${amountToSend.empty}, ` + - `amount: ${amountToSend.amount}` - } - - RowLayout { - Label { - text: `Set value` - } - - TextField { - id: amountTextField - - text: "0.0012" - } - - Button { - text: "SET" - - onClicked: { - amountToSend.setValue(amountTextField.text) - } - } - } - } - } - - Settings { - property alias multiplier: multiplierIndexSpinBox.value - } -} - -// category: Components diff --git a/storybook/pages/AmountToSendPage.qml b/storybook/pages/AmountToSendPage.qml index 8d3f7f6aee..0d2662a905 100644 --- a/storybook/pages/AmountToSendPage.qml +++ b/storybook/pages/AmountToSendPage.qml @@ -2,100 +2,158 @@ import QtQuick 2.15 import QtQuick.Controls 2.15 import QtQuick.Layouts 1.15 -import StatusQ.Core 0.1 +import Qt.labs.settings 1.1 + import shared.popups.send.views 1.0 -import Storybook 1.0 -import Models 1.0 - SplitView { - id: root + orientation: Qt.Vertical + SplitView.fillWidth: true - readonly property var tokensBySymbolModel: TokensBySymbolModel {} - - readonly property double maxCryptoBalance: parseFloat(maxCryptoBalanceText.text) - readonly property int decimals: parseInt(decimalsText.text) - - Logs { id: logs } - - Component.onCompleted: amountToSendInput.input.forceActiveFocus() - - SplitView { - orientation: Qt.Vertical + Item { SplitView.fillWidth: true + SplitView.fillHeight: true - Item { - SplitView.fillWidth: true - SplitView.fillHeight: true + AmountToSend { + id: amountToSend - AmountToSend { - id: amountToSendInput - isBridgeTx: false - interactive: true - selectedHolding: tokensBySymbolModel.data[0] + anchors.centerIn: parent - inputIsFiat: fiatInput.checked + interactive: interactiveCheckBox.checked + fiatInputInteractive: fiatInteractiveCheckBox.checked + markAsInvalid: markAsInvalidCheckBox.checked - maxInputBalance: inputIsFiat ? root.maxCryptoBalance*amountToSendInput.selectedHolding.marketDetails.currencyPrice.amount - : root.maxCryptoBalance - currentCurrency: "USD" - formatCurrencyAmount: function(amount, symbol, options, locale) { - const currencyAmount = { - amount: amount, - symbol: symbol, - displayDecimals: root.decimals, - stripTrailingZeroes: true - } - return LocaleUtils.currencyAmountToLocaleString(currencyAmount, options, locale) + mainInputLoading: ctrlMainInputLoading.checked + bottomTextLoading: ctrlBottomTextLoading.checked + + caption: "Amount to send" + + decimalPoint: decimalPointRadioButton.checked ? "." : "," + price: parseFloat(priceTextField.text) + + multiplierIndex: multiplierIndexSpinBox.value + + formatFiat: balance => `${balance.toLocaleString(Qt.locale())} USD` + formatBalance: balance => `${balance.toLocaleString(Qt.locale())} ETH` + } + } + + Pane { + id: logsAndControlsPanel + + SplitView.minimumHeight: 350 + + ColumnLayout { + spacing: 15 + + RowLayout { + Label { + text: "Price" } - onReCalculateSuggestedRoute: function() { - logs.logEvent("onReCalculateSuggestedRoute") + + TextField { + id: priceTextField + + text: "812.323" } } - } - LogsAndControlsPanel { - id: logsAndControlsPanel - - SplitView.minimumHeight: 250 - - logsView.logText: logs.logText - - ColumnLayout { + RowLayout { Label { - Layout.topMargin: 10 - Layout.fillWidth: true - text: "Max Crypto Balance" + text: "Decimal point" } - TextField { - id: maxCryptoBalanceText - background: Rectangle { border.color: 'lightgrey' } - Layout.preferredWidth: 200 - text: "1000000" + RadioButton { + id: decimalPointRadioButton + + text: "." } + RadioButton { + text: "," + checked: true + } + } + + RowLayout { Label { - Layout.topMargin: 10 - Layout.fillWidth: true - text: "Decimals" + text: "Multiplier index" } - TextField { - id: decimalsText - background: Rectangle { border.color: 'lightgrey' } - Layout.preferredWidth: 200 - text: "6" + SpinBox { + id: multiplierIndexSpinBox + + editable: true + value: 18 + to: 30 + } + } + + RowLayout { + CheckBox { + id: interactiveCheckBox + + text: "Interactive" + checked: true } CheckBox { - id: fiatInput + id: fiatInteractiveCheckBox - text: "Fiat input value" + text: "Fiat mode interactive" + checked: true + } + + CheckBox { + id: markAsInvalidCheckBox + + text: "Mark as invalid" + } + + CheckBox { + id: ctrlMainInputLoading + text: "Input loading" + } + + CheckBox { + id: ctrlBottomTextLoading + text: "Bottom text loading" + } + } + + Label { + font.bold: true + text: `fiat mode: ${amountToSend.fiatMode}, ` + + `valid: ${amountToSend.valid}, ` + + `empty: ${amountToSend.empty}, ` + + `amount: ${amountToSend.amount}` + } + + RowLayout { + Label { + text: `Set value` + } + + TextField { + id: amountTextField + + text: "0.0012" + } + + Button { + text: "SET" + + onClicked: { + amountToSend.setValue(amountTextField.text) + } } } } } + + Settings { + property alias multiplier: multiplierIndexSpinBox.value + } } // category: Components diff --git a/storybook/qmlTests/tests/tst_AmountToSendNew.qml b/storybook/qmlTests/tests/tst_AmountToSend.qml similarity index 98% rename from storybook/qmlTests/tests/tst_AmountToSendNew.qml rename to storybook/qmlTests/tests/tst_AmountToSend.qml index 44a15c452c..da3bdec8f2 100644 --- a/storybook/qmlTests/tests/tst_AmountToSendNew.qml +++ b/storybook/qmlTests/tests/tst_AmountToSend.qml @@ -13,10 +13,10 @@ Item { Component { id: componentUnderTest - AmountToSendNew {} + AmountToSend {} } - property AmountToSendNew amountToSend + property AmountToSend amountToSend SignalSpy { id: amountChangedSpy @@ -25,7 +25,7 @@ Item { } TestCase { - name: "AmountToSendNew" + name: "AmountToSend" when: windowShown function type(key, times = 1) { diff --git a/ui/app/AppLayouts/Wallet/panels/SwapInputPanel.qml b/ui/app/AppLayouts/Wallet/panels/SwapInputPanel.qml index 6ffbdb6f24..bba3b2b656 100644 --- a/ui/app/AppLayouts/Wallet/panels/SwapInputPanel.qml +++ b/ui/app/AppLayouts/Wallet/panels/SwapInputPanel.qml @@ -213,7 +213,7 @@ Control { Layout.preferredWidth: parent.width*.66 Layout.fillHeight: true - AmountToSendNew { + AmountToSend { readonly property bool balanceExceeded: SQUtils.AmountsArithmetic.fromNumber(maxSendButton.maxSafeCryptoValue, multiplierIndex).cmp(amount) === -1 diff --git a/ui/imports/shared/popups/send/SendModal.qml b/ui/imports/shared/popups/send/SendModal.qml index 7944481dff..93af0feac4 100644 --- a/ui/imports/shared/popups/send/SendModal.qml +++ b/ui/imports/shared/popups/send/SendModal.qml @@ -480,7 +480,7 @@ StatusDialog { RowLayout { visible: d.isSelectedHoldingValidAsset && !d.isCollectiblesTransfer - AmountToSendNew { + AmountToSend { id: amountToSend caption: d.isBridgeTx ? qsTr("Amount to bridge") diff --git a/ui/imports/shared/popups/send/views/AmountToSend.qml b/ui/imports/shared/popups/send/views/AmountToSend.qml index 6378e99110..596fb71d0a 100644 --- a/ui/imports/shared/popups/send/views/AmountToSend.qml +++ b/ui/imports/shared/popups/send/views/AmountToSend.qml @@ -1,232 +1,284 @@ import QtQuick 2.15 +import QtQuick.Controls 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 StatusQ.Validators 0.1 import utils 1.0 +import shared.controls 1.0 -ColumnLayout { +Control { id: root - readonly property alias input: topAmountToSendInput - readonly property bool inputNumberValid: !!input.text && !isNaN(d.parsedInput) && input.valid + /* Crypto value in a base unit as a string integer, e.g. 1000000000000000000 + * for 1 ETH */ + readonly property alias amount: d.amountBaseUnit - 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 + /* In fiat mode the input value is meant to be a fiat value, conversely, + * crypto value otherwise. */ + readonly property alias fiatMode: d.fiatMode - property var selectedHolding // Crypto asset symbol like ETH - property string currentCurrency // Fiat currency symbol like USD + /* Indicates whether toggling the fiatMode is enabled for the user */ + property bool fiatInputInteractive: interactive - property int multiplierIndex // How divisible the token is, 18 for ETH + /* Indicates if input represent valid number. E.g. empty input or containing + * only decimal point is not valid. */ + readonly property alias valid: textField.acceptableInput + readonly property bool empty: textField.length === 0 - property double maxInputBalance + // TODO: remove, temporarily for backward compatibility. External components + // should not rely on formatted amount because formatting rules are internal + // detail of that component. + readonly property alias text: textField.text - property bool isBridgeTx: false - property bool interactive: false - property bool inputIsFiat: false + /* Decimal point character to be displayed. Both "." and "," will be + * replaced by the provided decimal point on the fly */ + property alias decimalPoint: validator.decimalPoint - property string caption: isBridgeTx ? qsTr("Amount to bridge") : qsTr("Amount to send") + /* Number of fiat decimal places used to limit allowed decimal places in + * fiatMode */ + property int fiatDecimalPlaces: 2 - property bool fiatInputInteractive: true + /* Specifies how divisible given cryptocurrency is, e.g. 18 for ETH. Used + * for limiting allowed decimal places and computing final amount as an + * integer value */ + property int multiplierIndex: 18 - // Crypto value to send expressed in base units (like wei for ETH), - // as a string representing integer decimal - readonly property alias cryptoValueToSend: d.cryptoValueRawToSend + /* Price of one unit of given cryptocurrency (e.g. price for 1 ETH) */ + property real price: 1.0 - readonly property alias cryptoValueToSendFloat: d.cryptoValueToSend + property alias caption: captionText.text + property bool interactive: true - property var formatCurrencyAmount: - (amount, symbol, options = null, locale = null) => {} + readonly property bool cursorVisible: textField.cursorVisible + readonly property alias placeholderText: textField.placeholderText + /* Loading states for the input and text below */ property bool mainInputLoading property bool bottomTextLoading - signal reCalculateSuggestedRoute() + /* Allows mark input as invalid when it's valid number but doesn't satisfy + * arbitrary external criteria, e.g. is higher than maximum expected value. */ + property bool markAsInvalid: false + + /* Methods for formatting crypto and fiat value expecting double values, + e.g. 1.0 for 1 ETH or 1.0 for 1 USD. */ + property var formatFiat: balance => + `${balance.toLocaleString(Qt.locale())} FIAT` + property var formatBalance: balance => + `${balance.toLocaleString(Qt.locale())} CRYPTO` + + /* Allows to set value to be displayed. The value is expected to be a not + localized string like "1", "1.1" or "0.000000023400234222". Provided + value will be formatted and displayed. Depending on the fiatMode flag + it will affect output amount appropriately. */ + function setValue(valueString) { + if (!valueString) + valueString = "0" + + const decimalPlaces = d.fiatMode ? root.fiatDecimalPlaces + : root.multiplierIndex + + const stringNumber = SQUtils.AmountsArithmetic.fromString( + valueString).toFixed(decimalPlaces) + + const trimmed = d.fiatMode + ? stringNumber + : d.removeDecimalTrailingZeros(stringNumber) + + textField.text = d.localize(trimmed) + } + + function clear() { + textField.clear() + } + + function forceActiveFocus() { + textField.forceActiveFocus() + } QtObject { id: d - property double cryptoValueToSend - property double fiatValueToSend + property bool fiatMode: false - 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 + readonly property string inputDelocalized: + root.valid && textField.length !== 0 + ? textField.text.replace(root.decimalPoint, ".") : "0" + + function removeDecimalTrailingZeros(num) { + if (!num.includes(".")) + return num + + return num.replace(/\.?0*$/g, "") } - 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 + function localize(num) { + return num.replace(".", root.decimalPoint) } - readonly property string selectedSymbol: !!root.selectedHolding && !!root.selectedHolding.symbol ? root.selectedHolding.symbol: "" + readonly property string amountBaseUnit: { + if (d.fiatMode) + return secondaryValue - readonly property string cryptoValueRawToSend: { - return SQUtils.AmountsArithmetic.fromNumber( - d.cryptoValueToSend, root.multiplierIndex).toString() + const multiplier = SQUtils.AmountsArithmetic.fromExponent( + root.multiplierIndex) + + return SQUtils.AmountsArithmetic.times( + SQUtils.AmountsArithmetic.fromString(inputDelocalized), + multiplier).toFixed() } - // 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 string secondaryValue: { + const price = isNaN(root.price) ? 0 : root.price - readonly property double parsedInput: - LocaleUtils.numberFromLocaleString(topAmountToSendInput.text, - topAmountToSendInput.locale) + if (!d.fiatMode) + return SQUtils.AmountsArithmetic.times( + SQUtils.AmountsArithmetic.fromString(inputDelocalized), + SQUtils.AmountsArithmetic.fromNumber( + price * (10 ** root.fiatDecimalPlaces))).toFixed() - 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 + if (!price) // prevent div by zero below + return 0 - readonly property Timer waitTimer: Timer { - interval: 1000 - onTriggered: reCalculateSuggestedRoute() + const multiplier = SQUtils.AmountsArithmetic.fromExponent( + root.multiplierIndex) + + return SQUtils.AmountsArithmetic.div( + SQUtils.AmountsArithmetic.times( + SQUtils.AmountsArithmetic.fromString(inputDelocalized), + multiplier), + SQUtils.AmountsArithmetic.fromNumber(price)).toFixed() } } - onMaxInputBalanceChanged: { - input.validate() - } + contentItem: ColumnLayout { + StatusBaseText { + id: captionText - 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 - } - } - ] + visible: text.length > 0 - 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 + font.pixelSize: 13 + lineHeight: 18 + lineHeightMode: Text.FixedHeight + color: Theme.palette.directColor1 + elide: Text.ElideRight } + + RowLayout { + StyledTextField { + id: textField + + objectName: "amountToSend_textField" + + Layout.fillWidth: true + + implicitHeight: 44 + padding: 0 + background: null + + readOnly: !root.interactive + + color: text.length === 0 || (root.valid && !root.markAsInvalid) + ? Theme.palette.directColor1 + : Theme.palette.dangerColor1 + + placeholderText: { + if (!d.fiatMode || root.fiatDecimalPlaces === 0) + return "0" + + return "0" + root.decimalPoint + + "0".repeat(root.fiatDecimalPlaces) + } + + font.pixelSize: Utils.getFontSizeBasedOnLetterCount(text) + + validator: AmountValidator { + id: validator + + maxDecimalDigits: d.fiatMode ? root.fiatDecimalPlaces + : root.multiplierIndex + locale: root.locale.name + } + visible: !root.mainInputLoading + } + LoadingComponent { + objectName: "topAmountToSendInputLoadingComponent" + Layout.preferredWidth: textField.width + Layout.preferredHeight: textField.height + visible: root.mainInputLoading + } + } + + StatusBaseText { + id: bottomItem + + objectName: "bottomItemText" + + Layout.fillWidth: true + + text: { + const divisor = SQUtils.AmountsArithmetic.fromExponent( + d.fiatMode ? root.multiplierIndex + : root.fiatDecimalPlaces) + const divided = SQUtils.AmountsArithmetic.div( + SQUtils.AmountsArithmetic.fromString( + d.secondaryValue), divisor) + const asNumber = SQUtils.AmountsArithmetic.toNumber(divided) + + return d.fiatMode ? root.formatBalance(asNumber) + : root.formatFiat(asNumber) + } + + elide: Text.ElideMiddle + font.pixelSize: 13 + color: Theme.palette.directColor5 + + MouseArea { + objectName: "amountToSend_mouseArea" + + anchors.fill: parent + cursorShape: enabled ? Qt.PointingHandCursor : undefined + enabled: root.fiatInputInteractive + + onClicked: { + const secondaryValue = d.secondaryValue + + d.fiatMode = !d.fiatMode + + if (textField.length === 0) + return + + const decimalPlaces = d.fiatMode ? root.fiatDecimalPlaces + : root.multiplierIndex + const divisor = SQUtils.AmountsArithmetic.fromExponent( + decimalPlaces) + + const stringNumber = SQUtils.AmountsArithmetic.div( + SQUtils.AmountsArithmetic.fromString(secondaryValue), + divisor).toFixed(decimalPlaces) + + const trimmed = d.fiatMode + ? stringNumber + : d.removeDecimalTrailingZeros(stringNumber) + + textField.text = d.localize(trimmed) + } + } + visible: !root.bottomTextLoading + } + LoadingComponent { - objectName: "topAmountToSendInputLoadingComponent" - Layout.preferredWidth: topAmountToSendInput.width - Layout.preferredHeight: topAmountToSendInput.height - visible: root.mainInputLoading + objectName: "bottomItemTextLoadingComponent" + Layout.preferredWidth: bottomItem.width + Layout.preferredHeight: bottomItem.height + visible: root.bottomTextLoading } } - - 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 - } } diff --git a/ui/imports/shared/popups/send/views/AmountToSendNew.qml b/ui/imports/shared/popups/send/views/AmountToSendNew.qml deleted file mode 100644 index 596fb71d0a..0000000000 --- a/ui/imports/shared/popups/send/views/AmountToSendNew.qml +++ /dev/null @@ -1,284 +0,0 @@ -import QtQuick 2.15 -import QtQuick.Controls 2.15 -import QtQuick.Layouts 1.15 - -import StatusQ.Core 0.1 -import StatusQ.Core.Theme 0.1 -import StatusQ.Core.Utils 0.1 as SQUtils -import StatusQ.Components 0.1 -import StatusQ.Validators 0.1 - -import utils 1.0 -import shared.controls 1.0 - -Control { - id: root - - /* Crypto value in a base unit as a string integer, e.g. 1000000000000000000 - * for 1 ETH */ - readonly property alias amount: d.amountBaseUnit - - /* In fiat mode the input value is meant to be a fiat value, conversely, - * crypto value otherwise. */ - readonly property alias fiatMode: d.fiatMode - - /* Indicates whether toggling the fiatMode is enabled for the user */ - property bool fiatInputInteractive: interactive - - /* Indicates if input represent valid number. E.g. empty input or containing - * only decimal point is not valid. */ - readonly property alias valid: textField.acceptableInput - readonly property bool empty: textField.length === 0 - - // TODO: remove, temporarily for backward compatibility. External components - // should not rely on formatted amount because formatting rules are internal - // detail of that component. - readonly property alias text: textField.text - - /* Decimal point character to be displayed. Both "." and "," will be - * replaced by the provided decimal point on the fly */ - property alias decimalPoint: validator.decimalPoint - - /* Number of fiat decimal places used to limit allowed decimal places in - * fiatMode */ - property int fiatDecimalPlaces: 2 - - /* Specifies how divisible given cryptocurrency is, e.g. 18 for ETH. Used - * for limiting allowed decimal places and computing final amount as an - * integer value */ - property int multiplierIndex: 18 - - /* Price of one unit of given cryptocurrency (e.g. price for 1 ETH) */ - property real price: 1.0 - - property alias caption: captionText.text - property bool interactive: true - - readonly property bool cursorVisible: textField.cursorVisible - readonly property alias placeholderText: textField.placeholderText - - /* Loading states for the input and text below */ - property bool mainInputLoading - property bool bottomTextLoading - - /* Allows mark input as invalid when it's valid number but doesn't satisfy - * arbitrary external criteria, e.g. is higher than maximum expected value. */ - property bool markAsInvalid: false - - /* Methods for formatting crypto and fiat value expecting double values, - e.g. 1.0 for 1 ETH or 1.0 for 1 USD. */ - property var formatFiat: balance => - `${balance.toLocaleString(Qt.locale())} FIAT` - property var formatBalance: balance => - `${balance.toLocaleString(Qt.locale())} CRYPTO` - - /* Allows to set value to be displayed. The value is expected to be a not - localized string like "1", "1.1" or "0.000000023400234222". Provided - value will be formatted and displayed. Depending on the fiatMode flag - it will affect output amount appropriately. */ - function setValue(valueString) { - if (!valueString) - valueString = "0" - - const decimalPlaces = d.fiatMode ? root.fiatDecimalPlaces - : root.multiplierIndex - - const stringNumber = SQUtils.AmountsArithmetic.fromString( - valueString).toFixed(decimalPlaces) - - const trimmed = d.fiatMode - ? stringNumber - : d.removeDecimalTrailingZeros(stringNumber) - - textField.text = d.localize(trimmed) - } - - function clear() { - textField.clear() - } - - function forceActiveFocus() { - textField.forceActiveFocus() - } - - QtObject { - id: d - - property bool fiatMode: false - - readonly property string inputDelocalized: - root.valid && textField.length !== 0 - ? textField.text.replace(root.decimalPoint, ".") : "0" - - function removeDecimalTrailingZeros(num) { - if (!num.includes(".")) - return num - - return num.replace(/\.?0*$/g, "") - } - - function localize(num) { - return num.replace(".", root.decimalPoint) - } - - readonly property string amountBaseUnit: { - if (d.fiatMode) - return secondaryValue - - const multiplier = SQUtils.AmountsArithmetic.fromExponent( - root.multiplierIndex) - - return SQUtils.AmountsArithmetic.times( - SQUtils.AmountsArithmetic.fromString(inputDelocalized), - multiplier).toFixed() - } - - readonly property string secondaryValue: { - const price = isNaN(root.price) ? 0 : root.price - - if (!d.fiatMode) - return SQUtils.AmountsArithmetic.times( - SQUtils.AmountsArithmetic.fromString(inputDelocalized), - SQUtils.AmountsArithmetic.fromNumber( - price * (10 ** root.fiatDecimalPlaces))).toFixed() - - if (!price) // prevent div by zero below - return 0 - - const multiplier = SQUtils.AmountsArithmetic.fromExponent( - root.multiplierIndex) - - return SQUtils.AmountsArithmetic.div( - SQUtils.AmountsArithmetic.times( - SQUtils.AmountsArithmetic.fromString(inputDelocalized), - multiplier), - SQUtils.AmountsArithmetic.fromNumber(price)).toFixed() - } - } - - contentItem: ColumnLayout { - StatusBaseText { - id: captionText - - Layout.fillWidth: true - - visible: text.length > 0 - - font.pixelSize: 13 - lineHeight: 18 - lineHeightMode: Text.FixedHeight - color: Theme.palette.directColor1 - elide: Text.ElideRight - } - - RowLayout { - StyledTextField { - id: textField - - objectName: "amountToSend_textField" - - Layout.fillWidth: true - - implicitHeight: 44 - padding: 0 - background: null - - readOnly: !root.interactive - - color: text.length === 0 || (root.valid && !root.markAsInvalid) - ? Theme.palette.directColor1 - : Theme.palette.dangerColor1 - - placeholderText: { - if (!d.fiatMode || root.fiatDecimalPlaces === 0) - return "0" - - return "0" + root.decimalPoint - + "0".repeat(root.fiatDecimalPlaces) - } - - font.pixelSize: Utils.getFontSizeBasedOnLetterCount(text) - - validator: AmountValidator { - id: validator - - maxDecimalDigits: d.fiatMode ? root.fiatDecimalPlaces - : root.multiplierIndex - locale: root.locale.name - } - visible: !root.mainInputLoading - } - LoadingComponent { - objectName: "topAmountToSendInputLoadingComponent" - Layout.preferredWidth: textField.width - Layout.preferredHeight: textField.height - visible: root.mainInputLoading - } - } - - StatusBaseText { - id: bottomItem - - objectName: "bottomItemText" - - Layout.fillWidth: true - - text: { - const divisor = SQUtils.AmountsArithmetic.fromExponent( - d.fiatMode ? root.multiplierIndex - : root.fiatDecimalPlaces) - const divided = SQUtils.AmountsArithmetic.div( - SQUtils.AmountsArithmetic.fromString( - d.secondaryValue), divisor) - const asNumber = SQUtils.AmountsArithmetic.toNumber(divided) - - return d.fiatMode ? root.formatBalance(asNumber) - : root.formatFiat(asNumber) - } - - elide: Text.ElideMiddle - font.pixelSize: 13 - color: Theme.palette.directColor5 - - MouseArea { - objectName: "amountToSend_mouseArea" - - anchors.fill: parent - cursorShape: enabled ? Qt.PointingHandCursor : undefined - enabled: root.fiatInputInteractive - - onClicked: { - const secondaryValue = d.secondaryValue - - d.fiatMode = !d.fiatMode - - if (textField.length === 0) - return - - const decimalPlaces = d.fiatMode ? root.fiatDecimalPlaces - : root.multiplierIndex - const divisor = SQUtils.AmountsArithmetic.fromExponent( - decimalPlaces) - - const stringNumber = SQUtils.AmountsArithmetic.div( - SQUtils.AmountsArithmetic.fromString(secondaryValue), - divisor).toFixed(decimalPlaces) - - const trimmed = d.fiatMode - ? stringNumber - : d.removeDecimalTrailingZeros(stringNumber) - - textField.text = d.localize(trimmed) - } - } - visible: !root.bottomTextLoading - } - - LoadingComponent { - objectName: "bottomItemTextLoadingComponent" - Layout.preferredWidth: bottomItem.width - Layout.preferredHeight: bottomItem.height - visible: root.bottomTextLoading - } - } -} diff --git a/ui/imports/shared/popups/send/views/qmldir b/ui/imports/shared/popups/send/views/qmldir index 4c4cfba2ca..7cfecdf98b 100644 --- a/ui/imports/shared/popups/send/views/qmldir +++ b/ui/imports/shared/popups/send/views/qmldir @@ -1,6 +1,5 @@ AmountToReceive 1.0 AmountToReceive.qml AmountToSend 1.0 AmountToSend.qml -AmountToSendNew 1.0 AmountToSendNew.qml FeesView 1.0 FeesView.qml NetworkCardsComponent 1.0 NetworkCardsComponent.qml NetworkSelector 1.0 NetworkSelector.qml