diff --git a/ui/nim-status-client.pro b/ui/nim-status-client.pro index abff77f9c5..870ca9b325 100644 --- a/ui/nim-status-client.pro +++ b/ui/nim-status-client.pro @@ -382,6 +382,7 @@ DISTFILES += \ shared/CropCornerRectangle.qml \ shared/DelegateModelGeneralized.qml \ shared/FormGroup.qml \ + shared/GasSelectorButton.qml \ shared/IconButton.qml \ shared/ImageCropper.qml \ shared/ImageCropperModal.qml \ diff --git a/ui/shared/GasSelector.qml b/ui/shared/GasSelector.qml index ac2ed5b3d5..5ba6c10c8c 100644 --- a/ui/shared/GasSelector.qml +++ b/ui/shared/GasSelector.qml @@ -7,9 +7,10 @@ import "./" Item { id: root - anchors.left: parent.left - anchors.right: parent.right - height: sliderWrapper.height + Style.current.smallPadding + txtNetworkFee.height + buttonAdvanced.height + width: parent.width + height: Style.current.smallPadding + prioritytext.height + + (advancedMode ? advancedModeItemGroup.height : selectorButtons.height) + property double slowestGasPrice: 0 property double fastestGasPrice: 100 property double stepSize: ((root.fastestGasPrice - root.slowestGasPrice) / 10).toFixed(1) @@ -29,6 +30,8 @@ Item { property bool isValid: true readonly property string uuid: Utils.uuid() + property bool advancedMode: false + function defaultGasPrice() { return ((50 * (root.fastestGasPrice - root.slowestGasPrice) / 100) + root.slowestGasPrice) } @@ -40,247 +43,194 @@ Item { } let ethValue = root.getGasEthValue(inputGasPrice.text, inputGasLimit.text) let fiatValue = root.getFiatValue(ethValue, "ETH", root.defaultCurrency) - let summary = Utils.stripTrailingZeros(ethValue) + " ETH ~" + fiatValue + " " + root.defaultCurrency.toUpperCase() - labelGasPriceSummary.text = summary - labelGasPriceSummaryAdvanced.text = summary + selectedGasEthValue = ethValue selectedGasFiatValue = fiatValue } + function validate() { + // causes error on application load without a null check + if (!inputGasLimit || !inputGasPrice) { + return + } + inputGasLimit.validationError = "" + inputGasPrice.validationError = "" + const noInputLimit = inputGasLimit.text === "" + const noInputPrice = inputGasPrice.text === "" + if (noInputLimit) { + inputGasLimit.validationError = root.noInputErrorMessage + } + if (noInputPrice) { + inputGasPrice.validationError = root.noInputErrorMessage + } + if (isNaN(inputGasLimit.text)) { + inputGasLimit.validationError = invalidInputErrorMessage + } + if (isNaN(inputGasPrice.text)) { + inputGasPrice.validationError = invalidInputErrorMessage + } + let inputLimit = parseFloat(inputGasLimit.text || "0.00") + let inputPrice = parseFloat(inputGasPrice.text || "0.00") + if (inputLimit === 0.00) { + inputGasLimit.validationError = root.greaterThan0ErrorMessage + } + if (inputPrice === 0.00) { + inputGasPrice.validationError = root.greaterThan0ErrorMessage + } + const isValid = inputGasLimit.validationError === "" && inputGasPrice.validationError === "" + return isValid + } + + StyledText { - id: txtNetworkFee + id: prioritytext anchors.top: parent.top anchors.left: parent.left - //% "Network fee" - text: qsTrId("network-fee") + text: qsTr("Priority") font.weight: Font.Medium font.pixelSize: 13 color: Style.current.textColor } - StyledText { - id: labelGasPriceSummary - anchors.top: parent.top + StatusButton { + id: buttonAdvanced + anchors.verticalCenter: prioritytext.verticalCenter anchors.right: parent.right - font.weight: Font.Medium + text: advancedMode ? qsTr("Use suggestions") : qsTr("Use custom") + flat: true font.pixelSize: 13 - color: Style.current.secondaryText + onClicked: advancedMode = !advancedMode + } + + Row { + id: selectorButtons + visible: !advancedMode + anchors.top: prioritytext.bottom + anchors.topMargin: Style.current.halfPadding + spacing: 11 + + ButtonGroup { + id: gasGroup + onClicked: updateGasEthValue() + } + + GasSelectorButton { + buttonGroup: gasGroup + text: qsTr("Low") + price: slowestGasPrice + gasLimit: inputGasLimit ? inputGasLimit.text : "" + getGasEthValue: root.getGasEthValue + getFiatValue: root.getFiatValue + defaultCurrency: root.defaultCurrency + onChecked: inputGasPrice.text = price + } + GasSelectorButton { + buttonGroup: gasGroup + checkedByDefault: true + text: qsTr("Optimal") + price: (fastestGasPrice + slowestGasPrice) / 2 + gasLimit: inputGasLimit ? inputGasLimit.text : "" + getGasEthValue: root.getGasEthValue + getFiatValue: root.getFiatValue + defaultCurrency: root.defaultCurrency + onChecked: inputGasPrice.text = price + } + + GasSelectorButton { + buttonGroup: gasGroup + text: qsTr("High") + price: fastestGasPrice + gasLimit: inputGasLimit ? inputGasLimit.text : "" + getGasEthValue: root.getGasEthValue + getFiatValue: root.getFiatValue + defaultCurrency: root.defaultCurrency + onChecked: inputGasPrice.text = price + } } Item { - id: sliderWrapper - anchors.topMargin: Style.current.smallPadding - anchors.top: labelGasPriceSummary.bottom - height: sliderWrapper.visible ? gasSlider.height + labelSlow.height + Style.current.padding : 0 + id: advancedModeItemGroup + anchors.top: prioritytext.bottom + anchors.topMargin: 14 + visible: root.advancedMode width: parent.width - visible: Number(root.selectedGasPrice) >= Number(root.slowestGasPrice) && Number(root.selectedGasPrice) <= Number(root.fastestGasPrice) + height: childrenRect.height - StatusSlider { - id: gasSlider - minimumValue: root.slowestGasPrice - maximumValue: root.fastestGasPrice - stepSize: root.stepSize - value: root.defaultGasPrice() - onValueChanged: { - if (!isNaN(gasSlider.value)) { - inputGasPrice.text = gasSlider.value + "" + Input { + id: inputGasLimit + label: qsTr("Gas amount limit") + text: "21000" + customHeight: 56 + anchors.top: parent.top + anchors.left: parent.left + anchors.right: inputGasPrice.left + anchors.rightMargin: Style.current.padding + placeholderText: "21000" + validationErrorAlignment: TextEdit.AlignRight + validationErrorTopMargin: 8 + onTextChanged: { + if (root.validate()) { root.updateGasEthValue() } } - visible: parent.visible } - StyledText { - id: labelSlow - anchors.top: gasSlider.bottom - anchors.topMargin: Style.current.padding - anchors.left: parent.left - //% "Slow" - text: qsTrId("slow") - font.pixelSize: 15 - color: Style.current.textColor - visible: parent.visible - } - - StyledText { - id: labelOptimal - anchors.top: gasSlider.bottom - anchors.topMargin: Style.current.padding - anchors.horizontalCenter: gasSlider.horizontalCenter - //% "Optimal" - text: qsTrId("optimal") - font.pixelSize: 15 - color: Style.current.textColor - visible: parent.visible - } - - StyledText { - id: labelFast - anchors.top: gasSlider.bottom - anchors.topMargin: Style.current.padding + Input { + id: inputGasPrice + label: qsTr("Per-gas overall limit") + anchors.top: parent.top + anchors.left: undefined anchors.right: parent.right - //% "Fast" - text: qsTrId("fast") - font.pixelSize: 15 - color: Style.current.textColor - visible: parent.visible - } - } - - StatusButton { - id: buttonReset - anchors.top: sliderWrapper.bottom - anchors.topMargin: sliderWrapper.visible ? Style.current.smallPadding : 0 - anchors.right: buttonAdvanced.left - anchors.rightMargin: Style.current.padding - //% "Reset" - text: qsTrId("reset") - flat: true - font.pixelSize: 13 - visible: !sliderWrapper.visible - onClicked: { - gasSlider.value = root.defaultGasPrice() - inputGasPrice.text = root.defaultGasPrice() - } - } - - StatusButton { - id: buttonAdvanced - anchors.top: sliderWrapper.bottom - anchors.topMargin: sliderWrapper.visible ? Style.current.smallPadding : 0 - anchors.right: parent.right - anchors.rightMargin: -Style.current.padding - //% "Advanced" - text: qsTrId("advanced") - flat: true - font.pixelSize: 13 - onClicked: { - customNetworkFeeDialog.open() - } - } - - ModalPopup { - id: customNetworkFeeDialog - //% "Custom Network Fee" - title: qsTrId("custom-network-fee") - height: 286 - width: 400 - property bool isValid: true - - onIsValidChanged: { - root.isValid = isValid + width: 130 + customHeight: 56 + text: root.defaultGasPrice() + placeholderText: "20" + onTextChanged: { + if (root.validate()) { + root.updateGasEthValue() + } + } } - function validate() { - // causes error on application load without a null check - if (!inputGasLimit || !inputGasPrice) { - return - } - inputGasLimit.validationError = "" - inputGasPrice.validationError = "" - const noInputLimit = inputGasLimit.text === "" - const noInputPrice = inputGasPrice.text === "" - if (noInputLimit) { - inputGasLimit.validationError = root.noInputErrorMessage - } - if (noInputPrice) { - inputGasPrice.validationError = root.noInputErrorMessage - } - if (isNaN(inputGasLimit.text)) { - inputGasLimit.validationError = invalidInputErrorMessage - } - if (isNaN(inputGasPrice.text)) { - inputGasPrice.validationError = invalidInputErrorMessage - } - let inputLimit = parseFloat(inputGasLimit.text || "0.00") - let inputPrice = parseFloat(inputGasPrice.text || "0.00") - if (inputLimit === 0.00) { - inputGasLimit.validationError = root.greaterThan0ErrorMessage - } - if (inputPrice === 0.00) { - inputGasPrice.validationError = root.greaterThan0ErrorMessage - } - const isValid = inputGasLimit.validationError === "" && inputGasPrice.validationError === "" - customNetworkFeeDialog.isValid = isValid - return isValid - } - - Input { - id: inputGasLimit - //% "Gas limit" - label: qsTrId("gas-limit") - text: "21000" - customHeight: 56 - anchors.top: parent.top - anchors.left: parent.left - anchors.right: inputGasPrice.left - anchors.rightMargin: Style.current.padding - placeholderText: "21000" - validationErrorAlignment: TextEdit.AlignRight - validationErrorTopMargin: 8 - onTextChanged: { - if (customNetworkFeeDialog.validate()) { - root.updateGasEthValue() - } - } - } - - Input { - id: inputGasPrice - //% "Gas price" - label: qsTrId("gas-price") - anchors.top: parent.top - anchors.left: undefined - anchors.right: parent.right - width: 130 - customHeight: 56 - text: root.defaultGasPrice() - placeholderText: "20" - onTextChanged: { - if (inputGasPrice.text.trim() === "") { - inputGasPrice.text = root.defaultGasPrice() - } - if (customNetworkFeeDialog.validate()) { - root.updateGasEthValue() - } - } - - StyledText { - color: Style.current.darkGrey + StyledText { + color: Style.current.secondaryText //% "Gwei" text: qsTrId("gwei") anchors.top: parent.top anchors.topMargin: 42 - anchors.right: parent.right + anchors.right: inputGasPrice.right anchors.rightMargin: Style.current.padding font.pixelSize: 15 - } } StyledText { - id: labelGasPriceSummaryAdvanced - anchors.bottom: parent.bottom - anchors.bottomMargin: Style.current.smallPadding - anchors.right: parent.right - font.weight: Font.Medium + id: maxPriorityFeeText + text: qsTr("Maximum priority fee: %1 ETH").arg(selectedGasEthValue) + anchors.top: inputGasLimit.bottom + anchors.topMargin: 19 font.pixelSize: 13 - color: Style.current.secondaryText } - footer: StatusButton { - id: applyButton - anchors.right: parent.right - anchors.rightMargin: Style.current.smallPadding - //% "Apply" - text: qsTrId("invalid-key-confirm") - anchors.bottom: parent.bottom - enabled: customNetworkFeeDialog.isValid - onClicked: { - if (customNetworkFeeDialog.validate()) { - root.updateGasEthValue() - } - customNetworkFeeDialog.close() - } + StyledText { + id: maxPriorityFeeFiatText + text: `${selectedGasFiatValue} ${root.defaultCurrency}` + anchors.verticalCenter: maxPriorityFeeText.verticalCenter + anchors.left: maxPriorityFeeText.right + anchors.leftMargin: 6 + color: Style.current.secondaryText + anchors.topMargin: 19 + font.pixelSize: 13 + } + + StyledText { + id: maxPriorityFeeDetailsText + text: qsTr("Maximum overall price for the transaction. If the block base fee exceeds this, it will be included in a following block with a lower base fee.") + width: parent.width + anchors.top: maxPriorityFeeText.bottom + anchors.topMargin: Style.current.smallPadding + font.pixelSize: 13 + color: Style.current.secondaryText + wrapMode: Text.WordWrap } } } diff --git a/ui/shared/GasSelectorButton.qml b/ui/shared/GasSelectorButton.qml new file mode 100644 index 0000000000..c7b7eab013 --- /dev/null +++ b/ui/shared/GasSelectorButton.qml @@ -0,0 +1,86 @@ +import QtQuick 2.13 +import QtQuick.Controls 2.13 +import QtQuick.Layouts 1.13 +import "../imports" +import "./status" +import "./" + + +Rectangle { + property var buttonGroup + property string text: qsTr("Low") + property string gasLimit + property double price: 1 + property string defaultCurrency: "USD" + property bool hovered: false + property bool checkedByDefault: false + property var getGasEthValue: function () {} + property var getFiatValue: function () {} + property double ethValue: { + if (!gasLimit) { + return 0 + } + return getGasEthValue(price, gasLimit) + } + property double fiatValue: getFiatValue(ethValue, "ETH", defaultCurrency) + signal checked() + + id: gasRectangle + border.color: hovered || gasRadioBtn.checked ? Style.current.primary : Style.current.border + border.width: 1 + color: Style.current.transparent + width: 130 + height: 120 + clip: true + radius: Style.current.radius + + StatusRadioButton { + id: gasRadioBtn + ButtonGroup.group: buttonGroup + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top + anchors.topMargin: 14 + checked: gasRectangle.checkedByDefault + onCheckedChanged: { + if (checked) { + gasRectangle.checked() + } + } + } + + StyledText { + id: gasText + text: gasRectangle.text + font.pixelSize: 15 + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: gasRadioBtn.bottom + anchors.topMargin: 6 + } + + StyledText { + id: ethText + text: gasRectangle.ethValue + " ETH" + font.pixelSize: 13 + color: Style.current.secondaryText + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: gasText.bottom + anchors.topMargin: 4 + } + + StyledText { + id: fiatText + text: `${gasRectangle.fiatValue} ${gasRectangle.defaultCurrency}` + font.pixelSize: 13 + color: Style.current.secondaryText + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: ethText.bottom + } + + MouseArea { + anchors.fill: parent + hoverEnabled: true + onEntered: gasRectangle.hovered = true + onExited: gasRectangle.hovered = false + onClicked: gasRadioBtn.toggle() + } +}