feat(wallet): redesign gas selector for new EIP

Fixes #2536
This commit is contained in:
Jonathan Rainville 2021-05-21 16:19:03 -04:00 committed by Iuri Matias
parent 06484ec2c0
commit cbdbb6eb8b
3 changed files with 242 additions and 205 deletions

View File

@ -382,6 +382,7 @@ DISTFILES += \
shared/CropCornerRectangle.qml \ shared/CropCornerRectangle.qml \
shared/DelegateModelGeneralized.qml \ shared/DelegateModelGeneralized.qml \
shared/FormGroup.qml \ shared/FormGroup.qml \
shared/GasSelectorButton.qml \
shared/IconButton.qml \ shared/IconButton.qml \
shared/ImageCropper.qml \ shared/ImageCropper.qml \
shared/ImageCropperModal.qml \ shared/ImageCropperModal.qml \

View File

@ -7,9 +7,10 @@ import "./"
Item { Item {
id: root id: root
anchors.left: parent.left width: parent.width
anchors.right: parent.right height: Style.current.smallPadding + prioritytext.height +
height: sliderWrapper.height + Style.current.smallPadding + txtNetworkFee.height + buttonAdvanced.height (advancedMode ? advancedModeItemGroup.height : selectorButtons.height)
property double slowestGasPrice: 0 property double slowestGasPrice: 0
property double fastestGasPrice: 100 property double fastestGasPrice: 100
property double stepSize: ((root.fastestGasPrice - root.slowestGasPrice) / 10).toFixed(1) property double stepSize: ((root.fastestGasPrice - root.slowestGasPrice) / 10).toFixed(1)
@ -29,6 +30,8 @@ Item {
property bool isValid: true property bool isValid: true
readonly property string uuid: Utils.uuid() readonly property string uuid: Utils.uuid()
property bool advancedMode: false
function defaultGasPrice() { function defaultGasPrice() {
return ((50 * (root.fastestGasPrice - root.slowestGasPrice) / 100) + root.slowestGasPrice) return ((50 * (root.fastestGasPrice - root.slowestGasPrice) / 100) + root.slowestGasPrice)
} }
@ -40,247 +43,194 @@ Item {
} }
let ethValue = root.getGasEthValue(inputGasPrice.text, inputGasLimit.text) let ethValue = root.getGasEthValue(inputGasPrice.text, inputGasLimit.text)
let fiatValue = root.getFiatValue(ethValue, "ETH", root.defaultCurrency) 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 selectedGasEthValue = ethValue
selectedGasFiatValue = fiatValue 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 { StyledText {
id: txtNetworkFee id: prioritytext
anchors.top: parent.top anchors.top: parent.top
anchors.left: parent.left anchors.left: parent.left
//% "Network fee" text: qsTr("Priority")
text: qsTrId("network-fee")
font.weight: Font.Medium font.weight: Font.Medium
font.pixelSize: 13 font.pixelSize: 13
color: Style.current.textColor color: Style.current.textColor
} }
StyledText { StatusButton {
id: labelGasPriceSummary id: buttonAdvanced
anchors.top: parent.top anchors.verticalCenter: prioritytext.verticalCenter
anchors.right: parent.right anchors.right: parent.right
font.weight: Font.Medium text: advancedMode ? qsTr("Use suggestions") : qsTr("Use custom")
flat: true
font.pixelSize: 13 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 { Item {
id: sliderWrapper id: advancedModeItemGroup
anchors.topMargin: Style.current.smallPadding anchors.top: prioritytext.bottom
anchors.top: labelGasPriceSummary.bottom anchors.topMargin: 14
height: sliderWrapper.visible ? gasSlider.height + labelSlow.height + Style.current.padding : 0 visible: root.advancedMode
width: parent.width width: parent.width
visible: Number(root.selectedGasPrice) >= Number(root.slowestGasPrice) && Number(root.selectedGasPrice) <= Number(root.fastestGasPrice) height: childrenRect.height
StatusSlider { Input {
id: gasSlider id: inputGasLimit
minimumValue: root.slowestGasPrice label: qsTr("Gas amount limit")
maximumValue: root.fastestGasPrice text: "21000"
stepSize: root.stepSize customHeight: 56
value: root.defaultGasPrice() anchors.top: parent.top
onValueChanged: { anchors.left: parent.left
if (!isNaN(gasSlider.value)) { anchors.right: inputGasPrice.left
inputGasPrice.text = gasSlider.value + "" anchors.rightMargin: Style.current.padding
placeholderText: "21000"
validationErrorAlignment: TextEdit.AlignRight
validationErrorTopMargin: 8
onTextChanged: {
if (root.validate()) {
root.updateGasEthValue() root.updateGasEthValue()
} }
} }
visible: parent.visible
} }
StyledText { Input {
id: labelSlow id: inputGasPrice
anchors.top: gasSlider.bottom label: qsTr("Per-gas overall limit")
anchors.topMargin: Style.current.padding anchors.top: parent.top
anchors.left: parent.left anchors.left: undefined
//% "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
anchors.right: parent.right anchors.right: parent.right
//% "Fast" width: 130
text: qsTrId("fast") customHeight: 56
font.pixelSize: 15 text: root.defaultGasPrice()
color: Style.current.textColor placeholderText: "20"
visible: parent.visible onTextChanged: {
} if (root.validate()) {
} root.updateGasEthValue()
}
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
} }
function validate() { StyledText {
// causes error on application load without a null check color: Style.current.secondaryText
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
//% "Gwei" //% "Gwei"
text: qsTrId("gwei") text: qsTrId("gwei")
anchors.top: parent.top anchors.top: parent.top
anchors.topMargin: 42 anchors.topMargin: 42
anchors.right: parent.right anchors.right: inputGasPrice.right
anchors.rightMargin: Style.current.padding anchors.rightMargin: Style.current.padding
font.pixelSize: 15 font.pixelSize: 15
}
} }
StyledText { StyledText {
id: labelGasPriceSummaryAdvanced id: maxPriorityFeeText
anchors.bottom: parent.bottom text: qsTr("Maximum priority fee: %1 ETH").arg(selectedGasEthValue)
anchors.bottomMargin: Style.current.smallPadding anchors.top: inputGasLimit.bottom
anchors.right: parent.right anchors.topMargin: 19
font.weight: Font.Medium
font.pixelSize: 13 font.pixelSize: 13
color: Style.current.secondaryText
} }
footer: StatusButton { StyledText {
id: applyButton id: maxPriorityFeeFiatText
anchors.right: parent.right text: `${selectedGasFiatValue} ${root.defaultCurrency}`
anchors.rightMargin: Style.current.smallPadding anchors.verticalCenter: maxPriorityFeeText.verticalCenter
//% "Apply" anchors.left: maxPriorityFeeText.right
text: qsTrId("invalid-key-confirm") anchors.leftMargin: 6
anchors.bottom: parent.bottom color: Style.current.secondaryText
enabled: customNetworkFeeDialog.isValid anchors.topMargin: 19
onClicked: { font.pixelSize: 13
if (customNetworkFeeDialog.validate()) { }
root.updateGasEthValue()
} StyledText {
customNetworkFeeDialog.close() 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
} }
} }
} }

View File

@ -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()
}
}